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 4f9aceb
Browse filesBrowse the repository at this point in the historyBrowse files
- Replace flat conditions/action_value model with nested groups->ranges
structure: setting (top-level write) + group_kind + groups[] (each with
condition + ranges[] where each range carries its own action_value).
- Both group_kind and range_kind are free-choice condition kinds (nothing
hardcoded to time/voltage).
- Evaluation: find matching group -> find matching range -> write that
range's action_value to setting. Restore value fires on no-match.
- Rewrite rule_table UI form as nested grid with +/- buttons on both
levels, free-choice dropdowns, and per-range action value input.
- Fix fetchJson to pass through options (was silently dropping POST/DELETE
bodies, causing saves to fire as GETs and wipe local state).
- Fix automation _log: correct rule_id/rule_name variable names and use
'ts' column instead of 'created_at'.
- Rewrite tests for nested model; 27 tests pass.
Copy file name to clipboardExpand all lines: README.md
+54-56Lines changed: 54 additions & 56 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,7 +11,8 @@ Works with EG4, LuxPower, and any rebranded inverter using the LuxPower WiFi don
11
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
-
-**Writes inverter settings** via a safe automation rule engine with dry-run, clamping, and action logging
14
+
-**Writes inverter settings** via a safe holding-register write path with clamping and verification
15
+
-**SolarAssistant-style automations** — rule table, battery SOC control, battery protection, and notifications, with global dry-run for safety (38/57 settings mapped)
| Grafana | ✅ Built | Pre-loaded dashboards and data source |
44
-
| Automation rules | ✅ Built | Rule-table model (time + sensor conditions) that writes holding registers |
45
46
46
47
### Supported inverter models
47
48
@@ -68,13 +69,12 @@ families:
68
69
### In progress / near-term
69
70
70
71
-**Inverter Edit Mode page** — manual Read/Set for every editable EG4 6000XP holding register, mirroring the EG4 Monitor Maintenance tab.
71
-
-**Automation page overhaul** — rebuild around a premade option list + wizard (like SolarAssistant's Power Management), with restore-value support and time/day conditions.
72
72
-**Holding register map corrections** — align `collector/protocol.py` with the `LXP_REGISTERS.txt` reference, add missing registers, fix ranges, and correct AC charge current to register 168.
73
73
74
74
### Roadmap
75
75
76
76
- Generator and AC-coupled charge support (generator-charge register path is stubbed)
|`POST /api/quick-charge/stop`| Stop an active quick charge, restoring the prior value |
@@ -242,50 +246,44 @@ The API server runs on port 80 and provides:
242
246
|`GET /api/storage`| Show DB table sizes and disk usage |
243
247
|`WS /ws`| Live snapshot WebSocket stream |
244
248
245
-
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.
246
-
247
-
### Automation rules
248
-
249
-
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:
250
-
251
-
-`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)
254
-
-`notify` — send an email/webhook notification when a condition is met
255
-
256
-
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:
257
-
258
-
-`time_window`: `{"start": "21:00", "end": "06:00"}` (wraps past midnight)
259
-
-`conditions`: list of `{"sensor": "battery_voltage", "min": 0, "max": 54}` checks
260
-
-`action`: `{"register": "ac_charge_battery_current", "value": 85}` or a `ranges` table
261
-
-`dry_run`: when `true`, the rule logs what it *would* write but never sends a Modbus command
262
-
-`restore`: a value written back when conditions stop matching (or the automation is disabled)
263
-
264
-
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:
The built-in dashboard (`/`) includes a **Power Management** page with three tools:
250
+
251
+
-**Automations** — SolarAssistant-style condition → action rules with a global dry-run toggle.
252
+
-**Timer Schedule** — Grid charge + AC first time slots.
253
+
-**Quick Charge** — one-shot timed grid charge.
254
+
255
+
### Automations
256
+
257
+
Click **Add Automation** to create a rule. Choose from four types:
258
+
259
+
-**Rule table** — pick a setting, add up to two conditions, set the action value, and optionally a restore value.
260
+
-**Battery state of charge control** — time-of-day + SOC thresholds to choose grid vs battery source.
261
+
-**Battery protection** — if SOC drops below a threshold, write a shutdown voltage; restore when SOC recovers.
262
+
-**Send notification** — when a condition is met, send an email/webhook via the existing alert notifier.
263
+
264
+
Conditions include time-of-day, day-of-week, month-of-year, battery SOC/voltage/current, grid voltage/frequency, PV progress, load power, and inverter/battery temperatures.
265
+
266
+
**Safety:** the automation engine has a **global dry-run toggle** (`automation_global_dry_run`) that is ON by default. While dry-run is enabled, the engine evaluates every rule and logs what it *would* write, but it never sends a Modbus command. Turn dry-run OFF only when you are confident the rules behave as intended.
267
+
268
+
Individual automations can be enabled/disabled and temporarily disabled for 30 minutes up to 24 hours from the dashboard card. Saved automations are stored as JSON in the `automations_v2` setting.
269
+
270
+
### Schedule editor
271
+
272
+
The schedule editor writes directly to the inverter's holding registers via
273
+
`PUT /api/holding/{name}`. It covers two groups:
274
+
275
+
-**Grid charge** — `ac_charge_period_*` (3 slots) plus charge power and SOC limit
276
+
-**AC first** — `ac_first_period_*` (3 slots)
285
277
286
-
Enable the engine globally with the `automation_enabled` setting (or the dashboard toggle). Set `dry_run: false` on a rule only when you are ready for live inverter writes.
278
+
Time slots are encoded as `(minute << 8) | hour` (minute in the high byte,
279
+
hour in the low byte), matching the LuxPower register format. Writes are sent
280
+
sequentially (not in parallel) because the dongle rejects bursts of
281
+
simultaneous write connections.
287
282
288
-
**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.
283
+
**Design note:** Readable values (SOC, voltage, current, power) are telemetry
284
+
and are included automatically. Set values (charge current, SOC limits, time
285
+
slots, modes) are only written when explicitly changed via the website. lux-mon
286
+
never writes a setting just because it happens to be readable.
289
287
290
288
### Quick Charge
291
289
@@ -312,8 +310,8 @@ not the quick-charge toggle.
312
310
The quick-charge control lives on the **main dashboard** (a button with a
313
311
▼-toggle for a custom duration, 1–240 minutes, default 60). State is persisted
314
312
in the `lux_settings` table so it survives collector restarts, and all actions
315
-
are logged to the automation log. Writes reuse the automation engine's safe
Copy file name to clipboardExpand all lines: TODO.md
+9-8Lines changed: 9 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -24,9 +24,9 @@
24
24
-`collector/settings.py` lists `forecast_provider` options `open-meteo` and `forecast.solar`, but `collector/forecast.py` only implements the Open-Meteo path.
25
25
-**To do:** implement the Forecast.Solar provider (or remove the option from settings until it's real).
- Implemented 2026-08-28 with option-first field generation, four automation types, two-level conditions, restore-on-exit, disable-for timer, global dry-run, and top cards.
43
+
-**Mapping status:** 38 of 57 settings mapped to known holding registers. 19 remain unmapped (enum/toggle modes and generator/smart-load threshold families) pending confirmed register addresses.
44
+
-**To do:** discover + map the remaining 19 settings, or confirm they are controlled through combined/undocumented registers.
0 commit comments