Repository navigation
feat(gateway): PR1 — domain types, state bus, scale-factor helpers - #1
Merged
Merged
Conversation
…*, switch to GitHub Flow
…ergyKwh, operatingState numeric, lastUpdatedAt ms, vendorName, modelName) and move to src/state/inverter-state.types.ts
… publish()/snapshot(); publish() owns lastUpdatedAt; snapshot() zeros all production fields on stale
…integer→SF=0 fits; fractional→most-negative; overflow→SF=1+; clamp at SF_FLOOR); applyScaleFactor uses 10^(-sf); add encode() helper; splitAcc32 returns named object
…s, fixed algorithm, complete stale coverage; ci: drop -- separator for pnpm test --coverage; lint: allow unused vars prefixed with _
andreuSignes
force-pushed
the
pr1/domain-state-scalefactor
branch
from
June 28, 2026 15:45
018fc3a to
18103f0
Compare
…ction-setup + concurrency - .npmrc: add node-version=24 as single source of truth - ci.yml: grep node-version from .npmrc into $GITHUB_OUTPUT, pass to setup-node - ci.yml: swap manual corepack + pnpm --version for pnpm/action-setup@v4 (version: 11) - ci.yml: add concurrency block (cancel-in-progress on same ref) - ci.yml: add permissions: contents: read (least privilege) - package.json: engines.node >=20 -> >=24 chore: drop silently-ignored kebab-case key 'verify-deps-before-run' from pnpm-workspace.yaml (canonical key is verifyDepsBeforeRun)
- Add @commitlint/cli + @commitlint/config-conventional + husky (v9) as devDependencies - Initialize .husky/ via `pnpm exec husky init` - Add commit-msg hook invoking `pnpm exec commitlint --edit` - Add commitlint.config.js extending @commitlint/config-conventional with: - header-max-length: 72 - type-case: lowercase - subject-case: lowercase first letter - body/footer line length: 100 - Allow CommonJS globals in *.config.js via ESLint override (commitlint ships CJS by default) - Add CI guard: `pnpm dlx commitlint --from=HEAD~5 --to=HEAD` in .github/workflows/ci.yml Verified: `git commit -m "chore: ..."` passes; `git commit -m "Chore ..."` is rejected. Refs: Context7 docs for commitlint, husky, conventionalcommits.
actions/setup-node@v4 with `cache: 'pnpm'` invokes pnpm to compute cache keys. If pnpm is not on PATH yet, the step fails with "Unable to locate executable file: pnpm" and the whole job aborts before `pnpm/action-setup` runs. Reorder so `pnpm/action-setup@v4` installs pnpm first, then setup-node can locate it for caching. Discovered while verifying the commitlint CI guard on PR #1 — the failure is unrelated to commitlint and exists on prior commits too (ddd6ae6, 18103f0).
package.json declares `packageManager: pnpm@11.0.0` as the source of truth. pnpm/action-setup@v4 errors with "Multiple versions of pnpm specified" if both `version:` and `packageManager` are present. Remove the redundant `version: 11` input; let package.json drive the version.
actions/checkout defaults to a shallow clone (depth=1) for pull_request events, which means HEAD~5..HEAD cannot be resolved and the commitlint guard crashes with "ambiguous argument". Set `fetch-depth: 0` so the full history is available to commitlint.
The original `--from=HEAD~5 --to=HEAD` range tripped on PR1 commits that predate the Conventional Commits convention (long headers, long body lines, missing scopes). Once this PR merges and PR1's commits are baselined, a wider range can be reintroduced. For now, `--from=HEAD~1` checks only the freshest commit. The per-commit hook remains the primary defense; this CI step catches the bypass scenarios that motivated it — squashed merges via the GitHub web editor with a hand- typed, non-conforming message.
actions/checkout@v4 checks out a synthetic merge commit (`refs/pull/N/merge`) for PR builds, with two parents: the PR head and the PR base. Any `--from=..--to=HEAD` range that includes HEAD walks both lineages, so the guard lints PR1's pre-convention commits along with my new ones. Narrowing to HEAD~1 still picks up the synthetic merge + every PR1 commit reachable through it (CI showed commits as old as 9c0d55f failing). A real fix needs history rewrite of PR1 commits or a config that exempts them — both out of scope for this intro PR. The per-commit husky hook remains the primary defense. A follow-up can re-add a CI guard once this PR merges and PR1's commits are baselined.
andreuSignes
force-pushed
the
pr1/domain-state-scalefactor
branch
from
June 28, 2026 16:03
9db4681 to
6a88396
Compare
- actions/checkout: v4 -> v6 - pnpm/action-setup: v4 -> v6 - actions/setup-node: v4 -> v6 - Replace grep-based Node version extraction from .npmrc with native node-version-file: '.nvmrc' support (setup-node v6 does not read .npmrc for node-version-file; supported sources are .nvmrc, .node-version, .tool-versions, and package.json engines.node/volta.node/devEngines.runtime) - Remove fetch-depth: 0 since the commitlint CI guard was dropped earlier Refs: setup-node Context7 docs (/actions/setup-node), .nvmrc convention used by nvm/fnm/Volta.
andreuSignes
added a commit
that referenced
this pull request
Jun 28, 2026
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR1 v2 — domain types, state bus, scale-factor, pnpm, ESLint, CI
Foundation slice of the
sunspec-modbus-gatewaychange.What this PR delivers
Domain types (
src/state/inverter-state.types.ts)InverterStateinterface with all numeric fields renamed to design.md §3:acPowerWatts,acVoltageVolts,acCurrentAmps,gridFrequencyHz,lifetimeEnergyKwh,operatingState(numeric union1..8),isStale,lastUpdatedAt(ms epoch).vendorNameandmodelNamefields added per design.md §3.M101_STconst enum mapping SunSpecStenum values (1..8).InverterAdapterabstract class withvendorName,modelName,read().INVERTER_ADAPTERDI token.State bus (
src/state/inverter-state.service.ts)InverterStateServicewithpublish()andsnapshot()(renamed fromsetState()/getState()).publish()ownslastUpdatedAt: stampsDate.now()on fresh publishes,preserves the previous timestamp on stale publishes so the time-based
stale check trips correctly.
snapshot()zeroes ALL production fields on stale (acPowerWatts,acVoltageVolts, acCurrentAmps, gridFrequencyHz, operatingState → OFF=1),
preserves
lifetimeEnergyKwhand identity fields (vendorName, modelName,serialNumber).
Scale-factor math (
src/modbus/scale-factor.ts)chooseScaleFactoralgorithm: integer values prefer SF=0; fractionalvalues prefer the most-negative SF that fits; overflow downgrades to
positive SF; clamps at
SF_FLOOR(-10).applyScaleFactoruses10^(-sf)to recover the raw register value(e.g. 230.5 V with SF=-1 → raw 2305).
encode()one-shot helper returning{ value, scaleFactor }.splitAcc32()returns a named{ hi, lo }object (BE: upper 16 bits in hi).Review-blocking issues resolved (v1 review)
InverterStatematch design.md §3 exactly.InverterAdapter(fetchState→read) andInverterStateService(setState/getState→publish/snapshot).that handles 230.5 → SF=-1, 50000 → SF=1, 1e15 → SF=-10 (clamp).
fields, preserves
lifetimeEnergyKwhand identity.Strategy pivot (new in v2)
packageManagerfield inpackage.json,.npmrcwithshamefully-hoist=truefor NestJS deps,pnpm-lock.yamlreplaces
package-lock.json).eslint.config.mjswith@eslint/js+typescript-eslint,lintscript,pnpm lint)..github/workflows/ci.yml— lint + build + teston every PR and push to
main, Node 20, pnpm via corepack).maindirectly. PR2/PR3 renamedfrom
pr2/.../pr3/...tofeat/...; merge sequence updated intasks.md. PR1 branch keeps its name because the PR is already open.
Verification
Coverage: 94.36% statements / 88.88% branches / 94.82% lines.
CI workflow runs the same commands on every PR; ready for review.
Files
src/state/inverter-state.types.ts(new)src/state/inverter-state.service.ts(rewritten)src/modbus/scale-factor.ts(rewritten)src/gateway/inverters/inverter.adapter.ts(re-export shim)src/domain/inverter-state.ts(deleted — moved)test/unit/scale-factor.spec.ts(expanded)test/unit/inverter-state.service.spec.ts(rewritten)eslint.config.mjs(new).github/workflows/ci.yml(new)package.json,.npmrc,pnpm-lock.yaml,pnpm-workspace.yamlopenspec/changes/sunspec-modbus-gateway/{design,tasks}.md(updated)Refs: openspec/changes/sunspec-modbus-gateway/