Skip to content

drivers: port Growatt hybrid inverter driver (read-only) - #20

Merged
frahlg merged 1 commit into
masterfrom
worktree-agent-ab3ac8bd
Apr 15, 2026
Merged

frahlg merged 1 commit into
masterfrom
worktree-agent-ab3ac8bd

Conversation

@frahlg

@frahlg frahlg commented Apr 15, 2026

Copy link
Copy Markdown
Member

Summary

  • Ports drivers/growatt.lua from sourceful-hugin to the 42W v2.1 Lua host idiom (host.log(level, msg), decode_u32_be, decode_i32_be).
  • Read-only driver: emits PV, battery, and meter telemetry over Modbus TCP INPUT registers (FC 0x04), Big-Endian word order. Control is not implemented — the device runs autonomously.
  • Adds the full DRIVER metadata table so the driver shows up in GET /api/drivers/catalog and the Settings → Devices picker. Tested models: SPH, MOD.
  • Sign conversion at the driver boundary: PV negated (generation → site convention), battery = charge_w − discharge_w, meter already native-positive-import so no flip.
  • driver_command returns false silently instead of logging to avoid spamming warn every dispatch tick (the control loop sends a battery command every 5 s to every driver with battery capability).

Test plan

  • go test ./... — full suite green (including drivers + e2e)
  • Ad-hoc TestLuaDriverLifecycle-style smoke test: loads, init/poll/command/default cycle all succeed; host.set_make("Growatt") captured in identity
  • LoadCatalog("drivers") parses the metadata correctly (id, manufacturer, protocols, capabilities populated)
  • Live test against a real SPH / MOD unit (follow-up — no hardware in CI)

🤖 Generated with Claude Code

Ports the Growatt SPH/MOD Modbus driver from sourceful-hugin to the 42W
v2.1 Lua host. Read-only: emits PV, battery and meter telemetry; the
device manages charge/discharge autonomously so driver_command returns
false without logging (control loop ticks every 5 s and would otherwise
flood warn). Signs are converted at the driver boundary: PV negated,
battery derived from separate charge/discharge registers, meter already
matches site convention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f5c3dbe948

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread drivers/growatt.lua
local ok_pvw, pvw_regs = pcall(host.modbus_read, 1, 2, "input")
local pv_w = 0
if ok_pvw and pvw_regs then
pv_w = host.decode_u32_be(pvw_regs[1], pvw_regs[2]) * 0.1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use decode helpers that exist in the Lua host

driver_poll uses host.decode_u32_be/host.decode_i32_be, but the Rust Lua host registers big-endian decoders as decode_u32/decode_i32 (plus *_le) in src/lua/host_api/decode.rs. In that runtime, this call path fails with attempt to call a nil value as soon as the register read succeeds, so polling errors out and the Growatt driver never emits telemetry reliably.

Useful? React with 👍 / 👎.

@frahlg
frahlg merged commit 92524ac into master Apr 15, 2026
github-actions Bot pushed a commit to erikarenhill/forty-two-watts that referenced this pull request Apr 15, 2026
## 1.0.0 (2026-04-15)

### Bug Fixes

* **ci:** switch semantic-release to conventionalcommits preset ([15290c7](15290c7))
* Lua driver Command() reading wrong field — Sungrow ignored targets ([9237156](9237156))

### Drivers

* add Eastron SDM630 Lua driver ([srcfl#18](https://github.com/erikarenhill/forty-two-watts/issues/18)) ([d5ad806](d5ad806))
* add Ferroamp Modbus driver (alt transport to ferroamp.lua) ([srcfl#31](https://github.com/erikarenhill/forty-two-watts/issues/31)) ([03b802c](03b802c))
* port Deye SUN-SG hybrid inverter to 42W v2.1 Lua host ([srcfl#29](https://github.com/erikarenhill/forty-two-watts/issues/29)) ([df8fbc0](df8fbc0))
* port Fronius GEN24 (SunSpec) to Lua ([srcfl#19](https://github.com/erikarenhill/forty-two-watts/issues/19)) ([c1fc875](c1fc875))
* port Fronius Smart Meter (SunSpec Modbus, read-only) ([srcfl#24](https://github.com/erikarenhill/forty-two-watts/issues/24)) ([575895c](575895c))
* port GoodWe hybrid inverter (ET-Plus / EH) to Lua v2.1 ([srcfl#28](https://github.com/erikarenhill/forty-two-watts/issues/28)) ([e43d2d9](e43d2d9))
* port Growatt hybrid inverter driver (read-only) ([srcfl#20](https://github.com/erikarenhill/forty-two-watts/issues/20)) ([92524ac](92524ac))
* port Huawei SUN2000 hybrid inverter ([srcfl#15](https://github.com/erikarenhill/forty-two-watts/issues/15)) ([09a8855](09a8855))
* port Kostal Plenticore / Piko IQ (Lua, read-only) ([srcfl#21](https://github.com/erikarenhill/forty-two-watts/issues/21)) ([bdeca96](bdeca96))
* port Pixii PowerShaper battery driver to v2.1 Lua host ([srcfl#22](https://github.com/erikarenhill/forty-two-watts/issues/22)) ([70a96d1](70a96d1))
* port SMA hybrid inverter Lua driver ([srcfl#23](https://github.com/erikarenhill/forty-two-watts/issues/23)) ([dd34555](dd34555))
* port Sofar HYD-ES/HYD-EP from hugin to Lua v2.1 ([srcfl#26](https://github.com/erikarenhill/forty-two-watts/issues/26)) ([14f6131](14f6131))
* port SolarEdge SunSpec inverter + meter to Lua (read-only) ([srcfl#30](https://github.com/erikarenhill/forty-two-watts/issues/30)) ([1007e63](1007e63))
* port Solis hybrid inverter ([srcfl#27](https://github.com/erikarenhill/forty-two-watts/issues/27)) ([98b2a50](98b2a50))
* port Victron Energy GX Modbus driver ([srcfl#25](https://github.com/erikarenhill/forty-two-watts/issues/25)) ([ad71db2](ad71db2))

### UI

* inline target on hover + driver card + collapsible model cards ([de88f43](de88f43))
* legend wrap + nice-tick y-axis + cleaner chart labels ([srcfl#33](https://github.com/erikarenhill/forty-two-watts/issues/33)) ([aeb1d1c](aeb1d1c))
* live version from API + no-cache headers on static assets ([7b8779b](7b8779b))

### Control loop

* fold live DerEV readings into the EV clamp ([srcfl#36](https://github.com/erikarenhill/forty-two-watts/issues/36)) ([5d57d68](5d57d68))

### MPC planner

* fall back to forecast when learned PV twin collapses ([srcfl#39](https://github.com/erikarenhill/forty-two-watts/issues/39)) ([f3062ac](f3062ac))

### Telemetry

* add DerEV type for EV charger readings ([srcfl#34](https://github.com/erikarenhill/forty-two-watts/issues/34)) ([65c9e2c](65c9e2c))

### TSDB

* long-format SQLite (14d) + Parquet rolloff for older ([c53c964](c53c964))

### Safety

* watchdog marks stale drivers offline + reverts to autonomous ([519196c](519196c))
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 2.0.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

github-actions Bot pushed a commit that referenced this pull request Apr 16, 2026
## [0.4.0](v0.3.0...v0.4.0) (2026-04-16)

### Features

* config/UI improvements — kWh display, secure EV password, planner tab ([#65](#65)) ([35ab03d](35ab03d))
* Easee Cloud driver + host.http_get/post for Lua drivers ([#56](#56)) ([4cdc942](4cdc942))
* EV charger config + credential masking in API responses ([#58](#58)) ([c22cb80](c22cb80))

### Bug Fixes

* 5 Go-side P1 bugs from Codex review ([#46](#46)) ([0cd2885](0cd2885))
* 5 Go-side P1 bugs from Codex review ([#47](#47)) ([4f2eaf6](4f2eaf6))
* address P2 review comments across control, MPC, drivers, and UI ([#64](#64)) ([fcafa88](fcafa88))
* **ci:** disable @semantic-release/github PR annotation features ([4020d46](4020d46)), closes [#32](#32) [#33](#33) [#34](#34) [#35](#35) [#36](#36) [#39](#39)
* **ci:** switch semantic-release to conventionalcommits preset ([7e0bb89](7e0bb89))
* **ci:** upgrade GitHub Actions to Node.js 24 (drop deprecated Node 20) ([4005bd8](4005bd8))
* Lua driver Command() reading wrong field — Sungrow ignored targets ([9237156](9237156))
* populate EV Charger tab from driver config when ev_charger is empty ([5e6b116](5e6b116))
* remove dead evSlider event listeners that crash app.js ([8ae76c7](8ae76c7))
* replace wonky Catmull-Rom spline with simple linear forecast ([abea431](abea431))
* show '...' instead of stale v0.1.0 while JS loads version ([dc65065](dc65065))
* **solaredge_pv:** read SunSpec scale factors every poll, not cached ([#38](#38)) ([26f8793](26f8793))

### Drivers

* add Eastron SDM630 Lua driver ([#18](#18)) ([d5ad806](d5ad806))
* add Ferroamp Modbus driver (alt transport to ferroamp.lua) ([#31](#31)) ([03b802c](03b802c))
* fix 9 P1 bugs flagged by Codex review ([#44](#44)) ([b20e485](b20e485))
* port Deye SUN-SG hybrid inverter to 42W v2.1 Lua host ([#29](#29)) ([df8fbc0](df8fbc0))
* port Fronius GEN24 (SunSpec) to Lua ([#19](#19)) ([c1fc875](c1fc875))
* port Fronius Smart Meter (SunSpec Modbus, read-only) ([#24](#24)) ([575895c](575895c))
* port GoodWe hybrid inverter (ET-Plus / EH) to Lua v2.1 ([#28](#28)) ([e43d2d9](e43d2d9))
* port Growatt hybrid inverter driver (read-only) ([#20](#20)) ([92524ac](92524ac))
* port Huawei SUN2000 hybrid inverter ([#15](#15)) ([09a8855](09a8855))
* port Kostal Plenticore / Piko IQ (Lua, read-only) ([#21](#21)) ([bdeca96](bdeca96))
* port Pixii PowerShaper battery driver to v2.1 Lua host ([#22](#22)) ([70a96d1](70a96d1))
* port SMA hybrid inverter Lua driver ([#23](#23)) ([dd34555](dd34555))
* port Sofar HYD-ES/HYD-EP from hugin to Lua v2.1 ([#26](#26)) ([14f6131](14f6131))
* port SolarEdge SunSpec inverter + meter to Lua (read-only) ([#30](#30)) ([1007e63](1007e63))
* port Solis hybrid inverter ([#27](#27)) ([98b2a50](98b2a50))
* port Victron Energy GX Modbus driver ([#25](#25)) ([ad71db2](ad71db2))

### UI

* add status bar with driver health indicators ([b048d60](b048d60))
* EV status card + Easee control commands ([#59](#59)) ([b03749a](b03749a))
* fix summary cards grid for 7 cards + raise side-by-side breakpoint ([6e19973](6e19973))
* inline target on hover + driver card + collapsible model cards ([de88f43](de88f43))
* legend wrap + nice-tick y-axis + cleaner chart labels ([#33](#33)) ([aeb1d1c](aeb1d1c))
* remove manual EV charging slider ([063174c](063174c))
* show mode band in plan chart + grid target on status card ([877e0bd](877e0bd))
* smooth Catmull-Rom spline for forecast + 15min forecast zone ([dba51a5](dba51a5))

### Control loop

* fold live DerEV readings into the EV clamp ([#36](#36)) ([5d57d68](5d57d68))
* slew-rate anchors on actual battery power, not stale command ([#41](#41)) ([4f73f19](4f73f19))

### MPC planner

* fall back to forecast when learned PV twin collapses ([#39](#39)) ([f3062ac](f3062ac))
* log optimize params + ems_mode per action for plan chart ([9e8c14b](9e8c14b))
* value SoC at import−export spread in self-consumption modes ([#40](#40)) ([a90d525](a90d525))

### Telemetry

* add DerEV type for EV charger readings ([#34](#34)) ([65c9e2c](65c9e2c))

### TSDB

* long-format SQLite (14d) + Parquet rolloff for older ([c53c964](c53c964))

### Safety

* watchdog marks stale drivers offline + reverts to autonomous ([519196c](519196c))
@frahlg
frahlg deleted the worktree-agent-ab3ac8bd branch August 7, 2026 08:19
frahlg added a commit that referenced this pull request Sep 27, 2026
…overs (#1456)

* refactor(state,mpc,api): remove dead storage, planner and backup leftovers

- #49 Keep on a driver draft now takes the driver update lock and removes
  the record before the saved original, so an expiry already in flight
  finishes first and Keep answers 409 instead of "kept" for a reverted draft.
- #20 History maintenance runs only the plain SQLite stages. Remove the
  Parquet archive pipeline (rolloff, aggregate/sample archive, legacy
  compaction, cold/diagnostic Parquet pruning, Store.Prune) and the unused
  retentionDays closure of rolloffLoop; keep the Parquet readers.
- #40 Drop the hot-history alias (s.hot, hotPath, hotWriteMu), dead
  hot-only branches and readers, forceRotate, heldBytes, RotateHistory and
  the test-only historyWriter.maintainHistory.
- #41 Remove the always-empty ImportedHistoryFiles filter, the unused
  coldDir parameter of legacyHistoryIdle and the test-only backup.Restore;
  round-trip tests now use RestoreContents.
- #29 Remove the recourse/multistage challenger, unix and auto transports,
  transport Health and the stateful shadow evaluator. The worker request
  keeps the same solver settings as constants; the Energyplan process
  transport and Core DP shadow are unchanged.
- #47 Remove the always-zero PV nameplate and load caps (Service fields,
  Plan.pv_nameplate_w/load_max_w, forecast ACLimitW/ClampForecasts and
  their web chart caps), the load rain-check and test-only wrappers. A
  single sanitiser still maps NaN or negative load to 0 W.
- #48 Handshake errors now say the bundled Energyplan worker does not match
  Core and to run `ftw update` or reinstall. Drop PYTHONPATH and fix stale
  Update Center, CVXPY, Python shadow and scenario comments.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7

* test(mpc): the native contract test no longer expects a scenario model

The shared PV scenarios in buildRequest were reachable only by calling
ExternalOptimizer directly with a risk margin; production runs through
EnergyplanOptimizer, which applies the margin to the slots and zeroes it
before the request. Removing that dead block left the native contract
test asking for three scenarios. It now checks that a margin never
builds a scenario model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MuerPFZFG88kgu8sWVHeq7

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant