Skip to content

Driver: port GoodWe hybrid inverter (ET-Plus / EH) to Lua v2.1 - #28

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

frahlg merged 1 commit into
masterfrom
worktree-agent-a7cc1875

Conversation

@frahlg

@frahlg frahlg commented Apr 15, 2026

Copy link
Copy Markdown
Member

Summary

  • Ports the community GoodWe driver from sourceful-hugin/device-support/drivers/lua/goodwe.lua into forty-two-watts under the v2.1 Lua host idiom.
  • Read-only Modbus TCP (holding registers, big-endian). Emits pv, battery, and meter plus scalar diagnostics (MPPT V/A, battery DC V/A/temp, per-phase meter V/A/W, grid Hz) via host.emit_metric.
  • API drift applied: host.log(msg) -> host.log("info", msg); decode_u32/decode_i32 -> decode_u32_be/decode_i32_be; adds the DRIVER metadata table so the driver shows up in the catalog.
  • Efficiency: coalesces the three per-phase V+A reads into one 6-register read at 35121, and the three per-phase power reads into one 6-register read at 35132 — drops ~10 Modbus round-trips per poll.
  • Sign convention handled at the boundary: GoodWe's grid meter is export-positive, so we negate; PV is negated to "always <= 0"; battery is already site convention (positive = charge).
  • Control is not implemented. driver_command warns and returns false; driver_default_mode is a no-op (no forced state to revert).

Tested models (from hugin manifest): ET-Plus, EH series.

Test plan

  • cd go && go test -count=1 ./internal/drivers/ — green
  • cd go && go test -count=1 ./... — green
  • make e2e — green (full stack, 23s)
  • Smoke test: NewLuaDriver loads drivers/goodwe.lua, driver_init + driver_poll run without a Modbus capability (every read is pcall-wrapped and degrades to zero-valued telemetry).
  • Catalog test: LoadCatalog("drivers") picks up the DRIVER block with id=goodwe, manufacturer=GoodWe, protocols=[modbus], capabilities=[meter, pv, battery].
  • Live validation against ET-Plus / EH hardware (no device available here — needs field verification before promoting out of read-only).

🤖 Generated with Claude Code

Read-only Modbus TCP driver. Emits pv + battery + meter telemetry
from holding registers (FC 0x03), all multi-register values big-endian.

Ported from sourceful-hugin/device-support/drivers/lua/goodwe.lua with
the v2.1 API drift applied:
- host.log(msg) -> host.log("info", msg)
- host.decode_u32/i32 -> host.decode_u32_be / host.decode_i32_be
- adds DRIVER metadata table for catalog
- adds host.emit_metric diagnostics (MPPT V/A, battery DC, phase V/A/W,
  grid Hz, battery temp)
- coalesces per-phase V+A into one 6-register read at 35121 and per-phase
  power into one 6-register read at 35132, cutting ~12 Modbus round-trips
  per poll to 2

Sign convention: GoodWe reports grid meter with positive=export, so the
driver negates at the boundary. Battery power (reg 35180) is already in
site convention (positive=charge). PV is negated to the convention
(always <= 0 = generation).

Control is not implemented — this is a read-only driver for now.
driver_command logs a warning and returns false; driver_default_mode is
a no-op since there is no forced state to revert.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@frahlg
frahlg merged commit e43d2d9 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-a7cc1875 branch August 7, 2026 08:19
frahlg added a commit that referenced this pull request Sep 27, 2026
… tariff rule (#1451)

* fix(planner): keep replan inputs consistent and demand history on the tariff rule

- #25 Config reload swaps fuse, export ceiling, export prices, VAT, demand
  tariff and timezone in one locked UpdateSiteEconomics call. Each replan
  copies them, k and the arbitrage spread with its params, so one plan never
  mixes old and new values and the hot-reload data race is gone.
- #26 SetMode and SetSafetyK change the setting and queue a background replan
  under one lock instead of solving on the caller's context. A disconnecting
  HTTP client no longer cancels the solve into a fallback plan, and saving
  planner prefs no longer runs two solves under configWriteMu. Startup sets
  the restored mode and restores the persisted plan before the planner starts.
- #27 Demand-charge history (AlreadyKW) applies the Ellevio night weight and
  keeps one peak per local day, like the planned hours.
- #46 SavePrices removes cached rows that a new row overlaps, and the ENTSO-E
  parser keeps the finer series where PT15M and PT60M overlap, so a mixed
  resolution no longer stops every replan.
- #28 unchanged: covering the current clock hour needs an Energyplan contract
  change and pin bump.

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

* docs(changeset): say the one-peak-per-day rule covers every demand tariff

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