Skip to content

Commit

Permalink
#21 - added manual charging options (#24)
Browse files Browse the repository at this point in the history
added more documentation to BatTags
  • Loading branch information
jnk-cons committed Jan 27, 2024
1 parent 5b2bd51 commit f6d3fdd
Show file tree
Hide file tree
Showing 16 changed files with 393 additions and 55 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1964,7 +1964,7 @@ enum class EMSTag(
/**
* hex = "0x0100008E", type = DataType.NONE
*
* You know what the tag means or want to improve the tag description? Create a [Ticket](https://github.com/jnk-cons/easy-rscp/issues/new?title=Documentation+improvement+for+EMSTag.REQ_GET_MANUAL_CHARGE&labels=documentation&body=Documentation+update+for+enum+EMSTag.REQ_GET_MANUAL_CHARGE:).
* Request block to obtain the current status of the manual loading function. A container block of type [GET_MANUAL_CHARGE] is delivered in response.
*
* Original E3DC Documentation:
*
Expand All @@ -1977,7 +1977,9 @@ enum class EMSTag(
/**
* hex = "0x0180008E", type = DataType.CONTAINER
*
* You know what the tag means or want to improve the tag description? Create a [Ticket](https://github.com/jnk-cons/easy-rscp/issues/new?title=Documentation+improvement+for+EMSTag.GET_MANUAL_CHARGE&labels=documentation&body=Documentation+update+for+enum+EMSTag.GET_MANUAL_CHARGE:).
* Container block that is sent in response to a [REQ_GET_MANUAL_CHARGE] request.
* As a rule, the container contains the blocks [MANUAL_CHARGE_ACTIVE], [MANUAL_CHARGE_START_COUNTER],
* [MANUAL_CHARGE_ENERGY_COUNTER] and [MANUAL_CHARGE_LASTSTART].
*
* Original E3DC Documentation:
*
Expand All @@ -1998,12 +2000,12 @@ enum class EMSTag(
*
* de:
*/
MANUAL_CHARGE_START_COUNTER(hex = "0x01000150", type = DataType.UINT32),
MANUAL_CHARGE_START_COUNTER(hex = "0x01000150", type = DataType.INT64),

/**
* hex = "0x01000151", type = DataType.BOOL
*
* You know what the tag means or want to improve the tag description? Create a [Ticket](https://github.com/jnk-cons/easy-rscp/issues/new?title=Documentation+improvement+for+EMSTag.MANUAL_CHARGE_ACTIVE&labels=documentation&body=Documentation+update+for+enum+EMSTag.MANUAL_CHARGE_ACTIVE:).
* Data block that is delivered within the [GET_MANUAL_CHARGE] container. Contains the information whether a manual loading process is currently running or not.
*
* Original E3DC Documentation:
*
Expand All @@ -2014,22 +2016,24 @@ enum class EMSTag(
MANUAL_CHARGE_ACTIVE(hex = "0x01000151", type = DataType.BOOL),

/**
* hex = "0x01000152", type = DataType.UINT32
* hex = "0x01000152", type = DataType.DOUBLE64
*
* You know what the tag means or want to improve the tag description? Create a [Ticket](https://github.com/jnk-cons/easy-rscp/issues/new?title=Documentation+improvement+for+EMSTag.MANUAL_CHARGE_ENERGY_COUNTER&labels=documentation&body=Documentation+update+for+enum+EMSTag.MANUAL_CHARGE_ENERGY_COUNTER:).
* Data block that is delivered within the [GET_MANUAL_CHARGE] container. Contains information on how many watt-hours of energy were charged during the last manual charging process.
* Attention: The meter is reset on the following day for most home power stations.
*
* Original E3DC Documentation:
*
* en:
*
* de:
*/
MANUAL_CHARGE_ENERGY_COUNTER(hex = "0x01000152", type = DataType.UINT32),
MANUAL_CHARGE_ENERGY_COUNTER(hex = "0x01000152", type = DataType.DOUBLE64),

/**
* hex = "0x01000153", type = DataType.TIMESTAMP
*
* You know what the tag means or want to improve the tag description? Create a [Ticket](https://github.com/jnk-cons/easy-rscp/issues/new?title=Documentation+improvement+for+EMSTag.MANUAL_CHARGE_LASTSTART&labels=documentation&body=Documentation+update+for+enum+EMSTag.MANUAL_CHARGE_LASTSTART:).
* Data block that is delivered within the [GET_MANUAL_CHARGE] container. Contains information on when the last manual storage loading took place.
* Attention: The timestamp seems to be reset from time to time (The algorithm is unknown to me). In this case, the block simply contains garbage data.
*
* Original E3DC Documentation:
*
Expand All @@ -2042,7 +2046,8 @@ enum class EMSTag(
/**
* hex = "0x0100008F", type = DataType.UINT32
*
* You know what the tag means or want to improve the tag description? Create a [Ticket](https://github.com/jnk-cons/easy-rscp/issues/new?title=Documentation+improvement+for+EMSTag.REQ_START_MANUAL_CHARGE&labels=documentation&body=Documentation+update+for+enum+EMSTag.REQ_START_MANUAL_CHARGE:).
* Request block to start a manual charging process. The amount of energy to be charged in watt hours must be entered as the value.
* A data block of the type [START_MANUAL_CHARGE] is delivered in response.
*
* Original E3DC Documentation:
*
Expand All @@ -2055,7 +2060,7 @@ enum class EMSTag(
/**
* hex = "0x0180008F", type = DataType.BOOL
*
* You know what the tag means or want to improve the tag description? Create a [Ticket](https://github.com/jnk-cons/easy-rscp/issues/new?title=Documentation+improvement+for+EMSTag.START_MANUAL_CHARGE&labels=documentation&body=Documentation+update+for+enum+EMSTag.START_MANUAL_CHARGE:).
* Response block to a [REQ_START_MANUAL_CHARGE] request. Contains the information as to whether the loading process could be started or not.
*
* Original E3DC Documentation:
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package de.jnkconsulting.e3dc.easyrscp.api.service

import de.jnkconsulting.e3dc.easyrscp.api.service.model.BatterySpec
import de.jnkconsulting.e3dc.easyrscp.api.service.model.BatteryStatus
import de.jnkconsulting.e3dc.easyrscp.api.service.model.PowerState

/**
* Service to query the battery specification and status data
Expand All @@ -12,7 +11,7 @@ import de.jnkconsulting.e3dc.easyrscp.api.service.model.PowerState
interface BatteryService {

/**
* Liest die Batteriespezifikation aus dem E3DC Hauskraftwerk
* Reads the battery specification from the E3DC home power station
*
* @return Specification of the battery. As a rule, the list contains only one element. Theoretically, however, a home power station can have several batteries
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package de.jnkconsulting.e3dc.easyrscp.api.service

import de.jnkconsulting.e3dc.easyrscp.api.service.model.ChargingConfiguration
import de.jnkconsulting.e3dc.easyrscp.api.service.model.ChargingLimits
import de.jnkconsulting.e3dc.easyrscp.api.service.model.ManualChargeState
import de.jnkconsulting.e3dc.easyrscp.api.service.model.WriteChargingLimitsResult

/**
Expand Down Expand Up @@ -32,4 +33,38 @@ interface ChargingService {
* @since 2.0
*/
fun writeLimits(limits: ChargingLimits): WriteChargingLimitsResult

/**
* Reads out the status of manual battery charging
*
* @return Status of manual battery charging
*
* @since 2.2
*/
fun readManualChargeState(): ManualChargeState

/**
* Starts a manual charging process for the battery. Attention: If there is not enough excess production, the battery is charged with mains power.
*
* Most home power stations only allow one manual charge per day. easy-rscp has no influence on this.
*
* The status of the charging process can be monitored using the [readManualChargeState] function.
*
* @param amountWh Amount of energy to be charged in Wh
*
* @return true if the start of the loading process was successful. Otherwise false
*
* @since 2.2
*
*/
fun startManualCharge(amountWh: Int): Boolean

/**
* Stops a manual charging process.
*
* @return true if stopping the charging process was successful or no charging process is currently taking place. Otherwise false
*
* @since 2.2
*/
fun stopManualCharge(): Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package de.jnkconsulting.e3dc.easyrscp.api.service.model

import java.time.Instant

data class ManualChargeState(
val active: Boolean,
val chargedEnergyWh: Double,
val lastRun: Instant
)
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package de.jnkconsulting.e3dc.easyrscp.service

import de.jnkconsulting.e3dc.easyrscp.api.connection.ConnectionPool
import de.jnkconsulting.e3dc.easyrscp.api.connection.HomePowerPlantConnection
import de.jnkconsulting.e3dc.easyrscp.api.service.ChargingService
import de.jnkconsulting.e3dc.easyrscp.api.service.model.ChargingConfiguration
import de.jnkconsulting.e3dc.easyrscp.api.service.model.ChargingLimits
import de.jnkconsulting.e3dc.easyrscp.api.service.model.ManualChargeState
import de.jnkconsulting.e3dc.easyrscp.api.service.model.WriteChargingLimitsResult
import de.jnkconsulting.e3dc.easyrscp.service.converter.ChargingConfigurationConverter
import de.jnkconsulting.e3dc.easyrscp.service.converter.FrameConverter
import de.jnkconsulting.e3dc.easyrscp.service.converter.WriteChargingLimitsResultConverter
import de.jnkconsulting.e3dc.easyrscp.service.creator.FrameCreator
import de.jnkconsulting.e3dc.easyrscp.service.creator.RequestChargingConfigurationCreator
import de.jnkconsulting.e3dc.easyrscp.service.creator.SetPowerSettingsCreator
import de.jnkconsulting.e3dc.easyrscp.service.converter.*
import de.jnkconsulting.e3dc.easyrscp.service.creator.*
import mu.KotlinLogging

/**
Expand All @@ -21,15 +19,26 @@ import mu.KotlinLogging
* @param convertFrameToWriteChargingLimitsResult Converter to create a [WriteChargingLimitsResult] object from a frame. By default, an instance of [WriteChargingLimitsResultConverter] is used
* @param createRequestChargingConfigurationFrame Creator for frames to query the configuration of the charging system from the home power plant. By default, an instance of [RequestChargingConfigurationCreator] is used
* @param createSetPowerSettingsFrame Creator for frames to query the configuration of the charging system from the home power plant. By default, an instance of [SetPowerSettingsCreator] is used
* @param convertFrameToManualChargeState Converter to create a [ManualChargeState] object from a frame. Bu default, an instance of [ManualChargeStateConverter] is used
* @param createRequestManualChargeState Creator for frames to query the manual charge statefrom the home power station. By default, an instance of [RequestManualChargeStateCreator] is used
* @param convertFrameToStartManualChargeResponseState Converter to create a boolean result from an answer frame of a request manualm charge start request. Bu default, an instance of [StartManualChargeResponseConverter] is used
* @param createRequestStartManualCharge Creator for frames to request the start of a manual battery charge. By default, an instance of [RequestStartManualChargeCreator] is used
*
* @since 2.0
* @since 2.2
* - new function [readManualChargeState]
* - new function [startManualCharge]
*/
class DefaultChargingService(
private val connectionPool: ConnectionPool,
private val convertFrameToChargingConfiguration: FrameConverter<ChargingConfiguration> = ChargingConfigurationConverter(),
private val convertFrameToWriteChargingLimitsResult: FrameConverter<WriteChargingLimitsResult> = WriteChargingLimitsResultConverter(),
private val createRequestChargingConfigurationFrame: FrameCreator<Nothing?> = RequestChargingConfigurationCreator(),
private val createSetPowerSettingsFrame: FrameCreator<ChargingLimits> = SetPowerSettingsCreator()
private val createSetPowerSettingsFrame: FrameCreator<ChargingLimits> = SetPowerSettingsCreator(),
private val convertFrameToManualChargeState: FrameConverter<ManualChargeState> = ManualChargeStateConverter(),
private val createRequestManualChargeState: FrameCreator<Nothing?> = RequestManualChargeStateCreator(),
private val convertFrameToStartManualChargeResponseState: FrameConverter<Boolean> = StartManualChargeResponseConverter(),
private val createRequestStartManualCharge: FrameCreator<Int> = RequestStartManualChargeCreator()
): ChargingService {

private val logger = KotlinLogging.logger { }
Expand All @@ -52,4 +61,41 @@ class DefaultChargingService(
convertFrameToWriteChargingLimitsResult(writeResponse)
}

override fun readManualChargeState() =
connectionPool.executeAndRelease {
executeReadManualChargeState(it)
}

override fun startManualCharge(amountWh: Int) =
connectionPool.executeAndRelease {
executeManualCharge(it, amountWh)
}

override fun stopManualCharge(): Boolean =
connectionPool.executeAndRelease {
val state = executeReadManualChargeState(it)
if (state.active) {
executeManualCharge(it, 0)
}
else {
true
}
}

private fun executeReadManualChargeState(connection: HomePowerPlantConnection): ManualChargeState {
val request = createRequestManualChargeState(null)
logger.trace { "Requesting manual charge state: $request" }
val response = connection.send(request)
logger.trace { "Received response for manual charge state: $response" }
return convertFrameToManualChargeState(response)
}

private fun executeManualCharge(connection: HomePowerPlantConnection, amountWh: Int): Boolean {
val request = createRequestStartManualCharge(amountWh)
logger.trace { "Requesting start of manual charge: $request" }
val response = connection.send(request)
logger.trace { "Received response for start manual charge state: $response" }
return convertFrameToStartManualChargeResponseState(response)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package de.jnkconsulting.e3dc.easyrscp.service.converter

import de.jnkconsulting.e3dc.easyrscp.api.frame.Frame
import de.jnkconsulting.e3dc.easyrscp.api.frame.tags.EMSTag
import de.jnkconsulting.e3dc.easyrscp.api.service.model.ManualChargeState

/**
* Converter to build a [ManualChargeState] object from a frame
*
* @since 2.2
*/
class ManualChargeStateConverter: FrameConverter<ManualChargeState> {

override fun invoke(frame: Frame): ManualChargeState =
ManualChargeState(
active = frame.booleanByTag(EMSTag.MANUAL_CHARGE_ACTIVE, EMSTag.GET_MANUAL_CHARGE),
chargedEnergyWh = frame.doubleByTag(EMSTag.MANUAL_CHARGE_ENERGY_COUNTER, EMSTag.GET_MANUAL_CHARGE),
lastRun = frame.instantByTag(EMSTag.MANUAL_CHARGE_LASTSTART, EMSTag.GET_MANUAL_CHARGE),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package de.jnkconsulting.e3dc.easyrscp.service.converter

import de.jnkconsulting.e3dc.easyrscp.api.frame.Frame
import de.jnkconsulting.e3dc.easyrscp.api.frame.tags.EMSTag
import de.jnkconsulting.e3dc.easyrscp.api.service.model.ManualChargeState

/**
* Converter to build the response of a [EMSTag.REQ_START_MANUAL_CHARGE] request
*
* @since 2.2
*/
class StartManualChargeResponseConverter: FrameConverter<Boolean> {

override fun invoke(frame: Frame) =
frame.booleanByTag(EMSTag.START_MANUAL_CHARGE)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.jnkconsulting.e3dc.easyrscp.service.creator


import de.jnkconsulting.e3dc.easyrscp.api.frame.Frame
import de.jnkconsulting.e3dc.easyrscp.api.frame.tags.EMSTag
import de.jnkconsulting.e3dc.easyrscp.api.frame.tags.InfoTag
import de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder
import de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder

/**
* [de.jnkconsulting.e3dc.easyrscp.api.frame.Frame] generator to request the current manual charge state
*
* @since 2.2
*/
class RequestManualChargeStateCreator: FrameCreator<Nothing?> {

override fun invoke(param: Nothing?): Frame =
FrameBuilder()
.addData(
DataBuilder().tag(EMSTag.REQ_GET_MANUAL_CHARGE).build()
)
.build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package de.jnkconsulting.e3dc.easyrscp.service.creator


import de.jnkconsulting.e3dc.easyrscp.api.frame.Frame
import de.jnkconsulting.e3dc.easyrscp.api.frame.tags.EMSTag
import de.jnkconsulting.e3dc.easyrscp.frame.DataBuilder
import de.jnkconsulting.e3dc.easyrscp.frame.FrameBuilder

/**
* [de.jnkconsulting.e3dc.easyrscp.api.frame.Frame] generator to request the the start of a manual charge
*
* @since 2.2
*/
class RequestStartManualChargeCreator: FrameCreator<Int> {

override fun invoke(amount: Int): Frame =
FrameBuilder()
.addData(
DataBuilder().tag(EMSTag.REQ_START_MANUAL_CHARGE).uint32(amount).build()
)
.build()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class DefaultBatteryServiceTest: IntegrationTestBase() {
println(result)
}

@Test
//@Test
@EnabledIfEnvironmentVariable(named = "E3DC_HOST", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "E3DC_USER", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "RSCP_PASSWORD", matches = ".*\\S+.*")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable

class DefaultChargingServiceTest: IntegrationTestBase() {

@Test
// @Test
@EnabledIfEnvironmentVariable(named = "E3DC_HOST", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "E3DC_USER", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "RSCP_PASSWORD", matches = ".*\\S+.*")
Expand All @@ -17,7 +17,7 @@ class DefaultChargingServiceTest: IntegrationTestBase() {
println(result)
}

// @Test
// @Test
@EnabledIfEnvironmentVariable(named = "E3DC_HOST", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "E3DC_USER", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "RSCP_PASSWORD", matches = ".*\\S+.*")
Expand All @@ -33,4 +33,37 @@ class DefaultChargingServiceTest: IntegrationTestBase() {
val result = toTest.writeLimits(limits)
println(result)
}

// @Test
@EnabledIfEnvironmentVariable(named = "E3DC_HOST", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "E3DC_USER", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "RSCP_PASSWORD", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "E3DC_PORTAL_PASSWORD", matches = ".*\\S+.*")
fun `read manual charging state`() {
val toTest = DefaultChargingService(connectionPool)
val result = toTest.readManualChargeState()
println(result)
}

// @Test
@EnabledIfEnvironmentVariable(named = "E3DC_HOST", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "E3DC_USER", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "RSCP_PASSWORD", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "E3DC_PORTAL_PASSWORD", matches = ".*\\S+.*")
fun `start manual charging`() {
val toTest = DefaultChargingService(connectionPool)
val result = toTest.startManualCharge(0)
println(result)
}

// @Test
@EnabledIfEnvironmentVariable(named = "E3DC_HOST", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "E3DC_USER", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "RSCP_PASSWORD", matches = ".*\\S+.*")
@EnabledIfEnvironmentVariable(named = "E3DC_PORTAL_PASSWORD", matches = ".*\\S+.*")
fun `stop manual charging`() {
val toTest = DefaultChargingService(connectionPool)
val result = toTest.stopManualCharge()
println(result)
}
}
Loading

0 comments on commit f6d3fdd

Please sign in to comment.