You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 4f002ca
Browse filesBrowse the repository at this point in the historyBrowse files
**Local monitoring for LuxPower-based inverters — no cloud required.**
4
4
5
-
Works with EG4, LuxPower, and any rebranded inverter using the LuxPower WiFi dongle protocol (TCP port 8000).
5
+
Works with EG4, LuxPower, and any rebranded inverter using the LuxPower WiFi dongle protocol (TCP port 8000). Also supports RS-485 battery BMS monitoring (EG4 A5/5A, JK BMS, generic Modbus RTU).
6
6
7
7
## What It Does
8
8
9
9
-**Passively listens** to your inverter's WiFi dongle — zero bus contention
10
10
-**Actively polls** as a fallback for non-broadcasting dongles
11
-
-**Stores** time-series data in **MariaDB/MySQL** (InfluxDB optional)
11
+
-**Stores** time-series data in **MariaDB/MySQL** (InfluxDB optional, both can run together)
12
12
-**Exposes** a **REST API** for scripting, morning briefings, Home Assistant, etc.
13
13
-**Streams live snapshots** over a **WebSocket** (`/ws`) so the web dashboard updates instantly
14
14
-**Writes inverter settings** via a safe automation rule engine with dry-run, clamping, and action logging
15
-
-**Home Assistant energy dashboard** ready — publishes MQTT sensors with `state_class: total_increasing` for solar, grid, and battery flows
|`POST /api/quick-charge/stop`| Stop an active quick charge, restoring the prior value |
195
213
|`POST /api/backup`| Create a backup archive |
196
214
|`GET /api/backups`| List backup archives |
197
215
|`POST /api/prune`| Prune old detail data |
198
216
|`GET /api/storage`| Show DB table sizes and disk usage |
217
+
|`WS /ws`| Live snapshot WebSocket stream |
199
218
200
219
The built-in dashboard (`/`) includes an **Automations** page where you can enable the engine, add rules with time windows and sensor conditions, and test them in dry-run mode before allowing live inverter writes.
201
220
202
221
### Automation rules
203
222
204
-
Rules are stored as JSON in the `automation_rules` setting and evaluated after every snapshot. A rule can set a holding register to a static value or choose a value from a sensor-range table (e.g., different charge amps for different battery-voltage bands). Each rule supports:
223
+
Rules are stored as JSON in the `automation_rules` setting and evaluated after every snapshot. The engine uses a **rule-table model** (target-first, nested subset columns, restore-on-exit) that mirrors the EG4/Luxpower "Power Management" portal. Four automation types are supported:
224
+
225
+
-`rule_table` — a multi-dimensional rule table (e.g. grid charge current as a function of time-of-day and battery voltage)
-`battery_protection` — "if SOC ≤ X%, shutdown output; restore to Y when recovered" (restore-on-exit)
228
+
-`notify` — send an email/webhook notification when a condition is met
229
+
230
+
Only one automation may be active per target type. A rule can set a holding register to a static value or choose a value from a sensor-range table. Each rule supports:
205
231
206
232
-`time_window`: `{"start": "21:00", "end": "06:00"}` (wraps past midnight)
207
233
-`conditions`: list of `{"sensor": "battery_voltage", "min": 0, "max": 54}` checks
208
234
-`action`: `{"register": "ac_charge_battery_current", "value": 85}` or a `ranges` table
209
235
-`dry_run`: when `true`, the rule logs what it *would* write but never sends a Modbus command
236
+
-`restore`: a value written back when conditions stop matching (or the automation is disabled)
210
237
211
238
Only registers listed in `collector/protocol.py:HOLDING_REGISTERS` can be written, and every value is clamped to the register's documented min/max. Example rule matching SolarAssistant's night grid-charge behavior:
212
239
@@ -234,11 +261,37 @@ Enable the engine globally with the `automation_enabled` setting (or the dashboa
234
261
235
262
**Design note:** Readable values (SOC, voltage, current, power) are telemetry and are included automatically. Set values (charge current, SOC limits, time slots, modes) are only written when explicitly changed via the website or when an automation rule says "this is what we want it to be." lux-mon never writes a setting just because it happens to be readable.
236
263
237
-
The API also serves a built-in web dashboard at `/` with real-time gauges, power flow, historic charts, battery details, energy totals, and runtime settings.
264
+
### Quick Charge / Generator Charge
265
+
266
+
lux-mon implements the two one-shot inverter actions the EG4 Monitor portal exposes:
267
+
268
+
-**Quick Charge** — write `ac_charge_battery_current` (register 168) to a target current for a fixed number of minutes, then restore the prior value.
269
+
-**Generator Charge** — write the generator charge current / enable path (register path stubbed, see roadmap).
270
+
271
+
State is persisted in the `lux_settings` table so it survives collector restarts. Writes reuse the automation engine's safe write helpers (fresh socket, echo verification, clamping). Defaults are configurable via the `quick_charge_amps` and `quick_charge_minutes` settings.
272
+
273
+
### Solar PV forecast
274
+
275
+
lux-mon can forecast PV production using a weather-based model (Option A) with optional historical calibration (Option B):
276
+
277
+
1. Fetch hourly weather (cloud cover + shortwave radiation) from **Open-Meteo** (free, no API key).
278
+
2. Compute a clear-sky PV power curve using **pvlib** (sun position + clear-sky irradiance transposed onto the tilted array plane).
279
+
3. Scale by a cloud factor, then apply bifacial back-side gain.
280
+
4. Optionally correct today's forecast using the last N days of actual-vs-forecast error (bucketed by hour-of-day and cloud cover).
281
+
5. Persist the predicted/corrected watts to MariaDB (`lux_solar_forecast` table).
238
282
239
-
A pre-built Grafana dashboard is included in `grafana/dashboards/lux-mon-charts.json` and provisioned automatically by `scripts/setup-grafana-stack.sh`. It uses a SolarAssistant-compatible InfluxDB schema so existing SolarAssistant dashboards import directly.
283
+
The forecast is exposed at `GET /api/forecast` and overlaid on the dashboard's PV chart (corrected forecast shown as a yellow dashed line). All forecast parameters (location, array kWp/azimuth/tilt, bifacial gain, provider, horizon, refresh interval, calibration) are runtime settings editable from the dashboard ⚙️ tab.
240
284
241
-
For Home Assistant users, see [`docs/home-assistant-energy.md`](docs/home-assistant-energy.md).
285
+
### Alerts
286
+
287
+
Alert thresholds are evaluated after every snapshot and published to MQTT (for HA binary sensors) and MariaDB (`lux_alerts` table). Supported alerts:
288
+
289
+
- Battery SOC low / critical
290
+
- Battery temperature high
291
+
- Inverter temperature high
292
+
- Grid lost (configurable threshold in seconds)
293
+
294
+
Notifications are dispatched via authenticated SMTP relay and/or webhook, rate-limited to one per 5 minutes per alert. All thresholds and notification targets are runtime settings.
242
295
243
296
### Home Assistant integration
244
297
@@ -306,9 +359,25 @@ All config can be set via env vars:
306
359
|`LUX_API_HOST`|`0.0.0.0`| API bind address |
307
360
|`LUX_API_PORT`|`80`| API port |
308
361
362
+
### RS-485 / BMS environment variables
363
+
364
+
The `lux-mon-rs485` daemon (see `collector/rs485_collector.py`) polls an RS-485/serial device and writes to the same backends:
365
+
366
+
| Variable | Default | Description |
367
+
|----------|---------|-------------|
368
+
|`LUX_RS485_ENABLED`|`false`| Enable the RS-485 collector |
The collector supports two storage backends. Choose one by setting `LUX_STORAGE_TYPE`.
380
+
The collector supports two storage backends, which can be enabled together. Choose by setting `LUX_STORAGE_TYPE` (or the individual `LUX_*_ENABLED` flags).
312
381
313
382
### MariaDB / MySQL (default)
314
383
@@ -329,6 +398,10 @@ LUX_MARIADB_DATABASE=luxmon
329
398
Tables are auto-created on first run:
330
399
-`lux_snapshots` — one row per write interval with timestamp and raw register JSON
331
400
-`lux_registers` — one row per decoded register value, indexed by timestamp and name
401
+
-`lux_settings` — runtime settings (key/value)
402
+
-`lux_alerts` — alert events
403
+
-`lux_solar_forecast` — solar PV forecast time series
Set `LUX_MARIADB_TABLE_PREFIX` to change the table prefix from `lux_` if needed.
334
407
@@ -348,6 +421,8 @@ LUX_INFLUX_ORG=your-org
348
421
LUX_INFLUX_BUCKET=solar
349
422
```
350
423
424
+
The InfluxDB schema is **SolarAssistant-compatible** (one measurement per metric, `inverter_0`/`combined` fields), so existing SolarAssistant Grafana dashboards import directly.
425
+
351
426
### Adding a New Backend
352
427
353
428
The collector uses a pluggable writer pattern. To add support for PostgreSQL, SQLite, or another database:
@@ -360,7 +435,7 @@ See `_create_mariadb_writer()` and `_write_mariadb()` for the pattern to follow.
360
435
361
436
## Runtime Settings
362
437
363
-
Settings are stored in the `lux_settings` MariaDB table (auto-created) and read live by the API and dashboard — no config files, no restarts. Change any value with a single API call and the dashboard picks it up on the next refresh.
438
+
Settings are stored in the `lux_settings` MariaDB table (auto-created) and read live by the API and dashboard — no config files, no restarts. Change any value with a single API call and the dashboard picks it up on the next refresh. The collector detects config changes on each write cycle and re-applies live-safe settings in place (or exits for a Docker restart when a transport/model change requires it).
364
439
365
440
| Setting | Default | Description |
366
441
|---------|---------|-------------|
@@ -373,6 +448,20 @@ Settings are stored in the `lux_settings` MariaDB table (auto-created) and read
0 commit comments