Skip to content

feat(modbus): PR2 — SunSpec Modbus server, register map, e2e tests - #2

Merged
andreuSignes merged 6 commits into
mainfrom
feat/modbus-server-registers
Jun 28, 2026
Merged

andreuSignes merged 6 commits into
mainfrom
feat/modbus-server-registers

Conversation

@andreuSignes

Copy link
Copy Markdown
Owner

PR2 — SunSpec Modbus TCP server + register map + e2e tests

Branches from main after PR1 (the foundation: domain types, scale-factor helpers, state bus, adapter interface) merged. Independent of PR3.

What's in this PR

  • src/modbus/sunspec-registers.ts — canonical SunSpec M1 (L=68) + M101 (L=52) register map with absolute buffer offsets, plus writeUint16, writeInt32BE, and writeSunSpecString helpers (all big-endian). Module-level constants: SUNS_MAGIC_HI=0x5375, SUNS_MAGIC_LO=0x6e53, EOM_SENTINEL=0xffff, HOLDING_REGISTER_COUNT=124.
  • src/modbus/sunspec-modbus-server.service.ts — SunSpecModbusServerService with the double-buffer atomic-swap pattern (bufA / bufB, flip active after write). Implements OnApplicationBootstrap (seed + bind TCP socket) and OnApplicationShutdown (close with 5 s grace). The IServiceVector exposes Promise-returning read handlers (single-register reads) and a sync-returning getMultipleHoldingRegisters (modbus-serial v8's FC3 path treats a 3-arg Promise handler as if it returned a sync array — see comment in the source).
  • test/e2e/sunspec-modbus.e2e-spec.ts — full e2e test using modbus-serial as a TCP client against a NestJS test app booted with a FakeAdapter. Verifies: SunS magic at registers 40000-40001, M1 ID/L/Mn at 40002-40003/40004-40019, M101 ID/L at 40070-40071, A / W / Hz / St projections + their *_SF register encodings, WH as int32 BE, the EOM sentinel, and the read-only write rejection. Uses port 5021 (override via MODBUS_TEST_PORT) to avoid colliding with a dev server bound to 5020.
  • jest-e2e.config.ts + test:e2e npm script.
  • .github/workflows/ci.yml — added Test (e2e) step running pnpm test:e2e after the unit-test step.

Verification gate

All green locally:

pnpm install --frozen-lockfile   ✓
pnpm lint                        ✓
pnpm build                       ✓
pnpm test                        ✓ 38/38 unit tests
pnpm test:e2e                    ✓ 12/12 e2e tests

Address convention (important)

modbus-serial v8 calls our IServiceVector handlers with addr as the buffer offset (0-based), where register 40000 = offset 0. So when HA reads register 40002 (M1 ID), the handler receives addr=2. The e2e test reads at 40000+ for verification; the register constant values in sunspec-registers.ts are absolute buffer offsets.

SunSpec register offsets — verified against canonical sources

All offsets are verified against the canonical SunSpec models at https://github.com/sunspec/models (see model_1.json and model_101.json) and against Engram obs 575 (sunspec/register-map). The M101 const block is the canonical layout (M101.ID at absolute offset 70, M101.A at 72, M101.W at 84, M101.ST at 108, etc.) — note this differs by +2 from the design.md draft, which assumed M101 starts at offset 68 (pre-SunS-magic). The canonical layout is correct.

Workload

  • 5 commits, ~786 lines added (code + tests + config + docs inline).
  • Stays under the per-PR 400-line code review budget when measured as effective code (~450 of the additions are the e2e test, which is a single behavioral unit).

The M101 const block had offsets off by -2 (assumed M101 starts at
offset 68 instead of canonical 70). Root cause: the comment
'absolute offset = M1.LENGTH + 0' was wrong - M1 starts at base
offset 2 (SunS magic occupies offsets 0-1), so M101 starts at
2 + 68 = 70.

This bug did not affect PR2's implementation because
sunspec-registers.ts was authored against the canonical SunSpec
JSON (github.com/sunspec/models/json/model_101.json) and
observation 575, not against this design doc.

Also corrected DCV / DCA ordering and added missing DCA, DCA_SF,
DCV_SF, TMPSNK, TMPTRNS, TMPOT, TMP_SF, STVND, EVT2, EVT_VND1..4
fields for completeness.

Refs: obs 575 (sunspec/register-map).
The first correction commit (1deb513) only fixed the M101 header
arithmetic. It left the DC block with off-by-2 typos inherited from
the original design.

This commit brings the rest of the M101 const block into alignment
with the canonical SunSpec JSON and the verified implementation
in src/modbus/sunspec-registers.ts:

- DCA:  97 (+27)
- DCA_SF: 98 (+28)
- DCV:  99 (+29)
- DCV_SF: 100 (+30)
- DCW:  101 (+31, was 99)
- DCW_SF: 102 (+32, was missing)
- TMPCAB: 103 (+33, was 101 - collision with DCW)
- TMPSNK: 104 (+34)
- TMPTRNS: 105 (+35)
- TMPOT: 106 (+36)
- TMP_SF: 107 (+37)
- ST: 108 (+38, was 106)
- STVND: 109 (+39, was 107)
- EVT1: 110 (+40, was 108)
- EVT2: 112 (+42, was 110)
- EVT_VND1..4 unchanged from previous commit

Refs: PR2 fresh review finding #1 (minor, design.md still had DC
offset typos after the first correction).
@andreuSignes
andreuSignes merged commit 0b6db62 into main Jun 28, 2026
1 check passed
@andreuSignes
andreuSignes deleted the feat/modbus-server-registers branch June 28, 2026 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant