feat: initial myprimal MQTT bridge service

Dynamic MySolem/MyIndygo IoT bridge with MQTT, Home Assistant
autodiscovery, and Homebridge EasyMQTT compatibility.

- Device discovery via MySolem API on startup (all modules/inputs/outputs)
- Configurable poll engine (fast/normal/slow buckets, per-input overrides)
- MQTT retained state publishing + HA autodiscovery payloads
- Unified manual command routing (linesControl vs sendManualModuleCommand)
- Runtime config via MQTT $config/set, persisted to config.json
- Expression override system (API-provided > hardcoded > passthrough)
- Minimal REST API: /health, /state, /control, /rediscover
- Docker deployment (Dockerfile + docker-compose.yml)
- 38 unit tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 01:05:24 +02:00
commit 941abd98d5
48 changed files with 12118 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
# LRLEVEL — Pool Fill Valve Controller (LRLEVEL-0D2ED9)
**Module ID**: `6a1c4b153d6c33ab843edc13`
**Type**: `lr-niv` (niveau = level)
**Role**: Controls pool water fill valve; measures volume added and fill state
---
## Inputs
| Input ID | Type | Unit | Description |
|---|---|---|---|
| `6a1c4b153d6c33ab843edc15` | 33 (flow) | L | Volume of water added per fill cycle |
| `6a1c4b153d6c33ab843edc16` | 149 (level state) | raw | Fill valve / level state (0/1) |
### Input edc15 — Water volume (fill meter)
Expression: `javascript:if(x<=0){0.0}else if(x>=80000){68.76}else{-5.01×10⁻¹⁰×...}` (polynomial curve for pulse-to-litre conversion)
Recent readings — all **0 L** for the past 5 ticks (no fill events since 2026-06-03):
```
2026-06-04T19:12 0 L
2026-06-04T13:11 0 L
2026-06-04T07:10 0 L
2026-06-04T01:09 0 L
2026-06-03T19:08 0 L
```
Pool level currently stable — no refill needed.
### Input edc16 — Level state
Type 149 (new), raw 0/1. Consistently reads **1** in all recent ticks (every 6h):
```
2026-06-04T15:12 1
2026-06-04T09:11 1
2026-06-04T03:10 1
2026-06-03T21:09 1
2026-06-03T15:08 1
```
`1` = fill valve closed / level OK.
---
## Output
| Output ID | Name | Index |
|---|---|---|
| `6a1c4b153d6c33ab843edc17` | Station 1 | 0 |
Controls the physical fill valve. Program index 0 ("Station 1") exists but has no active windows — fill is triggered automatically by the level sensor, not by schedule.
---
## Remote state
- Battery: **94%**
- Last radio contact: 2026-06-04T20:12:35Z
- Relay: `60ca4276ad69dac1558f7c61` (LRMB10)
---
## Fill event detection
To detect when the pool was last filled and how much water was added, query the flow input over a wider date range and filter for ticks where `value > 0`:
```python
data = session.get(f"{BASE}/remote/input/getComputedSensorData", params={
"inputId": "6a1c4b153d6c33ab843edc15",
"startDate": "2026-01-01T00:00:00.000Z",
"endDate": "2026-06-04T23:59:59.000Z",
"ensurePrevTickForContinuity": "true",
}).json()
fill_events = [t for t in data["computedSensorData"] if t["value"] > 0]
```
---
## Notes
- Type 149 is a new input type not seen in other modules (likely lr-niv specific level state)
- The system works as: `pool-level-sensor` (float) → signals low → `LRLEVEL` opens fill valve (output 0) → `edc15` measures L added → valve closes when `pool-level-sensor` returns 1