Repository navigation
Drivers: port Huawei SUN2000 hybrid inverter - #15
Conversation
Ported from sourceful-hugin to the forty-two-watts v2.1 Lua host idiom. Supports PV (two MPPTs), LUNA2000 battery, built-in 3-phase meter, and forcible charge/discharge control over Modbus TCP. - All lifecycle functions implemented (init, poll, command, default_mode, cleanup). - DRIVER metadata block so it appears in the catalog. - host.log(level, msg) and host.decode_u32_be / i32_be rewritten to v2.1 API (hugin used host.log(msg) + host.decode_u32). - Reads ASCII serial number from holding 30015 once and anchors device identity via host.set_make + host.set_sn. - Diagnostic scalars (MPPT V/A, inverter temp, battery DC V/A/temp, per-phase V/A/W, grid Hz) emitted via host.emit_metric. - Site convention applied at the boundary: PV w negated (generation is negative), meter w + per-phase w + per-phase current negated (Huawei reports positive = export). - All host.modbus_read calls wrapped in pcall. - driver_default_mode and driver_cleanup revert to maximise self-consumption so a watchdog trip can't leave the battery stuck in forcible mode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dbb160b86f
ℹ️ 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".
| local ok_mw, mw_regs = pcall(host.modbus_read, 37113, 2, "holding") | ||
| local meter_w = 0 | ||
| if ok_mw and mw_regs then | ||
| meter_w = host.decode_i32_be(mw_regs[1], mw_regs[2]) | ||
| end |
There was a problem hiding this comment.
Fail poll on meter read errors instead of publishing zeros
When host.modbus_read(37113, ...) fails, meter_w stays at 0 and the poll continues to host.emit("meter", { w = -meter_w, ... }), so a transport/register failure is reported as valid zero grid power rather than a failed sample. In this codebase, successful emits update freshness/health, so during Modbus outages this driver can keep telemetry “healthy” with fabricated zeros and drive incorrect control decisions (e.g., dispatch based on false no-import/no-export state) instead of tripping stale-data safeguards.
Useful? React with 👍 / 👎.
## 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))
|
🎉 This PR is included in version 2.0.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
## [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))
Summary
Ports the Huawei SUN2000 hybrid inverter driver from
sourceful-huginto the forty-two-watts v2.1 Lua host idiom.SUN2000L1,SUN2000-LUNA2000.Notable porting decisions
host.log(msg)→host.log("info", msg)(v2.1 API).host.decode_u32(hi,lo)→host.decode_u32_be(hi,lo); same fori32. Huawei uses big-endian word order so every multi-register decode became_be.host.modbus_write_multiple→host.modbus_write_multi(v2.1 spelling).30015(10 regs ASCII) sohost.set_snanchorsdevice_idto hardware identity. Hugin didn't do this.host.emit_metricinto the long-format TS DB.wis negated (generation is negative); meterw, per-phasew, and per-phase current are negated because Huawei reports positive = export.host.modbus_readwrapped inpcall.driver_default_modeanddriver_cleanupboth callset_self_consumptionso a watchdog trip can't leave the battery stuck in forcible mode.Test plan
cd go && go test -count=1 -run TestLua ./internal/drivers/— passes.DRIVERblock parses: id=huawei-sun2000, manufacturer=Huawei, protocols=[modbus], capabilities=[meter,pv,battery].LuaDriver(make=Huaweicaptured; poll/command/default_mode run without Modbus capability sincepcallswallows the no-capability errors).cd go && go test ./...— full suite green.🤖 Generated with Claude Code