SRNE: guarded output-priority (E204) + BMS-enable (E215) writes (#71) - #25
Conversation
…rite allow-list (OpenEMS#71) Adds the two remaining registers needed to do "switch to battery at a SoC" fully from OpenEMS: output source priority (E204, e.g. SBU) and BMS-comms enable (E215). Same guarded scalar path as the OpenEMS#53 settings: controlEnabled default off, verified-machine-state gate, target default -1, one-shot, read-back verified. Output priority = SBU transfers the load onto the battery, so it is treated as an "arm" and gated (reconcileOutputPriority): it is written only once its safety prerequisites - the switchToLineSoc/switchToBatterySoc reserve band and bmsCommunication, where configured - have read-back verified OR already read the configured value on the device. A failed or pending reserve-floor write can never arm battery discharge against a wrong reserve. The exact value->mode enum is NOT verified in the SRNE manuals (generic Modbus manual says E204 is 0..2 but the unit reads 3 and the ASP manual lists four modes by name; E215 similarly). Ranges are bounded only (E204 0..3, E215 0..2); read-back verify guards a mis-accepted value; a wrong-but-in-range value is prevented only by keeping targets -1 until the enum is captured on the unit (OpenEMS#71). Tests: output-priority + BMS queue/reject at exact boundaries; prerequisite-gate unit test; composition test proving E204 arms when a prerequisite already matches the device (guards against a gate deadlock). Build green on JDK 21.
tushabe
left a comment
There was a problem hiding this comment.
I do not recommend merging the arm path as written yet. The module check passes locally and the default -1 targets are safe, but the prerequisite gate has two production-safety gaps.
-
Pending or failed prerequisites can pass the gate.
isPrerequisiteSettled()returns true wheneveractual == configuredTarget, regardless of handler state. That meansQUEUED,AWAITING_READBACK, or evenFAILEDcan armE204if the channel happens to show the target. Conversely,VERIFIEDreturns true even when the current channel is null or has drifted away. This does not enforce the stated “verified or already correct” contract. Please make the state distinction explicit:- unmanaged: settled only if intentionally allowed by the policy below;
IDLE: settled only when the current read equals the target;VERIFIED: settled only while the current read still equals the target;QUEUED,AWAITING_READBACK,FAILED,UNDEFINED: never settled.
Add exact tests for
FAILED + matching actual,AWAITING_READBACK + matching actual, andVERIFIED + drift/null. -
outputPrioritycan arm with no safety prerequisites configured. The current queue test sets onlyoutputPriority; all SoC-band and BMS targets are-1, so the gate treats all three as settled and transfers the load using unknown existing reserve/BMS settings. For an operation described as an arm, require an explicit complete prerequisite set—at minimumswitchToLineSoc,switchToBatterySoc, and the verified BMS mode—before a changedE204can queue. Also validate the reserve pair as a coherent band (line threshold below battery-return threshold) before arming. If the intended policy is to accept pre-existing device settings instead, those settings need explicit validated values and live reads, not unconditional-1 => true.
The enum mapping still must be captured onsite before any write, as OpenEMS#71 says. A read-only capture is safe; keep E204 and E215 targets at -1 until that evidence is recorded.
Address two production-safety gaps in the prerequisite gate: - State bypass: isPrerequisiteSettled treated a prerequisite as settled whenever the device read matched the target, regardless of handler state, so a QUEUED / AWAITING_READBACK / FAILED write (or a drifted VERIFIED) could arm E204. It now requires the handler at rest (IDLE or VERIFIED) AND the device currently reading the configured value; an in-flight, failed or drifted prerequisite never settles. - Arm without prerequisites: E204 could arm with the SoC band and BMS all left at -1, transferring the load onto unknown existing device settings. Arming a changed output priority is now rejected unless switchToLineSoc, switchToBatterySoc and bmsCommunication are all configured and the reserve band is coherent (switchToLineSoc strictly below switchToBatterySoc), then each must be settled. Tests: gate matrix now covers the matching-actual states (QUEUED/AWAITING/FAILED/ UNDEFINED + matching read, VERIFIED + drift/null); arm-without-prerequisites and incoherent-band (including the line==battery boundary) reject to FAILED; the already-correct arm path configures the full coherent band. Build green, JDK 21.
|
Both gaps fixed at 1. State bypass. 2. Arming with no prerequisites. A changed Distinction preserved: a misconfiguration (missing prereq / incoherent band) rejects to terminal One thing for your awareness (out of scope here, pre-dates this PR): the coherence check gates the arm, but |
tushabe
left a comment
There was a problem hiding this comment.
Re-review at 1925da4fc475f8de70a66773b07066bec5cfd9f3: the two requested gate fixes are correct, the new state matrix is appropriate, and CI is green.
One remaining production-safety blocker follows from the interaction noted in your response:
Reject an incoherent reserve band before either persistent threshold is queued. reconcileSafeSettings() still calls the scalar reconciles for E01F and E020 before reconcileOutputPriority() checks line < battery. With an invalid pair, both individually valid values can therefore be written to the inverter even though the E204 arm subsequently enters FAILED. If the unit is already in SBU/battery-priority mode from its panel configuration, that is not harmless; the bad reserve band becomes active immediately. It also leaves persistent drift after a rejected operation.
When outputPriority is requested, validate that the complete reserve pair is present, individually bounded, and coherent before reconciling either E01F or E020. On failure, queue no writes for the pair, BMS mode, or E204 and expose terminal FAILED. Add a component test that supplies an incoherent but individually in-range pair and asserts the write elements/handlers for E01F and E020 never queue, rather than only asserting aggregate FAILED.
The enum mapping remains an onsite read-only prerequisite, and all targets should remain -1 until it is recorded.
…#25 review) Aaron: reserve-band writes preceded the arm coherence check, so a rejected arm could still push a bad band onto the inverter - dangerous if the unit is already in SBU from the panel. - reconcileSourceTransfer now owns the reserve band (E01F/E020), BMS (E215) and output priority (E204) as one operation. When an arm is requested the whole set is validated up front (all present, individually bounded, band coherent) and on any failure NOTHING is written; only then is the band/BMS reconciled and E204 armed once each prerequisite is settled. - The incoherent band is also refused on the no-arm path when both thresholds are configured (rejectBand), since a bad band is dangerous whenever the unit is in SBU regardless of whether OpenEMS armed it. Tests: an incoherent-band arm writes nothing to the reserve band/BMS (handlers stay IDLE, only E204 FAILED); an incoherent band with no arm rejects the band (handlers FAILED); band == boundary. Build green, JDK 21.
|
Fixed at Your finding — a rejected arm no longer touches the reserve band. I also closed the related door you'd expect next. A bad band is dangerous whenever the unit is in SBU (its normal backup posture, settable from the panel), so an incoherent band is now rejected on the no-arm path too when both thresholds are configured ( One deliberate boundary, for your call. Coherence is enforced when both thresholds are OpenEMS-managed. If an operator sets only one threshold (leaving the other unmanaged) such that it inverts against the device's current value, that single write isn't blocked — the principle is "OpenEMS validates what OpenEMS manages," and validating a managed write against transient unmanaged device state is a more fragile design (it can reject a legitimate mid-configuration). The device value is readable, so I can close this too if you'd prefer — but I'd rather not couple a write to unmanaged device state without your nod. Flagging it as a conscious decision rather than leaving it silent. Enum still an onsite read-only prerequisite (OpenEMS#71); targets remain |
tushabe
left a comment
There was a problem hiding this comment.
Re-reviewed through 21c5baa69e897e8f95de9058c7568bd8acc520ef.
The remaining blocker is resolved:
- source transfer validates the complete reserve band, BMS target, and E204 target before any related write queues;
- an invalid arm leaves E01F/E020/E215 handlers IDLE and E204 terminal FAILED;
- incoherent complete reserve bands are also rejected when no arm is requested;
- the state-aware prerequisite checks from the prior revision remain intact.
I ran ./gradlew :io.openems.edge.ess.srne:check locally: BUILD SUCCESSFUL.
Approved. Merge after the latest build-java check passes. The consciously retained single-threshold behavior should be documented as a follow-up hardening item: if only E01F or E020 is managed, coherence against the unmanaged live counterpart is not currently enforced.
For onsite work, E204/E215 remain read-only discovery until OpenEMS#71 records the verified enum mapping; keep both targets at -1 until then.
Adds output source priority (
E204, e.g. SBU) and BMS-comms enable (E215) to the guarded write allow-list, so "switch to battery at a SoC" (SBU + BMS-SoC) can be set fully from OpenEMS instead of the panel.Tracked by nfe-modbus-energy-logger OpenEMS#71 (sub-issue of OpenEMS#56, immediate source transfer).
What
E204/E215as guarded scalar writes:controlEnableddefault off, verified-machine-state gate, target default-1, one-shot, read-back verified, bounded-range validated. Same envelope as Track changes to config OpenEMS/openems#53.Safety: output priority is an "arm", so it is gated
E204= SBU transfers the load onto the battery — effectively the enable for battery discharge. Mirroring the schedule feature's verified-then-enable,reconcileOutputPriority()writesE204only after its prerequisites (theswitchToLineSoc/switchToBatterySocreserve band andbmsCommunication, where configured) have read-back verified OR already read the configured value on the device. A failed or pending reserve-floor write can never arm battery discharge against a wrong reserve.The value→mode mapping is not verified in the SRNE manuals: the generic Modbus manual says
E204is0..2, but the live unit reads 3 and the ASP operation manual lists four modes (UTI/SUB/SOL/SBU) by name only;E215similarly (generic0/1vs operation-manual DIS/485/CAN). So the ranges here are bounded only (E2040..3,E2150..2) and read-back verify only confirms the register accepted the number, not that it is the intended mode. A wrong-but-in-range value (an output mode that drops the load, or disabling BMS) would be accepted. This is safe only while targets stay-1. The enum MUST be captured on the unit (set each mode via LCD, readE204; confirm the BMS register) and recorded before any live write — see OpenEMS#71.Tests (
:checkgreen, JDK 21)E204/E215queue-on-change and reject at the exact boundary (E2043 ok / 4 rejected;E2152 ok / 3 rejected).testOutputPriorityPrerequisiteGate— the gate helper across unmanaged / verified / already-correct / pending / failed.testOutputPriorityArmsWhenPrerequisiteAlreadyCorrect— provesE204still arms when a managed prerequisite already matches the device (guards against a gate deadlock in the disciplined "set band first, add priority later" workflow).Ran an independent adversarial reviewer over this before opening (it caught the missing gate, the missing E215 tests, and a gate deadlock — all fixed). Targets remain
-1; not for commissioning until OpenEMS#71 confirms the enum.