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
+200
View File
@@ -0,0 +1,200 @@
# myprimal
MQTT bridge for the MySolem / MyIndygo IoT platform (irrigation + pool). Reverse-engineered client that dynamically discovers all devices and exposes them via MQTT with Home Assistant autodiscovery and Homebridge EasyMQTT compatibility.
## Features
- Auto-discovers all modules, sensors, and outputs on startup
- Polls sensor readings on configurable intervals
- Publishes to MQTT with retained state
- Home Assistant MQTT autodiscovery (sensors + switches)
- Compatible with Homebridge [EasyMQTT](https://github.com/Shaquu/homebridge-easymqtt) plugin
- Manual commands via MQTT (`/set` topics)
- Runtime poll interval tuning via MQTT
- REST API for health, state snapshot, and direct control
- Docker deployment
## Setup
### 1. Configure
Copy `.env.example` to `.env` and fill in credentials:
```bash
cp .env.example .env
```
Required:
- `SOLEM_EMAIL` / `SOLEM_PASSWORD` — MySolem account credentials
- `MQTT_URL` — your MQTT broker (e.g. `mqtt://192.168.1.10:1883`)
### 2. Run locally
```bash
npm install
node src/index.js
```
### 3. Run with Docker
```bash
docker compose up -d
```
On first run, `config.json` is created in the Docker volume for persistent poll interval settings.
## MQTT Topics
All topics are prefixed with `MQTT_TOPIC_PREFIX` (default: `myprimal`).
### Sensor readings (retained)
```
myprimal/{module_slug}/sensor/{metric}
```
Payload: `{"value": 24.5, "unit": "°C", "ts": "2026-06-28T10:00:00.000Z"}`
Examples:
```
myprimal/lrpc_f1da27/sensor/temperature {"value": 24.5, "unit": "°C", ...}
myprimal/lrps_0565c0/sensor/ph {"value": 7.35, "unit": "pH", ...}
myprimal/lrps_0565c0/sensor/orp_redox {"value": 690, "unit": "mV", ...}
myprimal/lr6ag_070917/sensor/plumbago {"value": 54, "unit": "%", ...}
```
### Output state (retained)
```
myprimal/{module_slug}/output/{index}/state
```
Payload: `{"value": 0, "ts": "..."}``value` 1 = running, 0 = stopped
### Commands
```
myprimal/{module_slug}/output/{index}/set
```
Payload:
```json
{ "action": "run", "duration": 30 } // run for 30 minutes
{ "action": "stop" } // stop immediately
{ "action": "boost", "duration": 120 } // boost (pool pump only)
{ "action": "pause", "days": 3 } // pause/rain delay N days
```
### Availability (retained)
```
myprimal/{module_slug}/availability "online" | "offline"
```
### Config (retained, runtime-tunable)
Read current config:
```
myprimal/$config
```
Update poll intervals (seconds):
```
myprimal/$config/set {"poll": {"fast": 30, "normal": 120}}
```
Update interval for a specific sensor:
```
myprimal/$config/set {"poll": {"overrides": {"lrps_0565c0/ph": 30}}}
```
Changes take effect immediately and persist across restarts.
### Poll buckets (defaults)
| Bucket | Default | Sensor types |
|---|---|---|
| `fast` | 60s | pH, ORP, pressure, water temperature |
| `normal` | 300s | Soil moisture, air temperature, flow |
| `slow` | 900s | Binary sensors (rain, level switch, valve state) |
## Home Assistant
Entities are auto-created via MQTT discovery on startup. Check **Settings → Devices & Services → MQTT** — all Solem devices appear automatically.
Discovery topics: `homeassistant/{sensor|binary_sensor|switch}/myprimal_{id}/config`
No manual configuration required.
## Homebridge (EasyMQTT)
Install [homebridge-easymqtt](https://github.com/Shaquu/homebridge-easymqtt) and configure accessories pointing to the MQTT topics above.
Example for pool light switch:
```json
{
"accessory": "EasyMQTT",
"name": "Pool Light",
"type": "Switch",
"mqttGetTopic": "myprimal/lrpc_f1da27/output/1/state",
"mqttSetTopic": "myprimal/lrpc_f1da27/output/1/set",
"mqttGetTransformation": "return JSON.parse(message).value === 1 ? 'true' : 'false';",
"mqttSetTransformation": "return value === 'true' ? JSON.stringify({action:'run',duration:30}) : JSON.stringify({action:'stop'});"
}
```
## REST API
| Method | Path | Description |
|---|---|---|
| `GET` | `/health` | Service health + counts |
| `GET` | `/state` | All cached sensor readings |
| `GET` | `/state/:moduleId` | Readings for one module |
| `GET` | `/registry` | Full device registry |
| `POST` | `/control/:moduleId/:outputIndex` | Send manual command |
| `POST` | `/rediscover` | Re-discover all devices |
### Control example
```bash
# Run pool light (LRPC output 1) for 30 minutes
curl -X POST http://localhost:3001/control/6481bcea8782b78554c02bed/1 \
-H 'Content-Type: application/json' \
-d '{"action":"run","duration":30}'
# Stop
curl -X POST http://localhost:3001/control/6481bcea8782b78554c02bed/1 \
-H 'Content-Type: application/json' \
-d '{"action":"stop"}'
```
## Module Slug Reference (Domicile)
| Module | Slug | Type |
|---|---|---|
| LRPC-F1DA27 (pool controller) | `lrpc_f1da27` | Filtration pump (output 0), light (output 1) |
| LR6AG-070917 (irrigation) | `lr6ag_070917` | 6 irrigation stations (outputs 05) |
| LRMS-0721DC (sensor station) | `lrms_0721dc` | Soil moisture × 2, air temp |
| LRPS-0565C0 (pool analyser) | `lr_mas_0565c0` | Water temp, pH, ORP |
| LRPR-0D6800 (filter pressure) | `lr_pr_0d6800` | Filter pressure |
| LRLEVEL-0D2ED9 (fill valve) | `lr_niv_0d2ed9` | Fill valve control |
| LR2IPECO-0C9282 | `lr_ip_eco_0c9282` | Flow meter |
| POOL-LVL-0E953B (level switch) | `pool_level_sensor_0e953b` | Pool level binary |
## Architecture
```
MySolem API ──► solem.js (auth + HTTP)
registry.js (device discovery on startup)
poller.js (per-bucket poll timers)
state.js (in-memory cache, EventEmitter)
┌─────────┴──────────┐
mqtt/publish.js api/routes.js
mqtt/discovery.js (REST API)
mqtt/commands.js ──► control.js ──► MySolem API
mqtt/config-bridge.js
```