PICS Conformance Cross-Reference¶
Source: PICS_span_20230711_SPANcomments20230803.xlsx (SPAN Modbus PICS, July 2023)
Last tested: 2026-03-13 (6-phase SunSpec protocol re-test)
This document cross-references the manufacturer's Protocol Implementation Conformance Statement (PICS) against actual hardware behavior verified through testing.
M702 — DER Capacity (Settings)¶
| Addr | Point | PICS Status | PICS R/W | Our Test Result | Match? |
|---|---|---|---|---|---|
| 227 | WMaxRtg | supported | R | 10000 ✅ | ✅ |
| 232 | VAMaxRtg | supported | R | 11600 ✅ | ✅ |
| 233 | VarMaxInjRtg | supported | R | 5880 ✅ | ✅ |
| 234 | VarMaxAbsRtg | supported | R | 5880 ✅ | ✅ |
| 235 | WChaRteMaxRtg | supported | R | 10000 ✅ | ✅ |
| 236 | WDisChaRteMaxRtg | supported | R | 10000 ✅ | ✅ |
| 248 | CtrlModes | supported | R | 14271 ✅ | ✅ |
| 251 | WMax | supported | RW | Write=5000, readback=0 | ⚠️ VIOLATION |
| 256 | VAMax | supported | RW | Not tested | — |
| 257 | VarMaxInj | unimplemented | RW | 0xFFFF | ✅ Expected |
| 258 | VarMaxAbs | unimplemented | RW | 0xFFFF | ✅ Expected |
| 259 | WChaRteMax | unimplemented | RW | 0xFFFF | ✅ Expected |
| 260 | WDisChaRteMax | unimplemented | RW | 0xFFFF | ✅ Expected |
| 261 | VAChaRteMax | unimplemented | RW | 0xFFFF | ✅ Expected |
| 262 | VADisChaRteMax | unimplemented | RW | 0xFFFF | ✅ Expected |
M702 Takeaway¶
All "unimplemented" registers match exactly (0xFFFF). WMax (251) is declared "supported RW" (0-10000) but FC06 write of 5000 returned success while readback remained 0 (silent discard — no Modbus exception).
M704 — DER AC Controls¶
| Addr | Point | PICS Status | PICS R/W | Our Test Result | Match? |
|---|---|---|---|---|---|
| 298 | PFWInjEna | supported | RW | Writable/sticky ✅ but PF setpoints 0xFFFF | ⚠️ Issue 6 |
| 310 | WMaxLimPctEna | supported | RW | ❌ Write accepted, readback=0 | ⚠️ VIOLATION |
| 311 | WMaxLimPct | supported | RW (0-100) | Readable (1000). Earlier write=500 readback=500 may be cache artifact (see note). Phase 3: readback=1000 after write=500 | ⚠️ Unreliable |
| 312 | WMaxLimPctRvrt | unimplemented | RW | 0xFFFF ✅ | ✅ Expected |
| 313 | WMaxLimPctEnaRvrt | unimplemented | RW | 0xFFFF ✅ | ✅ Expected |
| 318 | WSetEna | supported | RW | ✅ WORKS (Remote Control) | ✅ |
| 319 | WSetMod | supported | RW | ✅ Works | ✅ |
| 320 | WSet | supported | RW (0-10000) | ✅ Works (no clamping — see Alarms) | ✅ |
| 322 | WSetRvrt | supported | RW (0-10000) | Writable/sticky ✅ (cosmetic — Issue 4) | ⚠️ |
| 324 | WSetPct | supported | RW (0-100) | ✅ Works (no clamping — see Alarms) | ✅ |
| 325 | WSetPctRvrt | supported | RW (0-100) | Not tested | — |
| 326 | WSetEnaRvrt | supported | RW | Writable/sticky ✅ (cosmetic — Issue 4) | ⚠️ |
| 327 | WSetRvrtTms | supported | RW | Countdown works but reversion does NOT fire | 🔴 Issue 4 |
| 329 | WSetRvrtRem | supported | R | Countdown: 30→...→0 ✅ (cosmetic — Issue 4) | ⚠️ |
| 331 | VarSetEna | supported | RW | ❌ Write accepted, readback=0 | ⚠️ VIOLATION |
| 332 | VarSetMod | supported | RW | Readable (1), accepts write | ✅ |
| 333 | VarSetPri | supported | RW | Readable (2), accepts write | ✅ |
| 334 | VarSet | supported | RW (-5880 to 5880) | Readable (0), accepts write | ✅ |
| 336 | VarSetRvrt | unimplemented | RW | — | ✅ Expected |
| 340 | VarSetEnaRvrt | unimplemented | RW | — | ✅ Expected |
| 341 | VarSetRvrtTms | unimplemented | RW | — | ✅ Expected |
| 345 | WRmp | unimplemented | RW | Returns None | ✅ Expected |
M704 Takeaway¶
WSet active power control (318/319/320/324) works correctly and matches PICS. WSetRvrt (322), WSetEnaRvrt (326), WSetRvrtTms (327), and WSetRvrtRem (329) all operate as declared at the register level — countdown is active and reversion target is sticky. HOWEVER: reversion efficacy testing confirmed the countdown is cosmetic — WSetEna and WSetPct unchanged 186s after expiry. The dead-man switch does not physically revert power (SAFETY CRITICAL — Issue 4).
WMaxLimPctEna (310) and VarSetEna (331) silently discard all writes (0/160 tests). PFWInjEna (298) is writable but gates unimplemented setpoint registers — no physical PF effect (Issue 6).
M715 — DER Lifecycle¶
| Addr | Point | PICS Status | PICS R/W | Our Test Result | Match? |
|---|---|---|---|---|---|
| 1089 | LocRemCtl | supported | R | Read-only ✅ (always Local=1) | ✅ |
| 1090 | DERHb | supported | R | Always 0 | ✅ |
| 1092 | ControllerHb | supported | RW | ❌ Write accepted, readback=0 | ⚠️ VIOLATION |
| 1094 | AlarmReset | supported | RW | Write OK, auto-clears to 0, no latched alarms | ✅ |
| 1095 | OpCtl | supported | RW | Reads 0 (idle). ⚠️ Write not tested (safety) | — |
M715 Takeaway¶
ControllerHb declared "supported RW" but hardware silently discards writes. This is a PICS conformance violation. LocRemCtl correctly declared as Read-only.
Failed Tests Against PICS Claims¶
The following registers are declared "supported RW" in the PICS but fail to persist writes.
Error Pattern (identical across all 4 registers)¶
- FC06 (Write Single Register): Returns success — no Modbus exception
- FC16 (Write Multiple Registers): Returns success — no Modbus exception
- Readback: Shows original/default value — write silently discarded
- No Modbus-layer error (no 0x01 Illegal Function, 0x02 Illegal Address, etc.)
Robustness Testing (0/160 — all permutations failed)¶
| Variable | Values Tested |
|---|---|
| Function Code | FC06, FC16 |
| Settle Time | 0.5s, 1.0s, 2.0s, 5.0s |
| Remote Control | Without (WSetEna=0), With (WSetEna=1) |
| Enable Values | 1, 2 (plus 100 for ControllerHb) |
| WMax Values | 100, 1000, 5000, 10000 |
| SunSpec Sequencing | Isolated writes AND full 6-phase protocol |
| Connection | Fresh TCP connection per test (atomic) |
Test Infrastructure Details¶
| Parameter | Value Used | Notes |
|---|---|---|
| Base address | 0-based PDU | Addresses (e.g. 310, 331) sent directly in Modbus FC03/FC06 PDU. Confirmed correct: reads return expected values (WMaxLimPct=1000, VarSetMod=1, WSetEna toggles 0↔1) |
| Unit ID | 2 only | Did NOT test UID=1 or UID=126 (standard SunSpec UIDs) |
| Socket timeout | 30s | Per-operation |
| Inter-op delay | 0.2-0.3s | Between read/write operations within a test |
| Pre-settle delay | 0.2s | Between FC06/FC16 write and settle start |
| Post-settle readback | Immediate | Read immediately after settle period |
| Inter-test delay | 0.3s | Between TCP connections (breathing room) |
| Remote Control verification | Modbus readback only | WSetEna=1 confirmed via FC03 read of register 318. NOT verified via Cloud API or FEM |
| Remote Control activation | Library send_command() |
Uses FranklinWHController.send_command(BatteryCommand(500W)) which writes WSetEna=1 + WSetPct via pysunspec2 |
| Target | 192.168.0.110:502 | WiFi connection, single aGate X |
| Firmware | V10R01B04D00 | As reported by M1.Vr |
Variables NOT Tested¶
| Variable | Why Not | Risk |
|---|---|---|
| Unit ID 1 or 126 | Library auto-detects UID=2; not varied | LOW — reads work at UID=2. UID=126 is SunSpec discovery UID; may expose different register map or commissioning interface. Worth one quick scan session. |
| Cloud API Remote Control verification | No FEM running during test | MEDIUM — Remote Control may not have fully activated |
| Base address ±1 | Library SunSpec scan confirms addresses | LOW — reads return correct values |
| FC15 (Write Multiple Coils) | Registers are holding registers, not coils | NONE — not applicable |
| Multi-register FC16 writes | Only single-register FC16 tested | LOW — FC06 equivalent tested |
| Different firmware versions | Only V10R01B04D00 tested | N/A — only one device available |
Register-Level Results¶
| # | Register (Model) | PICS Claims | Tests Run | Passed | Error Pattern |
|---|---|---|---|---|---|
| 1 | M704.WMaxLimPctEna (310) | supported RW | 32 | 0 | Silent discard |
| 2 | M704.VarSetEna (331) | supported RW | 32 | 0 | Silent discard |
| 3 | M715.ControllerHb (1092) | supported RW | 53 | 0 | Silent discard |
| 4 | M702.WMax (251) | supported RW (0-10000) | 27 | 0 | Silent discard |
Additional Findings (Phase 3 — Full Sequencing)¶
| Register (Model) | PICS Status | Test | Result |
|---|---|---|---|
| M704.WMaxLimPct (311) | supported RW | Write 500, readback | 1000 (not stuck) |
| M704.WMaxLimPct_SF (350) | supported R | Read | -1 (valid scale factor — 0xFFFF as signed sunssf = -1, means ×10^-1) |
| M704.VarSetMod (332) | supported RW | Write 5, readback | 1 (not stuck) |
| M704.VarSet (334) | supported RW | Write 100, readback | 0 (not stuck) |
| M715.DERHb (1090) | supported R | Read | 0 (always zero — device never sends heartbeat) |
Note: WMaxLimPct, VarSetMod, and VarSet appeared to accept writes in earlier tests but here show readback to defaults. This suggests these registers also silently discard writes, or previous readbacks were from cache. Further investigation would confirm, but the enable registers remain the key blockers.
Root cause unknown. All are speculation without further evidence.
Features Correctly Declared as Unimplemented¶
M702: WChaRteMax (259), WDisChaRteMax (260), VAChaRteMax (261), VADisChaRteMax (262), VarMaxInj (257), VarMaxAbs (258), PFOvrExt (267), PFUndExt (268) — all return 0xFFFF. M704: WMaxLimPctRvrt (312), WMaxLimPctEnaRvrt (313), VarSetRvrt (336), WRmp (345), PFWInjEnaRvrt (299), PFWInjRvrtTms (300), PFWAbsEna (304), PFWAbsEnaRvrt (305) — all return 0xFFFF.
Features That Match PICS¶
M704: WSetEna (318), WSetMod (319), WSet (320), WSetPct (324), WSetRvrt (322), WSetEnaRvrt (326), WSetRvrtTms (327), WSetRvrtRem (329), PFWInjEna (298). M715: LocRemCtl (1089, R-only as declared).
WSetRvrt (322) verified: Write=2500, readback=[0, 2500] ✅ STICKY.
PFWInjEna (298) verified: Write toggle 0↔1 works ✅ STICKY. However, PF setpoint registers (PFOvrExt/PFUndExt) are 0xFFFF, and the entire PF reversion group (PFWInjEnaRvrt, PFWAbsEna, PFWAbsEnaRvrt) is 0xFFFF. No observable physical PF change when toggling. Enable register works but there is nothing to enable — NOT a viable reactive power path.
Facts Only Summary¶
-
PCS rate registers: PICS declares "unimplemented", hardware returns 0xFFFF. Case closed.
-
4 registers declared "supported RW" fail tests (0/160): WMaxLimPctEna, VarSetEna, ControllerHb, WMax. All exhibit identical behavior: FC06/FC16 success, readback unchanged. Exhaustively tested across settle times, values, sequencing, and Remote Control state.
-
WSet active power control works — WSetEna, WSetMod, WSet, WSetPct all functional. WSetRvrtTms countdown works but reversion does NOT fire at expiry (Issue 4, SAFETY CRITICAL).
4a. All reactive power and PF control paths exhausted — none viable: - Path 1 — VarSetEna (331): BLOCKED. Silently discards all writes. 0/160 tests. (Issue 1) - Path 2 — PFWInjEna (298): DEAD END. Enable writable but PF setpoints (267/268) unimplemented (0xFFFF). No physical PF effect. (Issue 6) - Path 3 — CtrlModes FIXED_VAR: Firmware claims available (bit 2=1) but no functional Modbus path exists. (Issue 3) - Conclusion: Reactive power and PF are NOT controllable via any Modbus register on this firmware.
- CtrlModes (M702.248) bitmask = 14271 = 0x37BF: Fact: Firmware declares FIXED_VAR (bit 2) as available, yet VarSetEna writes are silently discarded.
LocRemCtl Analysis¶
This section presents observed facts (labeled as FACT) and hypotheses (labeled as HYPOTHESIS). They are clearly distinguished.
FACT: LocRemCtl (M715.1089) always reads Local = 1 and is declared R (read-only) in the PICS. There is no documented path to transition to Remote = 0.
FACT: The WSet group (318-329) works despite LocRemCtl=Local. These registers are the core Remote Control power path.
FACT: All other "supported RW" control registers (WMaxLimPctEna, VarSetEna, ControllerHb, WMax) silently discard writes. These are non-Remote-Control features.
FACT: CtrlModes bitmask claims FIXED_VAR and FIXED_PF are available at the firmware level.
HYPOTHESIS A (Access Gate — unverified): LocRemCtl=Local may be the root cause. The WSet group may be a selective Remote Control carve-out that works despite Local mode, while other features require Remote authority.
HYPOTHESIS B (Firmware Stub — competing): The failing registers may be unimplemented stubs that ACK at the Modbus transport layer but are not wired to any control subsystem. This is architecturally distinct from a gate:
Hypothesis A: Write → [LocRemCtl=Local filter] → dropped
Hypothesis B: Write → Modbus transport ACK → /dev/null (not wired)
Control Path Classification¶
┌───────────────────────────────────────────────────┐
│ WORKING ✅ (Remote Control carve-out) │
│ │
│ M704.WSetEna (318) ─► Remote Control enable │
│ M704.WSet (320) ─► Power setpoint (W) │
│ M704.WSetPct (324) ─► Power setpoint (%) │
│ M704.WSetRvrt (322) ─► Reversion target ✅ │
│ M704.WSetRvrtTms (327)─► Dead-man timer ✅ │
│ M704.WSetRvrtRem (329)─► Countdown readback ✅ │
│ M704.WSetEnaRvrt (326)─► Reversion enable ✅ │
│ M704.PFWInjEna (298) ─► PF inject (writable) │
├───────────────────────────────────────────────────┤
│ BLOCKED ❌ (LocRemCtl=Local?) │
│ │
│ M704.VarSetEna (331) ─► Reactive power — DEAD │
│ M704.WMaxLimPctEna (310) ─► Curtailment % — DEAD │
│ M715.ControllerHb (1092) ─► Heartbeat — DEAD │
│ M702.WMax (251) ─► Max power — DEAD │
└───────────────────────────────────────────────────┘
Hardware Reversion Safety — Test Result¶
CRITICAL FINDING: The WSetRvrtTms countdown is cosmetic. It does NOT physically revert power or disable Remote Control when it reaches 0.
Reversion Efficacy Test (2026-03-13 22:59 AEDT)¶
Setup: Remote Control active (WSetEna=1, WSetPct=-500), WSetRvrt=0, WSetEnaRvrt=1, WSetRvrtTms=30. Extended observation: 3+ minutes post-expiry, NO forced cleanup until verdict.
T WSetEna WSetPct RvrtRem Notes
── ─────── ─────── ─────── ────────────────────────────
1s 1 -500 29 counting down
7s 1 -500 23 counting down
11s 1 -500 19 counting down
16s 1 -500 14 counting down
21s 1 -500 9 counting down
26s 1 -500 4 counting down
31s 1 -500 0 EXPIRED — still active ⚠️
39s 1 -500 0 9s post — still active ⚠️
46s 1 -500 0 16s post — still active ⚠️
68s 1 -500 0 38s post — still active ⚠️
98s 1 -500 0 68s post — still active ⚠️
136s 1 -500 0 106s post — still active ⚠️
176s 1 -500 0 146s post — still active ⚠️
216s 1 -500 0 186s post — still active ⚠️
Final state (186s post-expiry): WSetEna=1, WSetPct=-500, WSetRvrtRem=0.
Cleanup: Remote Control was force-released by test script calling reset_control_state(). Device did NOT auto-release.
Corroborating evidence (user-observed): - FranklinWH app: "VPP Mode" displayed throughout, "Charging 2.5 kW" (app label for Remote Control) - MQTT Explorer: "Runtime Mode: VPP mode" persisted (MQTT label for Remote Control) - Home Assistant: History shows Remote Control sustained, no mode transitions
Alarms during reversion test: - M701.Alrm (76) = 0 — no DER alarms - M714.PrtAlrms (1044) = 0 — no DC port alarms - Device raised no alerts or alarms when countdown expired without reversion
Facts: - Countdown mechanism works perfectly (30→23→...→4→0) - Device did NOT auto-release Remote Control for 186 seconds (3+ min) after countdown reached 0 - WSetEna=1 and WSetPct=-500 unchanged throughout entire observation window - NOT a latency/network issue — observation window is 186s, far exceeding any plausible delay - The dead-man switch countdown is cosmetic — it does NOT trigger power reversion
Implication: WSetRvrtTms cannot be used as a hardware crash-recovery mechanism. The software watchdog (controller.py timeout) remains the only safety mechanism for reverting power after loss of communication.
Alarm Observability (2026-03-13 23:30 AEDT)¶
Alarm Probe Test Results¶
All alarm registers read 0 across all probe conditions:
| Test | M701.Alrm | M714.PrtAlrms | DERMode | Notes |
|---|---|---|---|---|
| Baseline (Remote Control active) | [0,0] | [0,0] | [0,1] | — |
| WSet=15000 (150% WMaxRtg) | [0,0] | [0,0] | [0,1] | Accepted, no clamp |
| WSetPct=+1500 (+150%) | [0,0] | [0,0] | [0,1] | Accepted, readback=1500 |
| WSetPct=-1500 (-150%) | [0,0] | [0,0] | [0,1] | Accepted, readback=-1500 |
| AlarmReset (1094)=1 | [0,0] | [0,0] | [0,1] | Write OK, readback=0 (auto-clear) |
Additional observations:
- OpCtl (1095): reads 0 (idle)
- MnAlrmInfo (193): static placeholder string "Manufacturer custom error info" — not dynamic alarm data
- AlarmReset (1094): accepts write, auto-clears to 0, no latched alarms exposed
Input Validation Finding¶
⚠️ No input validation or clamping on WSet/WSetPct values.
The device accepted WSet=15000W (150% of WMaxRtg=10000W) and WSetPct=±1500 (±150%) without any Modbus exception, alarm, or clamping. Values read back exactly as written. Software must enforce range limits independently.
Alarm Architecture Assessment¶
PICS-violated writes (WMaxLimPctEna, VarSetEna, etc.)
│
▼
┌─────────────┐
│ Access gate │ ← LocRemCtl=Local filter (hypothesised)
└─────────────┘
│
│ Write silently dropped HERE
│ Never reaches alarm-generating subsystem
▼
┌─────────────────────────┐
│ Control execution layer │ ← alarms live here
└─────────────────────────┘
Assessment: Silent-discard architecture places the access gate upstream of alarm-generating subsystems. PICS-violated writes do not reach the control execution layer and therefore produce no alarms — by design or by omission.
M701.Alrm bits (GROUND_FAULT, DC_OVER_VOLT, AC_DISCONNECT, etc.) are hardware/grid event triggers — none are triggerable via Modbus write under normal operating conditions.
PICS Violation Report Template¶
PICS Violation Report — aGate X (AGT-R1V1-AU Hybrid)
Firmware: V10R01B04D00
Test Date: 2026-03-13
PICS Source: PICS_span_20230711_SPANcomments20230803.xlsx
Issue 1 — LocRemCtl permanently Local
M715.LocRemCtl (1089) returns Local=1 with no path to Remote.
CtrlModes (M702.248) = 14271 declares FIXED_VAR (bit 2) and
FIXED_PF (bit 3) as firmware-available, contradicting the
non-functional VarSetEna and strengthening the case that a
Modbus access gate exists (LocRemCtl or equivalent) that is
not documented in PICS.
This causes cascading silent-discard on:
- M704.VarSetEna (331) declared supported RW
- M704.WMaxLimPctEna (310) declared supported RW
- M715.ControllerHb (1092) declared supported RW
Testing: 0/160 across FC06/FC16, settle 0.5-5s, ±Remote Control, + sequenced.
REQUEST: Document the mechanism to transition to Remote mode,
OR update PICS to reflect actual constraints.
Filed: 2026-03-13 | Firmware: V10R01B04D00 | Status: OPEN
Issue 2 — WMax (251) write silently discarded
PICS declares supported RW (0-10000).
Tested: FC06/FC16, values 100/1000/5000/10000, settle 0.5-5s,
with and without Remote Control. ALL silently discarded.
Note: WSet (320) accepts values 0-10000W and works correctly.
If WMax is intended as a ceiling on WSet, the absence of WMax
write capability means the only power ceiling is WMaxRtg (227)
= 10000W (read-only hardware rating). No software curtailment
ceiling is achievable via PICS-declared registers.
REQUEST: Confirm if SPAN Modbus unlock is required.
Filed: 2026-03-13 | Firmware: V10R01B04D00 | Status: OPEN
Issue 3 — CtrlModes declares FIXED_VAR available but Modbus
path is non-functional
M702.CtrlModes (248) = 14271 (0x37BF)
Bit 2 (FIXED_VAR) = 1 — firmware declares reactive power available
Bit 3 (FIXED_PF) = 1 — firmware declares PF control available
Yet M704.VarSetEna (331) silently discards all writes (0/160 tests).
VarSetEna is the ONLY SunSpec Modbus path to exercise FIXED_VAR.
No alternative Modbus registers expose reactive power control.
REQUEST: Clarify whether CtrlModes bitmask represents hardware
capability or Modbus-controllable capability. If the former,
update PICS documentation to define this distinction explicitly.
Filed: 2026-03-13 | Firmware: V10R01B04D00 | Status: OPEN
Issue 4 — WSetRvrtTms countdown does not revert power (SAFETY CRITICAL)
Severity: CRITICAL — safety architecture, not just conformance.
PICS declares WSetRvrtTms (327) as supported RW.
Countdown mechanism works (30→23→...→0), but WSetEna and
WSetPct are unchanged after expiry. Observed for 186s (3+ min)
post-expiry with NO forced cleanup — device never auto-released.
Edge case tested: WSetRvrtTms=1 (1s timer) also produces no
reversion — eliminates zero-value special-meaning interpretation.
No alarms raised (M701.Alrm=0, M714.PrtAlrms=0).
Corroborated by user via FHP app ("VPP Mode" persisted) and MQTT.
SunSpec Model 704 dead-man reversion is the specification-
defined mechanism for protecting physical assets when Modbus
communication is lost. A cosmetic countdown with no physical
effect eliminates this protection entirely.
Consequence: With ControllerHb also non-functional (Issue 1),
there is NO hardware-enforced safety mechanism on this device.
Loss of the controlling software process (crash, OOM, host
power loss) will leave the device in its last commanded state
indefinitely with no self-recovery path.
The software watchdog (controller.py) is currently the ONLY
safety mechanism. This is a single point of failure that
cannot be mitigated via any PICS-declared Modbus register.
Additional finding: WSetEnaRvrt (326) readback=1 (sticky),
but the reversion it enables never fires. This register is
also effectively cosmetic in current firmware.
REQUEST: Confirm implementation status. If reversion is
deferred to a future firmware version, provide target
firmware version and timeline. This blocks production
deployment of any unattended Remote Control application.
SYSTEMIC SAFETY FAILURE NOTE:
ControllerHb (Issue 1) and WSetRvrtTms (Issue 4) are
designed as complementary safety mechanisms in SunSpec:
Controller → ControllerHb → DERHb echo → if stops →
WSetRvrtTms fires → power reverts
BOTH mechanisms are non-functional simultaneously on this
firmware. This is not two independent bugs — it is a
complete failure of the SunSpec M704+M715 safety architecture.
Filed: 2026-03-13 | Firmware: V10R01B04D00 | Status: OPEN
Issue 5 — No input validation on WSet/WSetPct (SAFETY HIGH)
Severity: HIGH — software must enforce all range limits.
WSet (320) accepted 15000W (150% of WMaxRtg=10000W).
WSetPct (324) accepted ±1500 (±150% of rated range).
Both values read back exactly as written.
No Modbus exception code returned.
No alarm raised (M701.Alrm=0, M714.PrtAlrms=0).
DERMode unchanged ([0,1]) — device treated over-limit
values as valid setpoints.
PICS declares WSet range as 0-10000W, WSetPct as 0-100.
Device does not enforce these bounds at the Modbus layer.
Consequence: A software bug, corrupt message, or integer
overflow that produces an out-of-range setpoint will be
silently accepted. No hardware protection exists.
REQUEST: Confirm whether firmware-level clamping is
applied downstream of Modbus acceptance (i.e., does
WSet=15000 actually command 15000W or is it clamped
internally to WMaxRtg=10000W before execution?).
If not clamped internally, this is an input validation
defect.
Filed: 2026-03-13 | Firmware: V10R01B04D00 | Status: OPEN
Issue 6 — PFWInjEna (298) enable gates unimplemented setpoints
PICS declares PFWInjEna (298), PFOvrExt (267), PFUndExt (268),
and PF reversion group as supported RW.
PFWInjEna is writable/sticky (toggle 0↔1 confirmed).
PFOvrExt (267), PFUndExt (268), and all PF reversion
registers return 0xFFFF (unimplemented).
No physical PF change observed across any toggle condition.
PF readings during test (near-zero real power — noise floor):
Idle: PF=-2, Var=-725
PFWInjEna=0: PF=11, Var=-732
PFWInjEna=1: PF=-13, Var=-737
Remote Control active: PF=965, Var=-623, W=3002
PF variation is load-dependent, not PFWInjEna-dependent.
±13 PF delta at near-zero watts is measurement noise.
Var delta across all conditions = 12 Var (noise floor).
Conclusion: PFWInjEna gates setpoint registers that do not
exist in this firmware. Not a viable reactive power path.
This is the final reactive power path exhausted — no
Modbus-accessible reactive power control exists on this
firmware version.
REQUEST: Confirm implementation status of PF setpoint
registers (267/268). Update PICS to reflect unimplemented
status or provide firmware version where these are available.
Filed: 2026-03-13 | Firmware: V10R01B04D00 | Status: OPEN
Document Status¶
CLOSED — all items resolved:
✅ PCS rate registers — unimplemented, 0xFFFF confirmed
✅ WSet active power control (318/319/320/324) — fully functional
✅ WSetRvrt (322) — writable/sticky (cosmetic — Issue 4)
✅ WSetEnaRvrt (326) — writable/sticky (cosmetic — Issue 4)
✅ WSetRvrtRem (329) — countdown works (cosmetic — Issue 4)
✅ M702 unimplemented registers — all match PICS
✅ M701/M714 alarms = 0 across all probe conditions
✅ AlarmReset (1094) — writable, auto-clears, no latched alarms
✅ MnAlrmInfo (193) — static placeholder, not dynamic
✅ PFWInjEna (298) — writable but non-functional, Issue 6 filed
✅ Reactive power — ALL paths exhausted, none viable (final)
PICS VIOLATIONS FILED (6 issues):
⚠️ Issue 1 — LocRemCtl gate (WMaxLimPctEna/VarSetEna/ControllerHb)
⚠️ Issue 2 — WMax (251) silently discarded
⚠️ Issue 3 — CtrlModes claims FIXED_VAR, no Modbus path exists
🔴 Issue 4 — WSetRvrtTms cosmetic, reversion never fires ← SAFETY
🔴 Issue 5 — No input validation on WSet/WSetPct ← SAFETY
⚠️ Issue 6 — PFWInjEna gates unimplemented setpoint registers
OPEN:
🟡 OpCtl (1095) write behaviour — low priority, requires
physical site access. Non-blocking for Remote Control deployment.
PRODUCTION GATE:
🔴 Issue 4 — reversion non-functional (SAFETY CRITICAL)
🔴 Issue 5 — no input validation (SAFETY HIGH)
Software must clamp WSet to [0, WMaxRtg] and
WSetPct to [-1000, 1000] before writing.
No hardware protection exists.
PRODUCTION GATE — Software Mitigation Clearance Conditions:
Issue 4 mitigation:
□ Software watchdog covers all 4 scenarios in table above
□ Watchdog timeout ≤ 60s (configurable)
□ reset_control_state() explicitly writes WSetEna=0
□ Startup checks WSetEna (318), releases if =1 before dispatch
□ Documented as sole safety mechanism (no hardware backup)
Issue 5 mitigation:
□ WSet clamped to [0, WMaxRtg] before write
□ WSetPct clamped to [-1000, 1000] before write
(scaled: SF=-1, so 1000 = 100.0% — see WMaxLimPct_SF addr 350)
□ Unit test coverage for clamp boundary conditions
□ Vendor confirms downstream clamping status (informational)
Software Watchdog Requirements¶
Given WSetRvrtTms non-reversion (Issue 4) and ControllerHb non-functional (Issue 1), the software watchdog is the sole safety mechanism. It must cover:
| Scenario | Required Response |
|---|---|
| Modbus TCP connection lost | Detect within N seconds, call reset_control_state() |
| Controller process crash | OS-level supervisor (systemd, Docker restart) must restart AND release on startup if Remote Control was active |
| Host power loss | On restore: read WSetEna (318), if =1 call reset_control_state() before accepting new dispatch |
| Dispatch timeout | Detect stale dispatch (no new setpoint received), call reset_control_state() |
NOTE:
reset_control_state()must write WSetEna=0 explicitly. There is no hardware path that will do this automatically.
Capability Summary (Handoff)¶
WHAT WORKS: Active power dispatch via WSet/WSetPct.
Accepts any value — software MUST clamp to valid range.
WHAT DOESN'T: Reactive power (all paths exhausted, final).
PF control. Curtailment ceiling. Hardware reversion.
Heartbeat. Input validation.
SAFETY (x2): 1. Hardware dead-man is cosmetic — no self-recovery.
2. No input validation — software is range enforcement.
Both must be addressed before unattended deployment.
Source file: ~/Downloads/PICS_span_20230711_SPANcomments20230803.xlsx
Last updated: 2026-03-22 (terminology: VPP Mode → Remote Control)