# SRNE ASP48120SH3 Modbus Register Map (validated)

Validated Modbus register map for the SRNE ASP48120SH3 three-phase hybrid inverter, verified register-by-register against the live Nansana unit, including a **grid-off / grid-on transition test on 2026-07-27** that resolved the previously-unknown registers and corrected the battery-current sign.

**Bus**: RS485, 9600 8N1, function code 03 (read holding registers), slave address 1.
**Source of truth (code)**: `src/inverter/registers.py` in nfe-modbus-energy-logger.

## Valid register ranges (probed live)

* P00 product info: 0x0000 to 0x0049
* P01 battery / DC: 0x0100 to 0x0111
* P02 inverter telemetry: 0x0200 to **0x023F** (0x0240 and beyond return "Illegal data address")
* P03 control commands: 0xDF00 to 0xDF09 (write-only, read back 0)
* Settings E (writable params): 0xE001 to 0xE02D ; Settings F: 0xF000 to 0xF01F

## Live telemetry, full per-phase - VERIFIED

| quantity | phase A | phase B | phase C | notes |
| --- | --- | --- | --- | --- |
| grid voltage | 0x0213 | 0x022A | 0x022B | x0.1 V (all go to 0 when grid is off) |
| grid current | 0x0214 | **0x0238** | **0x0239** | x0.1 A. B/C live in the extended block, CONFIRMED grid-side by the transition test |
| grid frequency | 0x0215 | - | - | x0.01 Hz |
| grid active power | 0x023A | 0x023B | 0x023C | W (extended block, grid-side) |
| inverter voltage | 0x0216 | 0x022C | 0x022D | x0.1 V |
| inverter current | 0x0217 | 0x022E | 0x022F | x0.1 A (rises when off-grid, inverter sources the load) |
| inverter frequency | 0x0218 | - | - | x0.01 Hz |
| load current | 0x0219 | 0x0230 | 0x0231 | x0.1 A (load-side, holds through the transition) |
| load active power | 0x021B | 0x0232 | 0x0233 | W. No total register: sum the three phases |
| load reactive power | (derive) | 0x0234 | 0x0235 | var. Phase A: S=sqrt(P^2+Q^2) not given, derive |
| load apparent power | 0x021C | (derive) | (derive) | VA. B/C: derive S = sqrt(P^2 + Q^2) |
| load ratio | 0x021F | 0x0236 | 0x0237 | % |

Other P02: state 0x0210, password status 0x0211, bus V 0x0212 (x0.1) = PBus 0x0228 + NBus 0x0229, inverter DC component 0x021D (mV), mains charge current 0x021E (x0.1 A), heat-sink temps 0x0220/0x0221/0x0222 (x0.1 C, 0x0223 bogus), PV charge current 0x0224 (0, no PV), inverter fault 0x0226, charge status 0x0227. Extended grid block tail 0x023D/0x023E/0x023F (0/221/245/309 on grid) is grid-side reactive/apparent, exact split still to confirm.

## Battery / DC (P01) - VERIFIED

SOC 0x0100 (%), battery V 0x0101 (x0.1 V), battery current 0x0102 (x0.1 A, signed), device temp 0x0103 (reads 0, dead), DC load 0x0104-0x0106 (0), PV1 0x0107-0x0109 (0, no PV), charge state 0x010B, charge power 0x010E, PV2 0x010F-0x0111 (0).

**IMPORTANT - battery current sign is POSITIVE = DISCHARGE on this unit** (the OPPOSITE of the generic SRNE V1.7 doc). Proven by the 2026-07-27 off-grid test: with grid and PV both at 0, the battery is the only source, and while it was unambiguously discharging ~750 W to the loads it read **+14.2 A**; when the grid returned, discharge collapsed to +0.4 A (idle). So positive current = discharge, negative = charge.

## Grid-off / grid-on transition test (2026-07-27) - what died vs held

* GRID-side (went to 0 when utility cut, back live when restored): grid V A/B/C (0x0213/0x022A/0x022B), grid I A (0x0214), grid freq (0x0215), and the whole extended block 0x0238 to 0x023F (grid I B/C + grid power). This is how the extended block was identified.
* LOAD-side (held through the transition, source-independent): load I/P/Q per phase (0x0219, 0x021B, 0x0230-0x0237).
* INVERTER-side (rose off-grid as it took the load): inverter I A/B/C (0x0217/0x022E/0x022F).
* STATE 0x0210: **2 = on-grid / mains-bypass, 3 = off-grid / inverter-powered** (both codes validated by the transition). Charge state 0x010B: 0 off-grid, 1 on-grid.

## Corrections vs the generic SRNE V1.7 doc (do NOT trust the generic enums/signs)

* Battery current sign: **positive = discharge** (doc says positive = charge - WRONG here).
* Machine state 0x0210: 2 = running/mains-bypass, 3 = off-grid. NOT the generic "initialization/soft-start".
* Power factor 0x021A reads 0. Derive PF = active (0x021B) / apparent (0x021C).
* Device temperature 0x0103 reads 0. Use heat-sink temps 0x0220 to 0x0222.

## For the OpenEMS driver (important)

* There is NO inverter-output-power and NO ESS/battery AC-power register. The ESS SymmetricEss.ACTIVE_POWER must be DERIVED from battery DC power (0x0101 x 0x0102). Do NOT map load power 0x021B (that is the loads, phase A only).
* **Dc/ESS power sign**: since positive battery current = discharge, DcDischargePower = +V x I (do NOT negate). A negated formula reports a real discharge as a charge.
* Grid meter can now have per-phase current and power: grid I A/B/C (0x0214/0x0238/0x0239) and grid active power A/B/C (0x023A/0x023B/0x023C).
* Consumption/backup meter power from the load registers (0x021B/0x0232/0x0233 active, 0x0234/0x0235 reactive).

## Writes

Both FC06 and FC16 supported. Read-only registers reject with exception code 7. Settings 0xE0xx writable. Control 0xDF00 area (power/reset/restore) is dangerous on the live unit: documented, not fired.