= SOH Cycle Controller

This controller runs a defined State-of-Health (SOH) test cycle on an
ESS (battery storage system). It executes an optional *reference cycle*
and a *measurement cycle*, then evaluates the result to determine the
SOH of the ESS.

The purpose of this README is to store useful technical information
about the controller logic and states. This information is primarily
needed for developers (e.g., to answer questions from product
management).

'''''

== Configuration

The following configuration parameters are available:

* *`id`* +
Component-ID – Unique ID of this component (default: `ctrlEssSohCycle0`)
* *`ess++_++id`* +
ID of the ESS device to monitor (e.g., `ess0`)
* *`alias`* +
Human-readable name of this component; defaults to Component-ID
* *`enabled`* +
Whether this component is enabled (default: `true`)
* *`mode`* +
Operating mode – Select `MANUAL++_++ON` to activate the cycle logic;
`MANUAL++_++OFF` disables the cycle (default: `MANUAL++_++OFF`)
* *`logVerbosity`* +
Sets the logging verbosity level (default: `NONE`)
** `NONE`: No logs
** `DEBUG++_++LOG`: Detailed logging to Controller.Debug.Log
* *`referenceCycleEnabled`* +
Enables the optional reference cycle measurement (default: `false`)
** If `false`: cycle skips directly to the measurement cycle
** If `true`: full cycle with reference measurements included

'''''

== Monitoring

Typical channels can be followed from Grafana or online monitoring
(names may differ depending on configuration).

=== Core state and results

* *`ctrlEssSohCycle0/StateMachine`* +
Current state of the SOH state machine: +
`IDLE`, `PREPARE`, `REFERENCE++_++CYCLE++_++CHARGING`,
`REFERENCE++_++CYCLE++_++CHARGING++_++WAIT`, +
`REFERENCE++_++CYCLE++_++DISCHARGING`,
`REFERENCE++_++CYCLE++_++DISCHARGING++_++WAIT`, +
`MEASUREMENT++_++CYCLE++_++CHARGING`,
`MEASUREMENT++_++CYCLE++_++CHARGING++_++WAIT`, `CHECK++_++BALANCING`, +
`MEASUREMENT++_++CYCLE++_++DISCHARGING`,
`MEASUREMENT++_++CYCLE++_++DISCHARGING++_++WAIT`, +
`EVALUATE++_++RESULT`, `DONE`, `ERROR++_++ABORT`.
* *`ctrlEssSohCycle0/SohPercent`* +
Calculated SOH result of the last completed cycle in percent (0–100).
* *`ctrlEssSohCycle0/SohRawDebug`* +
Raw calculated SOH value without clamping to 100. May exceed 100 and is
intended for debugging.
* *`ctrlEssSohCycle0/MeasuredCapacity`* +
Last measured capacity of the ESS in Watt-hours (Wh).

=== Battery balancing status and diagnostics

* *`ctrlEssSohCycle0/IsBatteryBalanced`* +
Indicates whether the battery cells are sufficiently balanced. +
Values: `NOT++_++MEASURED`, `BALANCED`, `NOT++_++BALANCED`, `ERROR`.
* *`ctrlEssSohCycle0/BalancingDeltaMvDebug`* +
Cell voltage delta (max − min) in millivolts. Persisted for debugging
long-running cycles. +
`null` if delta cannot be calculated.
* *`ctrlEssSohCycle0/BalancingReason`* +
Explains why the balance check results in `NOT++_++MEASURED` or
`NOT++_++BALANCED`. +
Typical values: +
`NONE`, `BASELINE++_++MIN++_++MISSING`, `MAX++_++VOLTAGE++_++UNDEFINED`,
`MIN++_++VOLTAGE++_++UNDEFINED`, +
`DELTA++_++NEGATIVE`, `DELTA++_++ABOVE++_++THRESHOLD`, `TIMEOUT`,
`INTERNAL++_++ERROR`.

____
Note: exact enum values depend on the controller version.
____

=== Voltage monitoring

* *`ctrlEssSohCycle0/VoltageDelta`* +
Cell voltage delta (in mV) used for balancing evaluation and
diagnostics.

'''''

== Algorithm logic

The SOH Cycle Controller is implemented as a state machine with the
following phases:

=== 1) Idle & preparation

* *`IDLE`* – Waits for activation via `MANUAL++_++ON` mode.
* *`PREPARE`* – Prepares the ESS by discharging to minimum SOC (0%).

=== 2) Reference cycle (optional)

* *`REFERENCE++_++CYCLE++_++CHARGING`* – Charges the battery to maximum
SOC (100%) at 0.2C rate.
* *`REFERENCE++_++CYCLE++_++CHARGING++_++WAIT`* – Waits for voltage
stabilization.
* *`REFERENCE++_++CYCLE++_++DISCHARGING`* – Discharges the battery to
minimum SOC (0%) at 0.2C rate.
* *`REFERENCE++_++CYCLE++_++DISCHARGING++_++WAIT`* – Waits for voltage
stabilization.

=== 3) Measurement cycle (always executed)

* *`MEASUREMENT++_++CYCLE++_++CHARGING`* – Charges the battery to
maximum SOC (100%) at 0.2C rate. +
During this phase the controller continuously collects cell voltage
range data for balancing diagnostics.
* *`MEASUREMENT++_++CYCLE++_++CHARGING++_++WAIT`* – Waits for voltage
stabilization.
* *`CHECK++_++BALANCING`* – Evaluates battery cell balancing status
using the measured cell voltage delta. +
Sets:
** `IsBatteryBalanced`
** `BalancingDeltaMvDebug`
** `BalancingReason`
+
Threshold: *100 mV*.
* *`MEASUREMENT++_++CYCLE++_++DISCHARGING`* – Discharges the battery to
minimum SOC (0%) at 0.2C rate while measuring energy for capacity
calculation.
* *`MEASUREMENT++_++CYCLE++_++DISCHARGING++_++WAIT`* – Waits for voltage
stabilization.

=== 4) Result evaluation

* *`EVALUATE++_++RESULT`* – Calculates SOH from measured capacity; sets:
** `SohPercent`
** `SohRawDebug`
** `MeasuredCapacity`
* *`DONE`* – Cycle finished successfully.

=== 5) Error handling

* *`ERROR++_++ABORT`* – Fatal error occurred; manual reset required via
mode change to `MANUAL++_++OFF`.

'''''

== Battery balancing check: how voltage delta is measured

=== What is measured?

The balancing check uses the cell voltage delta:

....
delta_mV = maxCellVoltage_mV - minCellVoltage_mV
....

The value is written to `BalancingDeltaMvDebug` and used to determine
`IsBatteryBalanced`.

=== How are min and max voltages captured?

During the measurement charging phase and again at
`CHECK++_++BALANCING`, the controller calls:

[source,java]
----
refreshMeasurementChargingVoltageRange()
----

This method reads:

* `ess.getMinCellVoltage()`
* `ess.getMaxCellVoltage()`

and stores them internally.

*Important: min and max may be captured at different times*

Min and max voltages are updated independently:

The stored minimum voltage is actually the highest observed minimum
during charging:

[source,java]
----
if (stored == null || current > stored) {
    stored = current;
}
----

The stored maximum voltage is the highest observed maximum:

[source,java]
----
if (stored == null || current > stored) {
    stored = current;
}
----

As a result, the voltage delta may combine values measured at different
points in time. It represents a range envelope observed during charging,
not necessarily an instantaneous snapshot.

==== Implications

* The delta is a diagnostic indicator of imbalance.
* It may be slightly conservative, but helps answer:
** “Did a large spread occur during charging?”
** “Is the spread still significant near the end of charging?”

If an instantaneous delta at a single timestamp is required, min and max
would need to be sampled together and evaluated immediately.

'''''

=== CHECK++_++BALANCING behavior and diagnostics

In `CHECK++_++BALANCING`, the controller: 1. Refreshes voltage range
data 3. Calculates voltage delta and diagnostic reason 5. Sets: ++*++
BalancingDeltaMvDebug ++*++ BalancingReason ++*++ IsBatteryBalanced 13.
Continues the cycle even if the battery is not balanced

Diagnostics are persisted as channels, enabling root-cause analysis
without relying on logs.
