A fully local, cloud-independent monitoring system for GoodWe GW9.99K-EH solar inverters with Lynx F G2 battery storage and myenergi Zappi EV charger.
Polls 145+ inverter sensors every 30 seconds via Modbus TCP, stores data in InfluxDB, and serves a production-grade React dashboard over HTTPS — with no dependency on GoodWe SEMS cloud.
- Real-time power flow — animated diagram with solar, grid, battery, house, and EV
- 145+ live sensors — PV strings, battery cells, grid/AC, temperatures
- Configurable poll interval — 1s to 60min, changed in UI without restart
- ZeroHero VPP billing — GloBird ZEROCHARGE free window, Super Export 6–9pm tracker, $1/day credit monitor
- TOU billing — per-tier kWh and cost breakdown with CT meter accuracy
- Battery scheduling with:
- SOC conditions (min/max to start)
- kWh energy limits with per-day reset
- Dynamic discharge — auto-adjusts power % to pace toward target SOC at window end
- Immediate charge — uses inverter immediate charge registers to force grid charging regardless of solar
- AI Chat — Gemini 2.5 Flash with function calling (optional)
- Sankey flow diagram, historical charts, bill calculator
- Battery warranty tracker — MWh throughput vs warranty ceiling
- HTTPS — Let's Encrypt DNS-01 via AWS Route 53, auto-renewing
| Component | Tested Model | Notes |
|---|---|---|
| Inverter | GoodWe GW9.99K-EH (EHB-AU-G11) | Firmware 050531. Other GoodWe Modbus inverters may work. |
| Battery | Lynx F G2 — 38.4 kWh (2×6×3.2 kWh) | |
| EV Charger | myenergi Zappi | Via myenergi cloud API |
| Protocol | Modbus TCP port 502, slave 0xF7 (247) |
Note:
goodwePython library is version-locked at0.4.9. Do not upgrade.
| Service | Technology | Port |
|---|---|---|
| Database | InfluxDB 2.7 | 8086 (internal) |
| Collector | Python + goodwe==0.4.9 | — |
| Backend | FastAPI | 8000 (internal) |
| Frontend | React + Vite + Tailwind + nginx | 80 → 443 |
| TLS | Certbot + Route 53 DNS-01 | — |
- Linux VM (Ubuntu 22.04+), 2 GB RAM minimum
- Docker + Docker Compose
- Domain hosted on AWS Route 53 (for Let's Encrypt DNS-01)
- GoodWe inverter reachable on port 502
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER && newgrp dockergit clone https://github.com/yourusername/goodwe-local-monitor.git
cd goodwe-local-monitorcp .env.example .env
nano .env| Variable | Description |
|---|---|
INFLUX_TOKEN |
Random secret — openssl rand -hex 32 |
DOMAIN |
Your domain e.g. goodwe.yourdomain.com |
CERTBOT_EMAIL |
Email for Let's Encrypt |
AWS_ACCESS_KEY_ID |
Route 53 IAM key |
AWS_SECRET_ACCESS_KEY |
Route 53 IAM secret |
AUTH_USER |
Dashboard username (default: admin) |
AUTH_PASS_SHA |
SHA-256 of password — echo -n "pass" | sha256sum |
SESSION_SECRET |
Random secret — openssl rand -hex 32 |
GEMINI_API_KEY |
Optional — enables AI Chat tab |
Create an A record: goodwe.yourdomain.com → <VM local IP>
Let's Encrypt verifies via DNS TXT record — VM does not need to be internet-reachable.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"route53:GetChange",
"route53:ChangeResourceRecordSets",
"route53:ListHostedZones",
"route53:ListResourceRecordSets"
],
"Resource": "*"
}]
}sudo docker compose up -d
sudo docker compose logs -f certbot # watch cert issuance (~60s)Open https://goodwe.yourdomain.com — setup wizard will guide you through inverter IP and tariff configuration.
bat_power_percent: 100
dynamic_discharge: true
target_end_soc: 60
min_soc_to_start: 60
grid_export: Enable
export_kwh_limit: 15.5
bat_power_percent: -100
immediate_charge: true
immediate_charge_power: 20
⚠️ immediate_charge_poweris non-linear on GW9.99K-EH firmware. With a 40A single-phase breaker (9.2 kW max),20%≈ 7 kW battery charge — safe with typical 4–6 kW house load. Adjust for your breaker.
Confirmed on firmware 04094-02-S10:
| Register | Name | Values |
|---|---|---|
| 47000 | Operation Mode | 0=General, 3=Eco, 5=Eco Charge, 6=Eco Discharge |
| 47509 | Grid Export Enabled | 0=off, 1=on |
| 47510 | Grid Export Limit | 0–10000 W |
| 47511 | EMS Mode | 0=Self-Consumption, 1=Forced |
| 47512 | EMS Power Limit | 0–10000 W |
| 47515–47518 | Eco Slot 1 | Start, End, Power%, WorkWeek |
| 47519–47530 | Eco Slots 2–4 | Clear to 0x0000 |
| 47603 | Immediate Charge Power % | 0–100 (non-linear, actual control register) |
| 47604 | Immediate Charge Enable | 0=off, 1=on (primary) |
| 47545 | Immediate Charge Enable | 0=off, 1=on (secondary) |
WorkWeek: 0xFF7F = all days, 0xFF1F = weekdays, 0x007F = disabled.
| Quirk | Impact |
|---|---|
pbattery1 sign inverted (+ = discharging) |
All direction logic must account for this |
PV total register sentinel 0xFFFFFFFF |
Sum 4 string registers individually |
GridExport=0 does NOT stop solar export |
Only controls battery-to-grid |
OpMode=5 (Eco Charge) does NOT force grid charging on this firmware |
Use immediate charge registers instead |
Reg 47546 is read-only |
Use reg 47603 for immediate charge power |
# Logs
sudo docker compose logs backend --tail=50
sudo docker compose logs collector --tail=50
# Hot-swap backend only
sudo docker cp backend/main.py goodwe_backend:/app/main.py
sudo docker compose restart backend
# Full rebuild
sudo docker compose down frontend backend
sudo docker compose build --no-cache frontend backend
sudo docker compose up -d
# Force cert renewal
sudo docker compose restart certbot| Symptom | Fix |
|---|---|
| 502 Bad Gateway after rebuild | sudo docker compose restart frontend |
| Certbot fails | Check AWS credentials and IAM policy |
| Schedule not firing | docker compose logs backend | grep scheduler |
| Schedule stuck on "Activating" | Restart backend to reset limit tracker |
| EV shows 0W during grid charge | Ensure sta==3 and sta==4 both handled (fixed v44) |
goodwe-local-monitor/
├── backend/main.py # FastAPI — endpoints, scheduler, auth
├── collector/collector.py # Modbus polling → InfluxDB
├── frontend/src/ # React dashboard
├── certbot/ # Let's Encrypt DNS-01
├── docker-compose.yml
├── .env.example
└── README.md
- Dynamic immediate charge power — calculate safe % from live house load and solar
- Local DNS / self-signed cert option (no Route 53 requirement)
- Home Assistant MQTT publish
- Multi-inverter support
MIT — see LICENSE









