941abd98d5
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>
368 lines
12 KiB
Markdown
368 lines
12 KiB
Markdown
# MyIndygo — Platform Overview & API Reference
|
||
|
||
**Base URL**: `https://qualif.myindygo.com`
|
||
**Purpose**: Pool-focused platform — filtration scheduling, water chemistry, pool level monitoring
|
||
**Relation to MySOLEM**: Same backend database and Sails.js server. Both platforms share all module data, sensor readings, and programs. MyIndygo adds a pool admin/management layer on top.
|
||
|
||
---
|
||
|
||
## Authentication
|
||
|
||
Identical to MySOLEM — same cookie name, same credentials work on both platforms.
|
||
|
||
```bash
|
||
curl -c cookies.txt https://qualif.myindygo.com/login
|
||
curl -c cookies.txt -b cookies.txt \
|
||
-X POST \
|
||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||
-d "email=YOUR_EMAIL&password=YOUR_PASSWORD" \
|
||
"https://qualif.myindygo.com/login"
|
||
```
|
||
|
||
Cookie: `qualification-mysolem-solem-irrigation-platform.sid` (same name as MySOLEM)
|
||
|
||
---
|
||
|
||
## User IDs — MyIndygo vs MySOLEM
|
||
|
||
The test API account (`arnaud.nelissen.api@icloud.com`) has **different ObjectIds** on each platform:
|
||
|
||
| Platform | User ID |
|
||
|---|---|
|
||
| MySOLEM | `5de53bca7ed4c45c2cfe067f` |
|
||
| MyIndygo | `6a21c3db5ebe0fa5f7885460` |
|
||
|
||
The MyIndygo user ID appears in the page HTML:
|
||
```html
|
||
<script>const userId = "6a21c3db5ebe0fa5f7885460"</script>
|
||
```
|
||
|
||
The pool/site ID `5de53c1c7ed4c45c2cfe0688` is the main account's pool record on MyIndygo — it's only accessible when logged in as the main account (`anelissen@solem.fr`), not the test API account.
|
||
|
||
---
|
||
|
||
## Feature flags (MyIndygo-specific)
|
||
|
||
`GET /features` returns pool-focused flags including:
|
||
|
||
| Flag | Value | Meaning |
|
||
|---|---|---|
|
||
| `ocedisWaterChemistryCorrection` | `true` | Water chemistry correction (OCEDIS dosing) |
|
||
| `authenticationMyIndygoUIV2` | `true` | MyIndygo-specific login UI |
|
||
| `enableBSTSubscriptions` | `true` | BST subscription management |
|
||
| `enablePWAIndygoInstallation` | `true` | Pool app PWA install |
|
||
| `clusterTurfOpened` | `true` | Turf cluster watering |
|
||
| `enablePolytropicAPI` | `true` | Polytropic pump integration |
|
||
| `enableGraphicalProgrammation` | `true` | Visual program editor |
|
||
| `enableNdviSatellite` | `true` | NDVI satellite data |
|
||
| `enableEvapotranspiration` | `true` | ET-based watering |
|
||
| `enableHPA` | `true` | HPA (high-pressure assist?) |
|
||
|
||
---
|
||
|
||
## Pool Admin System (B2B)
|
||
|
||
MyIndygo has a pool dealer admin layer for managing multiple customer pools. These endpoints are **admin-only** — not accessible with a standard user account.
|
||
|
||
```
|
||
GET /pools/admin list all managed pools
|
||
POST /pools/admin/search search pools (DataTables format)
|
||
POST /pools/admin/sendMessage send a message to a pool customer
|
||
GET /pools/admin/getMessages get admin messages
|
||
DELETE /pools/admin/dissociateAndRemove/{id} remove pool from admin
|
||
```
|
||
|
||
Pool data is fetched via:
|
||
```
|
||
GET /user/pools current user's pools (returns {"pools": []} for test account)
|
||
GET /user/pools?userId= another user's pools (IDOR — no ownership check)
|
||
```
|
||
|
||
---
|
||
|
||
## Pool-Specific Endpoints
|
||
|
||
### Last sensor tick (no date range needed)
|
||
```
|
||
GET /input/getLastTickFromInput?inputId={inputId}
|
||
```
|
||
Returns the single most-recent raw tick for an input. Much lighter than `getComputedSensorData`.
|
||
|
||
Response:
|
||
```json
|
||
{
|
||
"tick": {
|
||
"input": "6480da07c2c0896da120eccd",
|
||
"value": 635,
|
||
"timestamp": "2026-06-04T01:03:00.000Z",
|
||
"createdAt": "2026-06-04T01:03:11.000Z",
|
||
"id": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
Note: `value` is **raw** (not expression-computed). For LRPS pH: `635` = 6.35 pH (divide by 100). For LRPS temp: `2331` = 23.31°C.
|
||
|
||
### Live pool sensor snapshot
|
||
|
||
```python
|
||
import requests
|
||
|
||
BASE = "https://qualif.myindygo.com"
|
||
s = requests.Session()
|
||
s.get(f"{BASE}/login")
|
||
s.post(f"{BASE}/login", data={"email": "...", "password": "..."})
|
||
|
||
POOL_INPUTS = {
|
||
"temp": "6480da07c2c0896da120eccc",
|
||
"pH": "6480da07c2c0896da120eccd",
|
||
"ORP": "6480da07c2c0896da120ecce",
|
||
"pressure": "6a17218be0b88ea3e9255251",
|
||
"level": "6a174dd1e0b88ea3e925531e",
|
||
}
|
||
|
||
readings = {}
|
||
for name, input_id in POOL_INPUTS.items():
|
||
r = s.get(f"{BASE}/input/getLastTickFromInput", params={"inputId": input_id}).json()
|
||
readings[name] = r.get("tick", {})
|
||
|
||
# Current values (raw):
|
||
# temp → divide by 100 → °C
|
||
# pH → divide by 100 → pH units
|
||
# ORP → raw mV
|
||
# pressure → raw ADC (apply expression for bar)
|
||
# level → 0=low/fill needed, 1=OK
|
||
```
|
||
|
||
### Output events history
|
||
```
|
||
GET /output/events?moduleId={moduleId}
|
||
GET /output/events?outputId={outputId}
|
||
GET /output/events?moduleId={id}&startDate={iso8601}&endDate={iso8601}
|
||
```
|
||
Returns `[]` if no events or empty. Tracks when outputs have been triggered.
|
||
|
||
### Output watering forecast
|
||
```
|
||
GET /output/forecast?moduleId={moduleId}
|
||
GET /output/forecast?outputId={outputId}
|
||
```
|
||
Returns upcoming scheduled watering windows.
|
||
|
||
### Real-time module informations (via LoRa)
|
||
```
|
||
GET /remote/module/informations
|
||
?moduleId={relayModuleId}
|
||
&childrenSerialNumber={childSerialNumber}
|
||
```
|
||
Or via socket.io (as seen in JS):
|
||
```js
|
||
io.socket.request({
|
||
method: "get",
|
||
url: "/remote/module/informations",
|
||
data: {
|
||
moduleId: relayId, // LRMB10 gateway ID
|
||
moduleUuid: uuid,
|
||
moduleSerialNumber: serialNumber
|
||
}
|
||
}, callback)
|
||
```
|
||
Returns real-time device state polled over LoRa. Fails with `module_not_reachable` if device is offline.
|
||
|
||
### Real-time combined config + programs
|
||
```
|
||
GET /remote/module/configuration/and/programs?moduleId={moduleId}
|
||
```
|
||
Returns configuration + programs in a single LoRa roundtrip. Requires device online.
|
||
|
||
### Pool controller control (linesControl)
|
||
JSON body (verified from real network capture):
|
||
```
|
||
POST /remote/module/control
|
||
Content-Type: application/json
|
||
```
|
||
```json
|
||
{
|
||
"moduleSerialNumber": "150302F1DA270001",
|
||
"linesControl": [
|
||
{
|
||
"index": 1,
|
||
"action": 2,
|
||
"time": "30:00"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Known `action` codes for LRPC pool controller:
|
||
|
||
| action | meaning | extra params |
|
||
|---|---|---|
|
||
| `0` | Stop pump output (index 0 only) | — |
|
||
| `1` | **Stop / cancel** any running manual command | — |
|
||
| `2` | Timed impulse | `time`: `"MM:SS"` string (e.g. `"30:00"` = 30 minutes) |
|
||
| `3` | Boost filtration | `time`: `"MM:SS"` |
|
||
| `4` | Start indefinitely | — |
|
||
| `5` | Pause N days (blocks manual + scheduled) | `manualDuration`: N (integer days) |
|
||
| `11` | Backwash cycle | `time`: encoded duration string, `speed`: pump speed level |
|
||
|
||
Response pool state includes `"pause": N` (days remaining) when an output is paused.
|
||
Response always reflects **pre-command state** — the command takes effect on the next LoRa downlink.
|
||
|
||
For backwash, duration is encoded using `encodeBackwashDuration(durationMinutes, minMinutes, maxMinutes)`.
|
||
|
||
> **Corrected from JS analysis**: duration is `time: "MM:SS"` (string), NOT `manualDuration` in seconds. Verified from real Safari capture.
|
||
|
||
### Module advertising profile
|
||
```
|
||
GET /remote/module/advertisingProfile?moduleId={moduleId}
|
||
GET /remote/modules/advertisingProfile (bulk)
|
||
```
|
||
BLE advertising profile data. Likely used for Bluetooth pairing.
|
||
|
||
### Team pool access
|
||
```
|
||
GET /teams/pools/{poolId}/membersAccessGranted
|
||
```
|
||
Returns `{ teamMembersAlreadyGrantedPool: [...] }` — team members with access to a specific pool.
|
||
|
||
---
|
||
|
||
## LRPC Pool Controller — Program Structure (MyIndygo extended)
|
||
|
||
MyIndygo exposes more program fields than MySOLEM for `lr-pc` modules:
|
||
|
||
```
|
||
GET /programs/modules?moduleIds={moduleId}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"programs": [
|
||
{
|
||
"id": "6481bcea8782b78554c02bf4",
|
||
"name": "",
|
||
"module": "6481bcea8782b78554c02bed",
|
||
"index": 0,
|
||
"weekDays": 127,
|
||
"numberOfDays": 2,
|
||
"synchroDay": 0,
|
||
"waterBudget": 100,
|
||
"startTimes": [-1, -1, -1, -1, -1, -1, -1, -1],
|
||
"programCharacteristics": {
|
||
"programType": 4,
|
||
"mode": 2,
|
||
"onSpeed": 1,
|
||
"speedSequence": 4368,
|
||
"microFilteringTemperature": 5,
|
||
"microFilteringDuration": 10,
|
||
"microFilteringPeriod": 480,
|
||
"microFilteringSpeed": 1,
|
||
"auxiliaryCyclicDuration": 0,
|
||
"frostFreeSpeed": 1,
|
||
"defaultProgramSpeed": 1,
|
||
"adaptOffset": 0,
|
||
"isShutterDependant": false,
|
||
"isRandom": false,
|
||
"isUsingButton": false
|
||
},
|
||
"windows": [
|
||
{
|
||
"start": 300, "end": 360,
|
||
"on": 3600, "off": 0,
|
||
"runningDays": 127
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**`programType` codes** (pool controller):
|
||
|
||
| code | meaning |
|
||
|---|---|
|
||
| `4` | Main filtration (pump runs on schedule) |
|
||
| `2` | Auxiliary / cyclic (treatment dosing, UV, etc.) |
|
||
| `0` | Disabled / empty slot |
|
||
|
||
**`mode`**: `2` = active, `0` = disabled
|
||
**`speedSequence`**: pump speed bitmask for variable-speed pumps (`4368` = 0x1110)
|
||
**`windows[].runningDays`**: per-window day bitmask (127 = every day, unlike mysolem where weekDays is per-program)
|
||
**`startTimes`**: array of 8 fixed start times in minutes from midnight (-1 = unused)
|
||
|
||
### Domicile LRPC filtration schedule
|
||
|
||
| Program | Type | Windows | Total |
|
||
|---|---|---|---|
|
||
| 0 (Filtration) | 4 | 05:00–06:00 (1h), 12:00–14:00 (2h), 22:00–23:00 (1h) | 4h/day |
|
||
| 1 (Auxiliary) | 2 | 21:30–22:30 (1h) | 1h/day |
|
||
| 2 | — | empty | — |
|
||
|
||
---
|
||
|
||
## LRPC Outputs (Pool Controller)
|
||
|
||
| Output ID | Name | Index |
|
||
|---|---|---|
|
||
| `6481bcea8782b78554c02bf1` | Station 1 | 0 |
|
||
| `6481bcea8782b78554c02bf2` | Station 2 | 1 |
|
||
| `6481bcea8782b78554c02bf3` | Station 3 | 2 |
|
||
|
||
---
|
||
|
||
## New Module Types (found via modules/search on MyIndygo)
|
||
|
||
| Type | Name | Description |
|
||
|---|---|---|
|
||
| `lr-pc-vs` | LRPCVS | Pool controller with variable speed pump |
|
||
| `lr-mv` | LRMV/LRPM1 | Motor valve (multi-output) |
|
||
| `lr-mb-30` | LRMB30 | LoRa gateway v30 |
|
||
| `lr-mb-100` | LRMB100 | LoRa gateway v100 |
|
||
| `lr-ip-fl` | LR1IPFL | IP flow metering |
|
||
| `lr-ip-fl-v2` | LR6IP | IP flow v2 |
|
||
| `lr-ip-wan-v2` | LRIP6 WAN | IP WAN v2 |
|
||
| `lr-is` | LR6IS/LR12IS | LoRa input sensor (multi-input) |
|
||
| `lr-is-city` | LR6IS | City-variant input sensor |
|
||
| `lr-is-city-wan` | LR12IS | City WAN sensor |
|
||
| `lr-ms-eco` | LRMS_ECO | Eco sensor station |
|
||
| `lr-ms-rs485` | LRMSRS485 | RS485 sensor station |
|
||
| `lr-ms-v2` | LRMS v2 | Sensor station v2 |
|
||
| `lr-ol` | LROL4 | LoRa OL (lighting?) |
|
||
| `lr-bst-100` | LRBST100 | BST 100 (booster?) |
|
||
| `lr-bst-react-4g` | LR-BST R4G | BST 4G reactive (booster 4G) |
|
||
| `joro` / `joro-v2` | WB-xxxx | Joro water balance meter |
|
||
| `smart-is` | SmartIS | Smart input sensor |
|
||
| `smart-is-lcd` | VILLA-/SMT-HOME | Smart IS with LCD display |
|
||
| `bl-ip-v2` | BL6IP | Bluetooth IP v2 |
|
||
| `ipx` | GEN_xxxx | IPX controller (generic) |
|
||
|
||
---
|
||
|
||
## Shared Endpoints (same on both MySOLEM and MyIndygo)
|
||
|
||
All endpoints from `api-docs/endpoints.md` work identically on `https://qualif.myindygo.com`. The backend database is shared — module IDs, input IDs, programs, and sensor data are identical on both platforms.
|
||
|
||
Key shared endpoints that work well for pool monitoring:
|
||
- `GET /remote/input/getComputedSensorData?inputId=...` — time-series sensor data
|
||
- `GET /remote/module/getComputedSensorsData?moduleId=...` — all inputs for a module
|
||
- `POST /module/sendManualModuleCommand` — send commands to devices
|
||
- `GET /modules/{moduleId}/notebook?startDate={unix_epoch}` — event log
|
||
- `GET /programs/modules?moduleIds=...` — read/write programs
|
||
|
||
---
|
||
|
||
## Pool Monitoring Quick Reference
|
||
|
||
Current pool sensor IDs and live values (2026-06-04):
|
||
|
||
| Sensor | Input ID | Last Raw Value | Computed |
|
||
|---|---|---|---|
|
||
| Water temp (LRPS) | `6480da07c2c0896da120eccc` | 2331 | 23.31 °C |
|
||
| pH (LRPS) | `6480da07c2c0896da120eccd` | 635 | 6.35 (low — target 7.2–7.6) |
|
||
| ORP/Redox (LRPS) | `6480da07c2c0896da120ecce` | 646 | 646 mV |
|
||
| Filter pressure (LRPR) | `6a17218be0b88ea3e9255251` | 956 | — |
|
||
| Pool level switch | `6a174dd1e0b88ea3e925531e` | 1 | OK (1=full) |
|
||
| Fill flow volume | `6a1c4b153d6c33ab843edc15` | 0 | 0 L (no active fill) |
|
||
| Fill valve state | `6a1c4b153d6c33ab843edc16` | 1 | Closed |
|