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>
6.9 KiB
MySolem / MyIndygo API — Device Control
All commands work on both https://qualif.mysolem.com and https://qualif.myindygo.com. Requires session cookie.
Manual irrigation command
POST /module/sendManualModuleCommand
Content-Type: application/x-www-form-urlencoded
Payloads are form-encoded. All patterns confirmed via mitmproxy captures. A JSON body variant (same fields) also works — see the example at the end of this section.
Note: the command field is not an action name — it accepts an output ID string, "allStations", or a program ID.
Status commands (enable / disable module)
commandType=status&command=on&commandParams=0 → enable (no rain delay)
commandType=status&command=off&commandParams=0 → disable
commandType=status&command=off&commandParams=1 → 1-day rain delay
commandType=status&command=off&commandParams=255 → disable with max rain delay
Station commands
Run a specific station for a duration:
commandType=station&command={outputId}&commandParams[hours]=0&commandParams[minutes]=1
Cycling station (on/off intervals + optional agricultural module):
commandType=station&command={outputId}&commandParams[hours]=1&commandParams[minutes]=0
&commandParams[on]=1200&commandParams[off]=930&agriculturalModuleId={moduleId}
Stop a specific station:
commandType=station&command={outputId}&commandParams=stop&agriculturalModuleId={moduleId}
Run all stations for a duration:
commandType=station&command=allStations&commandParams[hours]=0&commandParams[minutes]=2
Stop everything immediately (no outputIndex, no commandParams):
commandType=manualStop
Program command (NEW)
Send a specific program to the module:
commandType=program&command={programId}
JSON body equivalent (original documented form)
{
"moduleSerialNumber": "6606010709170001",
"commandType": "station",
"outputIndex": 0,
"command": null,
"commandParams": {
"hours": 0,
"minutes": 15,
"on": 0,
"off": 0
}
}
outputIndex: 0-based index of the output/stationhours+minutes: duration to wateron/off: cycle timing (0 = continuous)agriculturalModuleId: required when stopping or cycling a specific station
Flush pending commands to device
Triggers the platform to push all queued commands to a site's modules:
POST /canopy/{siteId}/sendModulesCommand
Content-Type: application/json
{}
Response: 202 Accepted — { "message": "Modules command accepted for processing" }
Remote module rename
GET /remote/module/name?moduleId={moduleId}&name={newName}
Dissociate module from relay
POST /remote/module/dissociate
Content-Type: application/json
{
"relayMsn": "relaySerialNumber",
"modulesMsn": ["childSerialNumber"]
}
Update module metadata
PUT /module/update
Content-Type: application/json
{ "module": { "id": "moduleId", "name": "New Name", ... } }
PUT /module/update/address
PUT /module/update/status
Remove module
DELETE /module/{moduleId}
GET /module/remove/from/my/modules/{moduleId}
Program update (socket.io legacy)
io.socket.post("/program/update", {
programs: [...],
module: moduleId,
programmationtype: "temporal"
}, callback)
Pool Controller Control (LRPC / lr-pc) — MyIndygo
Pool controllers use a different control endpoint with a linesControl array structure.
POST /remote/module/control
Content-Type: application/x-www-form-urlencoded
Payload (JSON body — Content-Type: application/json):
{
"moduleSerialNumber": "150302F1DA270001",
"linesControl": [
{
"index": 0,
"action": 2,
"time": "60:00"
}
]
}
Action codes (verified from app mitmproxy captures + live testing):
| action | meaning | extra params | notes |
|---|---|---|---|
0 |
Stop pump (index 0 only) | — | Returns erreurtraitement: 6 on other outputs |
1 |
Stop / cancel any running command | — | Works on all outputs; cancels both timed and indefinite runs |
2 |
Timed impulse | time: "MM:SS" string |
Also used to override a running timer early |
3 |
Boost filtration | time: "MM:SS" |
Response: origin:3, info:["boost"] |
4 |
Start indefinitely | — | Runs until action 1 is sent; common in notebook for evening light use |
5 |
Pause N days | manualDuration: N (integer days) |
Disables output including scheduled programs; response shows "pause": N = days remaining |
11 |
Backwash cycle | time: encoded duration, speed: pump speed level |
To stop a timed impulse early: send action 1 — cancels any running manual command.
To stop a timed output (alternative): override with action 2, time: "00:01".
Pause semantics: action 5 suspends the output entirely (manual commands and scheduled programs) for N days. The response pool state includes "pause": N showing days remaining. manualDuration here is days, not seconds.
Response timing: the pool state in the response always reflects the pre-command device state — it shows what the device reported before processing the new command. The command takes effect on the next LoRa downlink window.
Duration format: "MM:SS" string, e.g. "30:00" = 30 minutes. Verified from real capture.
Examples:
Stop fill valve (LRLEVEL):
{"moduleSerialNumber": "3A01020D2ED90001", "linesControl": [{"action": 0, "index": 0}]}
Boost filtration 1 min (LRPC):
{"moduleSerialNumber": "150302F1DA270001", "linesControl": [{"action": 3, "time": "01:00", "index": 0}]}
Response — full real-time device state, e.g. for LRPC after boost:
{
"dialogTimeStamp": "...",
"ackTimestamp": 1780609493,
"temperature": 20,
"pool": [
{"index": 0, "origin": 3, "time": "01:00", "value": 1, "info": ["boost"]},
{"index": 1, "time": "00:00", "tempRef": 7, "value": 0}
]
}
Python example — timed pool pump impulse (30 min on Station 2):
import json
s.post(f"{BASE}/remote/module/control",
json={
"moduleSerialNumber": "150302F1DA270001",
"linesControl": [{"index": 1, "action": 2, "time": "30:00"}]
}
)
LRPC output IDs (Domicile)
| Index | Name | Output ID |
|---|---|---|
| 0 | Station 1 | 6481bcea8782b78554c02bf1 |
| 1 | Station 2 | 6481bcea8782b78554c02bf2 |
| 2 | Station 3 | 6481bcea8782b78554c02bf3 |
Notes
- Commands go through the platform's relay infrastructure — the device doesn't need to be directly reachable
- Use
POST /canopy/{siteId}/sendModulesCommandafter updating programs viaPUT /programsto force an immediate push moduleSerialNumberis theserialNumberfield on the module object (not theid)- For irrigation valves (lr-ag, lr-ip-eco): use
POST /module/sendManualModuleCommand - For pool controllers (lr-pc, lr-pc-vs): use
POST /remote/module/controlwithlinesControl