Skip to content

Adds GoodWe SEMS Portal energy plugin - #44

Open
startswithaj wants to merge 58 commits into
mainfrom
feat/goodwe-sems
Open

Adds GoodWe SEMS Portal energy plugin#44
startswithaj wants to merge 58 commits into
mainfrom
feat/goodwe-sems

Conversation

@startswithaj

Copy link
Copy Markdown
Owner

What this adds

A cloud energy plugin for GoodWe inverters via the SEMS Portal, filling the gap ChargeHQ covered for GoodWe owners.

Works with any GoodWe inverter registered in SEMS — single or multi-inverter stations, with or without a battery. Log in with your SEMS account, pick your station from a list, done.

Requires a GoodWe HomeKit or smart meter. Without one SEMS reports no power flow block, so there is no grid or consumption figure — setup fails with a clear message rather than half-working.

Notes on the SEMS API

It is undocumented — the endpoint GoodWe's own mobile app uses. Behaviour was derived from four open-source clients plus four real captured payloads.

  • Two login endpoints. SEMS+ (MD5+base64 password) is tried first, legacy portal as fallback; whichever the account accepts wins and is preferred next time.
  • grid is an unsigned magnitude. Direction comes from loadStatus (1 = import, -1 = export), confirmed across all four payloads. gridStatus looks like the obvious flag and agrees on three of four, but is -1 in both directions on a multi-inverter station — signing by it would silently report an exporting system as importing, and solar excess would never trigger a charge. Tests pin all three real cases.
  • SEMS misspells the battery keys as bettery/betteryStatus. A test asserts the misspelling is honoured so a future tidy-up fails loudly.
  • Rate limiting is real (GY0429). Poll interval is 60s, not the 30s used for other cloud sources.

Rate-limit handling

Contained entirely in the adapter — no changes to EnergyPoller or ChargeController. On GY0429 it stops issuing requests for 300s and serves the last good reading, bounded to 15 minutes, after which it throws so the outage is recorded honestly. Every outbound call routes through one guard, so connect() and getDeviceInfo() cannot bypass the backoff.

Trade-off worth knowing: a served-from-cache reading is recorded by DataRecorder as a normal reading, so a throttle can put up to 15 minutes of repeated values into energy history. Chosen deliberately over pausing charging.

Simulator

devtools/sems-simulator — a fake SEMS Portal so the integration can be exercised without an account. deno task sems:sim, then point ChargeHA at it with GOODWE_SEMS_BASE_URL. Self-contained, imports nothing from ChargeHA, not a workspace member.

Four station profiles (three-phase no battery, hybrid with battery, multi-inverter, no HomeKit), a daily solar curve, injectable rate limits and token expiry, and it serves both grid-sign interpretations so the mapping is testable either way. deno task sems:sim:smoke runs 25 protocol checks. Request logging never prints passwords, even at LOG_LEVEL=debug.

Also in here

  • @deno/vite-plugin v1 → v2. v1 resolved workspace specifiers by shelling out to deno info --json with Node's default 1MB buffer and silently swallowed failures as "unresolvable". This plugin pushed that output to 1,075,294 bytes, breaking @chargeha/plugins/* resolution for the whole client. A latent limit — the next plugin would have hit it regardless.
  • Shared discovery rows. The result presentation was extracted out of NetworkDeviceSearch so a cloud station list renders identically to LAN discovery. LAN markup unchanged.

Testing

173 server tests, 1569 client tests, deno task check:all green.

Known limitations

  • Verified against captured payloads and the simulator, not a live SEMS account. connect() logs the raw powerflow at info so the first real connection confirms the sign convention.
  • No real payload of a battery actively discharging, so loadStatus could conceivably mean "not from grid" rather than "exporting". Does not affect non-battery inverters.
  • Grid voltage is not available — SEMS reports it per inverter, not in the station power flow.

@startswithaj

Copy link
Copy Markdown
Owner Author

/build

@startswithaj
startswithaj changed the base branch from feat/charger-support to main August 9, 2026 12:55
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

📦 PR image published: ghcr.io/startswithaj/chargeha:pr-44

docker pull ghcr.io/startswithaj/chargeha:pr-44

Built from 0eb8f22view run

@startswithaj
startswithaj changed the base branch from main to feat/charger-support August 9, 2026 21:16
…ilds so a save race can no longer strand the null adapter
…hat logs raw responses without affecting served data
Adds a dev-only component style guide page
Base automatically changed from feat/charger-support to main August 13, 2026 22:10
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