# MySolem / MyIndygo — Complete API Reference Reverse-engineered from black-box analysis: mitmproxy captures of iOS apps, Safari Web Inspector recordings, JS bundle static analysis, and live endpoint probing. **Sources**: `inputs/apps_captured_all.jsonl`, `inputs/lrpc_post.json`, `inputs/mysolem_captured_all.jsonl`, `inputs/indygo_captured_all.jsonl`, `inputs/captured_all.jsonl`, JS bundles at `/min/production.min.js` and `/linker/js/esbuild-bundle.js`. --- ## Table of Contents 1. [Platforms & Architecture](#1-platforms--architecture) 2. [Authentication](#2-authentication) 3. [Key IDs — Domicile Site](#3-key-ids--domicile-site) 4. [Web API — Endpoints](#4-web-api--endpoints) 5. [Mobile API — /api/ Endpoints](#5-mobile-api--api-endpoints) 6. [Device Control](#6-device-control) 7. [MyIndygo — Pool Platform](#7-myindygo--pool-platform) 8. [Device Reference — Domicile](#8-device-reference--domicile) 9. [Write Endpoints Reference](#9-write-endpoints-reference) 10. [Reference Tables](#10-reference-tables) 11. [Security Findings](#11-security-findings) --- ## 1. Platforms & Architecture | Platform | Base URL | Focus | |---|---|---| | MySOLEM | `https://qualif.mysolem.com` | Irrigation, garden sensors | | MyIndygo | `https://qualif.myindygo.com` | Pool control, water chemistry | Both platforms share **the same backend database and API server** — same module IDs, input IDs, session cookie, and sensor data. MyIndygo adds a pool admin/management layer on top. All web endpoints work identically on either host. **Backend**: Node.js + Sails.js v0.13.8, MongoDB (all IDs are 24-char hex ObjectIds) **Proxy**: nginx/1.18.0 (Ubuntu) **Real-time**: Socket.IO v3 / Engine.IO v3 (`EIO=3`), direct WebSocket transport (not long-polling) ``` wss://qualif.mysolem.com/socket.io/?EIO=3&transport=websocket ``` **Two separate API layers on the same host**: - **Web API** — no path prefix, session cookie auth - **Mobile API** — `/api/` prefix, OAuth2 Bearer token auth --- ## 2. Authentication ### 2.1 Web API — Session Cookie Cookie-based session (Sails.js). No CSRF token required on JSON endpoints. ```bash # Step 1 — init cookie jar curl -c cookies.txt https://qualif.mysolem.com/login # Step 2 — authenticate 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.mysolem.com/login" # → 302 redirect to / on success # All subsequent calls curl -b cookies.txt https://qualif.mysolem.com/features ``` **Cookie name**: `qualification-mysolem-solem-irrigation-platform.sid` **Session TTL**: ~7 days Same cookie works on both `qualif.mysolem.com` and `qualif.myindygo.com`. **Python:** ```python import requests BASE = "https://qualif.mysolem.com" s = requests.Session() s.get(f"{BASE}/login") # init cookie s.post(f"{BASE}/login", data={ "email": "YOUR_EMAIL", "password": "YOUR_PASSWORD", }) # 302 → / on success # s carries the session cookie automatically on all subsequent calls ``` ### 2.2 Mobile API — OAuth2 Bearer Token The iOS apps use a completely separate auth layer at `/api/` prefix. ``` POST /oauth2/token Content-Type: application/json {"scope": "*", "grant_type": "client_credentials"} ``` Response: ```json { "access_token": "6wtff8dVlhjn7y...", "expires_in": 5184000, "token_type": "Bearer" } ``` `expires_in` = 5,184,000 seconds = **60 days**. The `client_id` and `client_secret` are embedded in the app binary and validated server-side — not present as explicit headers in captured traffic. All subsequent mobile API requests: ``` Authorization: Bearer {access_token} ``` **Python:** ```python import requests BASE = "https://qualif.mysolem.com" r = requests.post(f"{BASE}/oauth2/token", json={"scope": "*", "grant_type": "client_credentials"}) token = r.json()["access_token"] headers = {"Authorization": f"Bearer {token}"} resp = requests.get(f"{BASE}/api/getUser", headers=headers) ``` --- ## 3. Key IDs — Domicile Site ### Users | Platform | Account | User ID | |---|---|---| | MySOLEM | arnaud.nelissen.api@icloud.com | `5de53bca7ed4c45c2cfe067f` | | MyIndygo | arnaud.nelissen.api@icloud.com | `6a21c3db5ebe0fa5f7885460` | ### Site | Resource | ID | |---|---| | Site (Domicile) | `62b30d26ecbceb08607592c1` | | Pool (Domicile) | `5de53c1c7ed4c45c2cfe0688` | | Field supervision | `643c5134347b27edbf38b173` | ### Modules | Module | Type | Module ID | Serial Number | childSerial6 | |---|---|---|---|---| | LRMB10-070532 (gateway) | `lr-mb-10` | `60ca4276ad69dac1558f7c61` | `1000000705320001` | — | | LRMS-0721DC (sensor station) | `lr-ms` | `60ca42adad69dac1558f7c64` | `7400040721DC0001` | `0721DC` | | LR6AG-070917 (irrigation valve) | `lr-ag` | `626984f038ec558c598bb6d5` | `6606010709170001` | `070917` | | LRPS-0565C0 (pool analyser) | `lr-mas` | `6480da07c2c0896da120ecca` | `9400030565C00001` | `0565C0` | | LRPC-F1DA27 (pool controller) | `lr-pc` | `6481bcea8782b78554c02bed` | `150302F1DA270001` | `F1DA27` | | LR2IPECO-0C9282 (IP ECO) | `lr-ip-eco` | `6516ca57b7c098e6807cb332` | `3802020C92820001` | `0C9282` | | LRPR-0D6800 (filter pressure) | `lr-pr` | `6a17218be0b88ea3e925524f` | `1D00010D68000001` | `0D6800` | | POOL-LVL-0E953B (float switch) | `pool-level-sensor` | `6a174dd1e0b88ea3e925531c` | `0300020E953B0001` | — | | LRLEVEL-0D2ED9 (fill valve) | `lr-niv` | `6a1c4b153d6c33ab843edc13` | `3A01020D2ED90001` | `0D2ED9` | All child modules route through gateway `1000000705320001` (LRMB10) via LoRa radio. ### Inputs | Sensor | Module | Input ID | Type | Unit | |---|---|---|---|---| | Plumbago moisture | LRMS | `60ca42adad69dac1558f7c66` | 12 | % | | Pelouse moisture | LRMS | `60ca42adad69dac1558f7c67` | 12 | % | | Air temperature | LRMS | `60ca42adad69dac1558f7c69` | 5 | °C | | Rain sensor | LR6AG | `626984f038ec558c598bb6d7` | 2 | 0/1 | | Pool water temperature | LRPS | `6480da07c2c0896da120eccc` | 133 | °C | | Pool pH | LRPS | `6480da07c2c0896da120eccd` | 6 | pH (raw ÷ 100) | | Pool ORP / Redox | LRPS | `6480da07c2c0896da120ecce` | 7 | mV | | Flow meter | LR2IPECO | `6516ca57b7c098e6807cb334` | 33 | L | | Filter pressure | LRPR | `6a17218be0b88ea3e9255251` | 8 | bar | | Pool level switch | POOL-LVL | `6a174dd1e0b88ea3e925531e` | 21 | 0/1 | | Fill volume | LRLEVEL | `6a1c4b153d6c33ab843edc15` | 33 | L | | Fill valve state | LRLEVEL | `6a1c4b153d6c33ab843edc16` | 149 | 0/1 | | LRPC temperature | LRPC | `6481bcea8782b78554c02bef` | 133 | °C | ### Outputs | Output | Module | Output ID | Index | Flow rate | |---|---|---|---|---| | AG Station 1 | LR6AG | `626984f038ec558c598bb6d8` | 0 | 30 m³/h | | AG Station 2 | LR6AG | `626984f038ec558c598bb6d9` | 1 | 14 m³/h | | AG Station 3 | LR6AG | `626984f038ec558c598bb6da` | 2 | 20 m³/h | | AG Station 4 | LR6AG | `626984f038ec558c598bb6db` | 3 | 1 m³/h | | AG Station 5 | LR6AG | `626984f038ec558c598bb6dc` | 4 | 38 L/h | | AG Station 6 | LR6AG | `626984f038ec558c598bb6dd` | 5 | 38 L/h | | LRPC Station 1 (filtration pump) | LRPC | `6481bcea8782b78554c02bf1` | 0 | — | | LRPC Station 2 (pool light) | LRPC | `6481bcea8782b78554c02bf2` | 1 | — | | LRPC Station 3 | LRPC | `6481bcea8782b78554c02bf3` | 2 | — | | IPECO Station 1 | LR2IPECO | `6516ca57b7c098e6807cb336` | 0 | — | | IPECO Station 2 | LR2IPECO | `6516ca57b7c098e6807cb337` | 1 | — | | LRLEVEL fill valve | LRLEVEL | `6a1c4b153d6c33ab843edc17` | 0 | — | ### Programs | Program | Module | Program ID | Index | Type | |---|---|---|---|---| | Filtration (4h/day) | LRPC | `6481bcea8782b78554c02bf4` | 0 | 4 (filtration) | | Auxiliary/cyclic | LRPC | `6481bcea8782b78554c02bf5` | 1 | 2 (auxiliary) | | Unused | LRPC | `6481bcea8782b78554c02bf6` | 2 | — | --- ## 4. Web API — Endpoints All paths relative to `https://qualif.mysolem.com` (or `https://qualif.myindygo.com` — identical backend). All require session cookie. ### 4.1 Feature Flags ``` GET /features ``` Returns flat JSON object of boolean flags: `dashboard`, `enableHydraulicSupervision`, `enableBSTSubscriptions`, `enablePolytropicAPI`, `enableGraphicalProgrammation`, `enableNdviSatellite`, `enableEvapotranspiration`, `ocedisWaterChemistryCorrection`, etc. ### 4.2 Sites ``` GET /canopy-sites?userId={userId} ``` Returns `{ items: [...], total: N }`. Each item includes full `modules` array and `fencing` config. ``` GET /canopy-sites/{siteId}/users GET /canopy-sites/{siteId}/modules?limit=20&skip=0&includeTotalCount=true GET /canopy-sites/{siteId}/modules/indicators GET /canopy-sites/{siteId}/informations GET /canopy-sites/{siteId}/rules GET /canopy-sites/{siteId}/map/modules GET /canopy-sites/{siteId}/modules/eco-mode-compatible GET /canopy-sites/{siteId}/site-notes GET /canopy-sites/{siteId}/notifications GET /canopy/{siteId}/waterBudget ``` ### 4.3 Modules **Search / list (MongoDB-style filter):** ``` POST /modules/search Content-Type: application/json { "query": { "owner": "userId" }, "projection": { "name": 1, "type": 1, "serialNumber": 1 } } ``` Response: `{ result: { results: [...], totalCount: null } }` ⚠ Empty query `{}` returns **all 4,607 platform modules** — no ownership enforcement. ``` GET /modules/available?userId={userId} GET /modules/echarts-data?includeCustomers=true&includeAgency=false → {chartModuleTypes: [{name, value}], chartModuleConnectivities: [...]} GET /sim-cards/needsPayment?moduleIds={id1}&moduleIds={id2} → {result: {"moduleId": false, ...}} ``` **Flexible module list:** ``` POST /users/{userId}/modules Content-Type: application/json { "fields": {"name": 1, "type": 1, "serialNumber": 1, "displayType": 1}, "filters": {}, "addOwnershipFlag": true, "includeTotalCount": true, "limit": 20, "skip": 0 } ``` Response: `{modules: [...], totalCount: N}` **Remote state (cached + live LoRa):** ``` GET /remote/module/state?moduleId={moduleId} GET /remote/module/configuration?moduleId={moduleId} GET /remote/module/configuration/and/programs?moduleId={moduleId} GET /remote/module/informations?moduleId={relayId}&childrenSerialNumber={childSerial} ``` Live endpoints trigger a LoRa radio roundtrip — returns `module_not_reachable` if device is offline. **Module metadata:** ``` GET /modules/{moduleId}/users GET /modules/{moduleId}/tags GET /modules/{moduleId}/notebook?startDate={unix_epoch_seconds} GET /input/getInputsLinkedModule/{moduleId} ``` **User avatar:** ``` GET /user/avatar → image/png (no-cache) GET /user/avatar?t={ms} → cache-bust variant ``` ### 4.4 Sensor Data (Inputs) **Batch input config:** ``` GET /inputs?inputId=id1%3Bid2%3Bid3 ``` Semicolons (`%3B`) separate multiple IDs. Returns full input objects with `expression`, `type`, `unit`, `module`, `index`. **All inputs for a module (most efficient for dashboard loads):** ``` GET /remote/module/getComputedSensorsData ?moduleId={moduleId} &startDate={iso8601} &endDate={iso8601} &forceRawOrComputed=computed ``` Returns array of input objects, each with `computedSensorData` ticks. Pass `forceRawOrComputed=raw` for raw ADC values. **Single input time-series:** ``` GET /remote/input/getComputedSensorData ?inputId={inputId} &startDate={iso8601} &endDate={iso8601} &ensurePrevTickForContinuity=true ``` Response: ```json { "id": "60ca42adad69dac1558f7c66", "name": "Plumbago", "type": 12, "unit": 10, "interval": 60, "computedSensorData": [ { "tickTimestamp": "2026-06-01T00:42:00.000Z", "value": 54.1875 } ] } ``` **Last tick only (fastest — no date range needed):** ``` GET /input/getLastTickFromInput?inputId={inputId} ``` Returns `{ "tick": { "input": "...", "value": , "timestamp": "...", "id": "..." } }`. Value is **raw** (pre-expression). Apply the input's `expression` to get the computed value. LRPS pH: `raw ÷ 100` → pH. LRPS temp: `raw ÷ 100` → °C. **Averages & CSV:** ``` GET /inputs/average?inputIds={id1},{id2}&startDate={iso8601}&endDate={iso8601} GET /input/getComputedInputIpxDataBetweenDatesAsCsv?inputId={id}&startDate={iso8601}&endDate={iso8601} ``` **Live pool sensor snapshot (Python):** ```python 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", {}).get("value") # pH: readings["pH"] / 100 → pH units # temp: readings["temp"] / 100 → °C ``` ### 4.5 Outputs ``` GET /outputs/modules?moduleIds={moduleId} POST /outputs/search {"query": {"module": {"$in": ["moduleId"]}}} POST /output/update GET /output/events?moduleId={moduleId} GET /output/events?outputId={outputId}&startDate={iso8601}&endDate={iso8601} GET /output/forecast?moduleId={moduleId} GET /output/forecast?outputId={outputId} ``` Response structure: `{ outputs: [{ id, module, index, name, flowRate: { value, unit } }] }` Flow rate unit: `1` = L/h, `2` = m³/h. ### 4.6 Programs ``` GET /programs/modules?moduleIds={moduleId} PUT /programs {"programs": [...], "module": "moduleId"} POST /programs create new program DELETE /programs PATCH /programs/name POST /programs/search {"query": {"id": {"$in": [...]}}} GET /programs/reset?moduleId={moduleId} POST /checkAvailableWindows check time conflicts before saving ``` **Program object:** ```json { "id": "...", "name": "Pelouse Terrasse", "module": "moduleId", "index": 0, "programmingType": "temporal", "weekDays": 127, "waterBudget": 100, "windows": [{ "start": 420, "end": 422, "on": 120, "off": 0 }] } ``` Window times are **minutes from midnight**. `on` / `off` are in **seconds**. `weekDays` 127 = every day (bitmask, bit 0 = Monday). **LRPC extended program fields** (pool controller): ```json { "programCharacteristics": { "programType": 4, "mode": 2, "onSpeed": 1, "speedSequence": 4368, "microFilteringTemperature": 5, "microFilteringDuration": 10, "microFilteringPeriod": 480, "frostFreeSpeed": 1 }, "windows": [{ "start": 300, "end": 360, "on": 3600, "off": 0, "runningDays": 127 }] } ``` `programType`: 4 = filtration, 2 = auxiliary/cyclic, 0 = disabled. ### 4.7 Users ``` GET /users-info/{userId}?includeUserGroups=true GET /users/{userId}/modules GET /users/{userId}/sites GET /users/{userId}/module-groups GET /users/{userId}/subscriptionModules GET /users/{userId}/checkSubscriptionSimcardStatus POST /users/search {"query": {...}, "projection": {...}} ``` **Grouped resources (paginated POST):** ``` POST /users/{userId}/module-groups → {moduleGroups, totalCount} POST /users/{userId}/agricultural-module-groups → {agriculturalModuleGroups, totalCount} POST /users/{userId}/watering-module-groups → {wateringModuleGroups, totalCount} POST /users/{userId}/customers → {customers, totalCount} POST /users/{userId}/associated → {users, totalCount} POST /users/{userId}/tags → {tags} ``` **Activity log:** ``` POST /users/{userId}/records { "limit": 10, "skip": 0, "filters": {"startDate": "2026-05-28T21:51:48.266Z", "showAllEvents": true}, "includeTotalCount": true, "sortColumn": "timestamp", "sortOrder": -1, "fields": {"category": 1, "label": 1, "module": 1, "timestamp": 1} } → {records: [...], totalCount: N} POST /users/{userId}/records/categories {"filters": {"startDate": "..."}, "includeCustomers": true} → [{category, displayCategory}] ``` ### 4.8 Module Notebook Full chronological history of manual commands, alerts, firmware updates, pool events: ``` GET /modules/{moduleId}/notebook?startDate={unix_epoch_seconds} ``` `startDate` is a **Unix timestamp in seconds** (not ISO 8601). Response: `{ "records": [...] }` sorted oldest-first. ```json { "category": "manual_command", "label": "...", "priority": "low", "state": "archived", "timestamp": "2026-06-04T21:00:00.000Z", "params": { "action": 2, "index": 1, "manualDuration": 1800 }, "input": "inputId" } ``` Known `category` values: `manual_command`, `pool`, `platform_alert`, `firmware_update`. For `pool` category: `params.poolAlertType` = `backwashReminder`, `backwashDone`, `poolLevelSensorCleaningReminder`, etc. For `manual_command`: `params` mirrors the linesControl action/index that triggered the event. Note: `manualDuration` in notebook is stored in **seconds** internally, regardless of the `time:"MM:SS"` format sent to the API. ### 4.9 Notifications & Journal ``` GET /journal/notifications GET /journal/counts GET /journal/warnings/accepted PUT /journal/acknowledge/all POST /canopy-sites/{siteId}/notifications/acknowledge-all ``` **Filtered site notifications:** ``` POST /canopy-sites/{siteId}/notifications {"limit": 1, "includeTotalCount": true, "filters": {"state": {"$in": ["unread", "process"]}}} → {records: [...]} ``` **Cross-site journal:** ``` POST /journal/notifications { "canopyIds": ["siteId"], "limit": 100, "skip": 0, "filters": {"priority": {"$in": ["critical", "high"]}, "state": {"$in": ["unread", "process"]}}, "projection": {...} } → {records: [...], total: N} ``` ### 4.10 Weather ``` GET /moduleGroups/getCanopyWeather ?location[latitude]={lat}&location[longitude]={lng} ``` Returns 3-day AccuWeather-style forecast. ### 4.11 Field Supervision ``` GET /canopy/{siteId}/field-supervisions/modules → {canopyModules: [{type, modules: [{serialNumber, name, outputs: [{id, index, name}]}]}]} GET /canopy/{siteId}/field-supervision/{supervisionId}/resume → {allPrograms: [...]} GET /moduleGroups/{siteId}/field-supervision/{supervisionId}/events/{startISO}/{endISO}?timezone=Europe/Paris → {events: [{eventId, moduleId, moduleName, identifier, name, start, end, flowRate, index}]} ``` Field supervision ID for Domicile: `643c5134347b27edbf38b173` ### 4.12 Satellite / NDVI Imagery Host: **`sentinel.mysolem.com`** (separate subdomain). ``` GET /moduleGroups/{siteId}/fencing/{timestampMs}/images-check → {availableImages: {available: true, reason: "ok"}} GET /moduleGroups/{siteId}/fencing/{timestampMs}/images → {imagesList: {images: [{name, urlRgb, urlSavi}]}} GET https://sentinel.mysolem.com/api/plot/{plotId}/image/{timestamp}/savi → PNG (vegetation index) GET https://sentinel.mysolem.com/api/plot/{plotId}/image/{timestamp}/rgb → PNG (true color) ``` Timestamp format: `YYYYMMDDTHHmmss`, e.g. `20220417T103631`. Domicile plot ID: `62f4efcb4a6dd563203558c3`, fencing timestamp: `1656939129360`. ### 4.13 Clusters & Daily Data ``` GET /clusters/{clusterId}/data GET /clusters/{clusterId}/modules GET /clusters/{clusterId}/programs PUT /clusters/{clusterId}/programs PATCH /clusters/{clusterId}/name PATCH /clusters/{clusterId}/flowrate DELETE /clusters/{clusterId} GET /garden/dailyData/{moduleId} GET /modules/heatmap-data?moduleIds={ids}&startDate=X&endDate=Y ``` ### 4.14 Pool-Specific (MyIndygo) ``` GET /user/pools → current user's pools (⚠ returns [] for API test account) GET /user/pools?userId={id} → another user's pools (IDOR — no ownership check) GET /maintenance-logs-data?moduleId={moduleId} ``` `maintenance-logs-data` returns `{ code, messages: [...] }`. Each message has `poolAlertType`, `priority`, `state`, `timestamp`, and a `pool` object with full pool config and `pool.status` (live sensor values, last pressure, last pH, etc.). **Pool admin (admin-only, 403 on standard account):** ``` GET /pools/admin POST /pools/admin/search POST /pools/admin/sendMessage GET /pools/admin/getMessages DELETE /pools/admin/dissociateAndRemove/{id} ``` ``` GET /teams/pools/{poolId}/membersAccessGranted → {teamMembersAlreadyGrantedPool: [...]} ``` --- ## 5. Mobile API — /api/ Endpoints Base URL: `https://qualif.mysolem.com` (same host, different path prefix). Auth: `Authorization: Bearer {token}` on all requests. ### 5.1 User ``` GET /api/getUser GET /api/getUserWithHisModules GET /api/getUserPools GET /api/getUserWateringModuleGroups → {"wateringModuleGroups": []} GET /api/getUserAgriculturalModuleGroups → {"agriculturalModuleGroups": []} GET /api/getCustomers → {"err": {"code": "user_is_not_professional"}} GET /api/users/{userId}/canopy-sites GET /api/users/{userId}/access-requests GET /api/users/{userId}/module-access-requests ``` `GET /api/getUserPools` is the only endpoint that exposes the pool record `5de53c1c7ed4c45c2cfe0688` via the API test account — the web platform `/user/pools` returns empty for that account. ### 5.2 Pool ``` GET /api/getPoolStatus?attributesToPopulate[]=modules&poolIdentifier={poolId} ``` Returns pool status with full modules array. Pool ID for Domicile: `5de53c1c7ed4c45c2cfe0688`. Pool config fields: `volume` (25 m³), `installationType`, `filteringType`, `waterTreatmentType`, `phRegulationMode`, `chlorineRegulationMode`, `heatingSystemType`, `coverType`, `cyanuricAcidRate`, `model`, `latitude`, `longitude`, `address`. ### 5.3 Modules ``` GET /api/getModuleWithHisUsers?module={moduleId} GET /api/getModuleInventory?module={gatewaySerial}&startDate={iso8601}&endDate={iso8601}&data=1 → {children: [...]} ← all child modules under this gateway with full config GET /api/getModuleBackups?module={moduleId} → array of saved program snapshots GET /api/getPrograms?id={moduleId} → full program objects including startTimesCycles, stationsDuration PUT /api/updateModule Content-Type: application/json {"module": {"id": "moduleId", "batteryVoltage": 77, "powerState": 0, "status": {...}}} ``` ### 5.4 Gateway-Routed Module Access All real-time commands go through the LRMB10 gateway. URL pattern: ``` /api/module/{gatewaySerial}/{action}/{childSerial6} ``` - `gatewaySerial` = full 16-char serial, e.g. `1000000705320001` - `childSerial6` = **last 6 hex chars** of the child module's serial (case-insensitive) **Domicile routing:** | Child | childSerial6 | Full serial | |---|---|---| | LRPC-F1DA27 | `F1DA27` | `150302F1DA270001` | | LR6AG-070917 | `070917` | `6606010709170001` | | LRMS-0721DC | `0721DC` | `7400040721DC0001` | | LR2IPECO-0C9282 | `0C9282` | `3802020C92820001` | | LRLEVEL-0D2ED9 | `0D2ED9` | `3A01020D2ED90001` | | LRPR-0D6800 | `0D6800` | — | **Read endpoints:** ``` GET /api/module/{gatewaySerial}/information ← gateway itself GET /api/module/{gatewaySerial}/information/{child6} ← child real-time info (LoRa roundtrip) GET /api/module/{gatewaySerial}/status/{child6} ← current output/input states GET /api/module/{gatewaySerial}/configuration/{child6} ← device configuration GET /api/module/{gatewaySerial}/programs/{child6} ← programs stored on device GET /api/module/{gatewaySerial}/inventory ← all children (= getModuleInventory) ``` Gateway information response example: ```json { "mtype": "10", "msn": "1000000705320001", "name": "LRMB10-070532 (Perso)", "vsoft": "6.5.0", "ble": "1.0.13", "ssid": "Freebox-2A5E5D", "bluetoothLink": true, "wifiLink": true, "ethernetLink": false, "modemLink": false, "up": 782800, "cdate": "2026-06-05T00:11:38+02:00" } ``` **Write endpoints:** ``` POST /api/module/{gatewaySerial}/status/{child6} Content-Type: application/json {"ag": [{"index": 0, "rainDelay": 0}, {"index": 1, "rainDelay": 0}]} → sets rain delay per output; response includes full device status POST /api/module/{gatewaySerial}/programs/{child6} Content-Type: application/json {"programs": [...], "programmationType": 1} → sends programs to device over LoRa POST /api/module/{gatewaySerial}/manual/{child6} Content-Type: application/json {...command body...} → sends a manual command to device over LoRa ``` ### 5.5 Sensor & Chart Data ``` GET /api/getInputs?id={moduleId} → all inputs for module with expression, expressionInverse, alert config GET /api/getComputedChartData ?inputId={inputId} &startDate={iso8601} &endDate={iso8601} &numberOfBar={N} &locale=fr → input data bucketed into N bars; includes computedSensorData array GET /api/getComputedOutputActivationHistory ?moduleId={moduleId} &outputIndex={0|1|2} &startDate={iso8601} &endDate={iso8601} &numberOfBar={N} → [{currentPeriodStartDate, currentPeriodEndDate, value}] value=1 means output was active during that period numberOfBar=1440 over 24h = 1 bar per minute ``` ### 5.6 Programs (Mobile) ``` GET /api/getPrograms?id={moduleId} PUT /api/updatePrograms Content-Type: application/json { "programmationType": 1, "programs": [{"id": "...", "name": "...", "waterBudget": 100, "windows": [...], "programCharacteristics": {...}}] } → {updatedPrograms: [...]} POST /api/library/program-templates {"limit": 10, "moduleId": "moduleId", "offset": 0} → {"programTemplates": []} ``` ### 5.7 Documents & Metadata ``` GET /api/getDocument?documentType=installation-guide&product-type={moduleType} → {"url": "https://qualif.mysolem.com/files/notices/{type}/solem/fr-installation-guide.pdf"} GET /api/getAdvertisingProfilePresets → [{name, ble: {windows: [...]}, loRa: {interval}}] Profiles: default, ecoMode, etc. GET /api/getAllBanishedSerialNumbers → ["120C0200750001", ...] Blacklisted/revoked device serial numbers. ``` ### 5.8 Reporting (App Telemetry) Called by the app after sending data to devices — informational, do not depend on responses. ``` POST /api/reportModuleDatasSent {"module": "moduleId"} POST /api/reportProgramsDatasSent {"programs": ["programId", ...], "module": "moduleId"} POST /api/reportManualCommandSent {"route": "http", "command": {...}, "id": "moduleId"} ``` ### 5.9 Push Notifications ``` POST /api/addApnsTokenToUser {"isSandbox": false, "topic": "com.mysolem.qualif", "user": "{userId}", "token": "{apnsToken}"} → {"code": "ok"} ``` ### 5.10 Web vs Mobile API Comparison | Feature | Web (cookie) | Mobile (Bearer) | |---|---|---| | Auth | Session cookie | OAuth2 Bearer | | Pool access | Admin-only / main account | `GET /api/getUserPools` | | Module routing | Direct by ID | Via gateway serial + childSerial6 | | Sensor data | `getComputedSensorData` (raw ticks) | `getComputedChartData` (bucketed) | | Output history | `output/events` | `getComputedOutputActivationHistory` | | Programs | `programs/modules` | `getPrograms` (richer fields) | | Module inventory | `modules/search` | `getModuleInventory` via gateway | | Program backups | — | `getModuleBackups` | --- ## 6. Device Control All control commands work on both `qualif.mysolem.com` and `qualif.myindygo.com`. All require session cookie. ### 6.1 Pool Controller (LRPC) — linesControl Used for LRPC (`lr-pc`), LRLEVEL (`lr-niv`), and other pool devices. ``` POST /remote/module/control Content-Type: application/json { "moduleSerialNumber": "150302F1DA270001", "linesControl": [ {"index": 0, "action": 1} ] } ``` `moduleSerialNumber` is the device's `serialNumber` field (not the MongoDB `id`). Multiple entries in `linesControl` can be sent in one call. **Action codes (verified from app mitmproxy captures + live testing):** | action | meaning | extra params | |---|---|---| | `0` | Stop pump (index 0 only) | — | | `1` | **Stop / cancel** any running command | — | | `2` | Timed impulse | `time`: `"MM:SS"` string (e.g. `"30:00"` = 30 min) | | `3` | Boost filtration | `time`: `"MM:SS"` | | `4` | Start indefinitely (runs until action 1) | — | | `5` | Pause N days (blocks manual + scheduled programs) | `manualDuration`: N (integer days) | | `11` | Backwash cycle | `time`: encoded duration, `speed`: pump speed level | **Important notes:** - Action 0 returns `{"erreurtraitement": 6}` if called on a timed output that's running. - To stop a running timed output: send `action 1`, or override with `action 2, time: "00:01"`. - Action 5 `manualDuration` is **days** (not seconds). Response includes `"pause": N` (days remaining). - **Response shows pre-command device state** — the new command takes effect on the next LoRa downlink window. **Response structure (LRPC):** ```json { "dialogTimeStamp": "2026-06-04T22:35:16Z", "ackTimestamp": 1780612516, "temperature": 19, "pool": [ {"index": 0, "time": "00:00", "tempRef": 7, "value": 0}, {"index": 1, "origin": 2, "time": "30:00", "value": 1, "info": ["man"]}, {"index": 2, "time": "00:00", "tempRef": 7, "value": 0} ], "sensorState": [ {"index": 0, "value": 2318}, {"index": 1, "value": 2147483647} ], "inputsAlerts": [], "radio": ["cmd"] } ``` `pool[n].value`: 1 = running, 0 = stopped. `pool[n].origin`: 2 = manual, 3 = boost. `pool[n].info`: `["man"]` = manual, `["boost"]` = boost mode. `pool[n].pause`: days remaining on a pause (only present when paused). `radio: ["cmd"]` = command queued for LoRa transmission. **Common examples:** ```bash # Start pool light (index 1) for 30 minutes curl -b cookies.txt -X POST https://qualif.myindygo.com/remote/module/control \ -H "Content-Type: application/json" \ -d '{"moduleSerialNumber":"150302F1DA270001","linesControl":[{"index":1,"action":2,"time":"30:00"}]}' # Stop pool light curl -b cookies.txt -X POST https://qualif.myindygo.com/remote/module/control \ -H "Content-Type: application/json" \ -d '{"moduleSerialNumber":"150302F1DA270001","linesControl":[{"index":1,"action":1}]}' # Boost filtration for 2 hours curl -b cookies.txt -X POST https://qualif.myindygo.com/remote/module/control \ -H "Content-Type: application/json" \ -d '{"moduleSerialNumber":"150302F1DA270001","linesControl":[{"index":0,"action":3,"time":"02:00"}]}' # Pause pool light for 3 days (disables manual + programs) curl -b cookies.txt -X POST https://qualif.myindygo.com/remote/module/control \ -H "Content-Type: application/json" \ -d '{"moduleSerialNumber":"150302F1DA270001","linesControl":[{"index":1,"action":5,"manualDuration":3}]}' # Stop fill valve (LRLEVEL, index 0) curl -b cookies.txt -X POST https://qualif.myindygo.com/remote/module/control \ -H "Content-Type: application/json" \ -d '{"moduleSerialNumber":"3A01020D2ED90001","linesControl":[{"action":0,"index":0}]}' ``` ### 6.2 Irrigation Valve (LR6AG) — sendManualModuleCommand Used for `lr-ag`, `lr-ip-eco`, and other irrigation modules. ``` POST /module/sendManualModuleCommand Content-Type: application/x-www-form-urlencoded ``` All patterns confirmed via mitmproxy captures. | Intent | Form params | |---|---| | Run station N minutes | `commandType=station&command={outputId}&commandParams[hours]=0&commandParams[minutes]=N` | | Run all stations N minutes | `commandType=station&command=allStations&commandParams[hours]=0&commandParams[minutes]=N` | | Cycling station (on/off intervals) | `commandType=station&command={outputId}&commandParams[hours]=1&commandParams[minutes]=0&commandParams[on]=1200&commandParams[off]=930&agriculturalModuleId={moduleId}` | | Stop specific station | `commandType=station&command={outputId}&commandParams=stop&agriculturalModuleId={moduleId}` | | Stop everything | `commandType=manualStop` | | Enable (no rain delay) | `commandType=status&command=on&commandParams=0` | | Disable / rain delay N days | `commandType=status&command=off&commandParams=N` | | Max rain delay | `commandType=status&command=off&commandParams=255` | | Run program | `commandType=program&command={programId}` | **Notes:** - `command` accepts an output ID string, `"allStations"`, or a program ID. - `agriculturalModuleId` is required for stop and cycling station commands. - `commandType=manualStop` takes no `outputIndex` or `commandParams`. **JSON body variant (also accepted):** ```json { "moduleSerialNumber": "6606010709170001", "commandType": "station", "outputIndex": 0, "command": null, "commandParams": {"hours": 0, "minutes": 15, "on": 0, "off": 0} } ``` Rain delay example (suspend station 0 indefinitely): ```json { "moduleSerialNumber": "6606010709170001", "commandType": "status", "command": "off", "commandParams": 255, "outputIndex": 0 } ``` ### 6.3 Flush Commands to Device Triggers the platform to push all queued commands to a site's modules immediately: ``` POST /canopy/{siteId}/sendModulesCommand Content-Type: application/json {} ``` Response: `202 Accepted` — `{ "message": "Modules command accepted for processing" }` Use after `PUT /programs` to force immediate LoRa push. ### 6.4 LoRa Data Push (Force Sync) ``` POST /modules/sendDataViaLoRaWAN POST /agriculturalModuleGroup/sendDataViaLoRaWAN POST /wateringModuleGroup/sendDataViaLoRaWAN ``` ### 6.5 Module Rename ``` GET /remote/module/name?moduleId={moduleId}&name={newName} ``` --- ## 7. MyIndygo — Pool Platform MyIndygo (`https://qualif.myindygo.com`) is the pool-focused frontend on the same backend. All endpoints from section 4 work identically. ### 7.1 Pool Auth Note The test API account (`arnaud.nelissen.api@icloud.com`) has different user IDs on each platform: - MySOLEM: `5de53bca7ed4c45c2cfe067f` - MyIndygo: `6a21c3db5ebe0fa5f7885460` The pool ID `5de53c1c7ed4c45c2cfe0688` is accessible via the mobile API (`GET /api/getUserPools`) or when logged in as the main account (`anelissen@solem.fr`) on the web. ### 7.2 LRPC Filtration Schedule (Domicile) Current program 0 (filtration, type 4), runs every day: | Window | Start | End | Duration | |---|---|---|---| | Morning | 05:00 | 06:00 | 1h | | Midday | 12:00 | 14:00 | 2h | | Evening | 22:00 | 23:00 | 1h | | **Total** | | | **4h/day** | Special features: - **Micro-filtration** (frost mode): activates every 480 min for 10 min when temp < 5°C, speed 1 - **Temperature-adaptive**: 2 schedules (96d + 31d/year) with 12 temperature thresholds adjusting run windows Program 1 (auxiliary, type 2): 21:30–22:30 active window, 5-min cycles — likely electrolyser or secondary treatment pump. ### 7.3 Pool Sensor Quick Reference | Sensor | Input ID | Unit | Notes | |---|---|---|---| | Water temp | `6480da07c2c0896da120eccc` | °C (raw ÷ 100) | Healthy: 20–28°C | | pH | `6480da07c2c0896da120eccd` | pH (raw ÷ 100) | Target: 7.2–7.6 | | ORP/Redox | `6480da07c2c0896da120ecce` | mV (raw) | Healthy: 650–750 mV | | Filter pressure | `6a17218be0b88ea3e9255251` | bar: `(1.5×raw−500)/1000` | Running: ~0.70–0.75 bar; off: ~0.08 bar | | Pool level switch | `6a174dd1e0b88ea3e925531e` | 0/1 | 0 = fill needed, 1 = OK | | Fill volume | `6a1c4b153d6c33ab843edc15` | L | Volume added per fill cycle | | Fill valve state | `6a1c4b153d6c33ab843edc16` | 0/1 | 1 = valve closed | ### 7.4 Fill System Logic ``` pool-level-sensor (float switch) reads 0 (low) → LRLEVEL (lr-niv) opens fill valve (output 0, Station 1) → edc15 input measures litres added → valve closes when pool-level-sensor returns 1 (OK) ``` Filter pressure (`LRPR`) can be used to detect filtration start/stop and dirty filter (pressure rises above ~0.80 bar when filter needs backwash). Backwash reminder fires every ~15 days. ### 7.5 MyIndygo Feature Flags Pool-specific flags returned by `GET /features` on `qualif.myindygo.com`: | Flag | Meaning | |---|---| | `ocedisWaterChemistryCorrection` | OCEDIS chemical dosing correction | | `enableBSTSubscriptions` | BST subscription management | | `enablePolytropicAPI` | Variable-speed pump (Polytropic) integration | | `enableGraphicalProgrammation` | Visual program schedule editor | | `enableNdviSatellite` | NDVI satellite imagery | | `enableEvapotranspiration` | ET-based automated watering | | `clusterTurfOpened` | Turf cluster watering mode | --- ## 8. Device Reference — Domicile ### 8.1 LRMB10-070532 — LoRa Gateway **ID**: `60ca4276ad69dac1558f7c61` | **Serial**: `1000000705320001` All other modules communicate through this gateway via LoRa radio. | Field | Value | |---|---| | Firmware | 6.5.0 | | BLE | 1.0.13 | | WiFi MAC | `10:52:1C:01:2D:F4` | | BT MAC | `C8:B9:61:07:05:32` | | WiFi SSID | `Freebox-2A5E5D` | | Links | WiFi ✓, Bluetooth ✓, Ethernet ✗, Modem ✗ | `remote/module/configuration` works directly (gateway is WiFi-connected, not LoRa-only). ### 8.2 LRPC-F1DA27 — Pool Controller **ID**: `6481bcea8782b78554c02bed` | **Serial**: `150302F1DA270001` | **Type**: `lr-pc` Controls filtration pump (index 0), pool light (index 1), and a third output (index 2, unused). Pool config: 25 m³, rectangular 6×3 m depth 1.4 m, outdoor, glass filter, stabilised chlorine tablets, automatic pH regulation, manual chlorine regulation. Programs: see section 7.2. Alert history: `GET /maintenance-logs-data?moduleId=6481bcea8782b78554c02bed` `remote/module/configuration` returns `module_not_reachable` — LoRa relay only. ### 8.3 LRPS-0565C0 — Pool Analyser **ID**: `6480da07c2c0896da120ecca` | **Serial**: `9400030565C00001` | **Type**: `lr-mas` Measures pool water temperature, pH, and ORP/redox. Battery at 60% — alerts fired Feb 2026. Calibration needed (flagged Mar 2026). | Input | ID | Type | Interval | |---|---|---|---| | Water temperature | `6480da07c2c0896da120eccc` | 5 | ~30 min | | pH | `6480da07c2c0896da120eccd` | 6 | varies | | ORP/Redox | `6480da07c2c0896da120ecce` | 7 | varies | No outputs — pure analyser. ### 8.4 LR6AG-070917 — Irrigation Valve Controller **ID**: `626984f038ec558c598bb6d5` | **Serial**: `6606010709170001` | **Type**: `lr-ag` 6 irrigation stations, 1 rain sensor input. Battery: 81%. Relay: LRMB10. All 6 programs reset on 2026-06-04 with no active windows. Stations 0 and 1 currently have 255-day rain delay (suspended indefinitely). | Station | Output ID | Index | Flow rate | |---|---|---|---| | Station 1 | `626984f038ec558c598bb6d8` | 0 | 30 m³/h | | Station 2 | `626984f038ec558c598bb6d9` | 1 | 14 m³/h | | Station 3 | `626984f038ec558c598bb6da` | 2 | 20 m³/h | | Station 4 | `626984f038ec558c598bb6db` | 3 | 1 m³/h | | Station 5 | `626984f038ec558c598bb6dc` | 4 | 38 L/h | | Station 6 | `626984f038ec558c598bb6dd` | 5 | 38 L/h | Rain sensor (input `626984f038ec558c598bb6d7`, type 2): 1 = rain, 0 = clear. ### 8.5 LRMS-0721DC — Sensor Station **ID**: `60ca42adad69dac1558f7c64` | **Serial**: `7400040721DC0001` | **Type**: `lr-ms` Pure sensor — no outputs, no programs. Battery: 78%. `relayIsLongPolling: true`. | Input | ID | Type | Interval | |---|---|---|---| | Plumbago (soil moisture) | `60ca42adad69dac1558f7c66` | 12 | 60 min | | Pelouse (soil moisture) | `60ca42adad69dac1558f7c67` | 12 | 60 min | | Air/soil temperature | `60ca42adad69dac1558f7c69` | 5 | 15 min | Moisture spikes (>10% jump within an hour) correlate with irrigation events — useful to infer watering history when `output/events` returns no records. ### 8.6 LR2IPECO-0C9282 — IP ECO Controller **ID**: `6516ca57b7c098e6807cb332` | **Serial**: `3802020C92820001` | **Type**: `lr-ip-eco` 2 irrigation stations + pulse flow meter. Battery: 89% primary, secondary battery: 3200 mV. All 3 programs have no active windows. | Input | ID | Type | |---|---|---| | Flow meter | `6516ca57b7c098e6807cb334` | 33 (flow, L) | Flow meter alternates: values are cumulative per minute, pattern suggests short irrigation bursts. `watering.state: 1` in remote state = currently active at last poll. ### 8.7 LRPR-0D6800 — Filter Pressure Sensor **ID**: `6a17218be0b88ea3e925524f` | **childSerial6**: `0D6800` | **Type**: `lr-pr` Pure pressure sensor. Battery: 94%. 1-minute reporting interval. Input `6a17218be0b88ea3e9255251`, type 8. Expression: `(1.5 × raw − 500) / 1000` → bar. Normal operating pressure: **0.70–0.75 bar** (pump running). Drop to ~0.08 bar = pump stopped. Rising pressure above ~0.80 bar = dirty filter — backwash needed. ### 8.8 pool-level-sensor — Float Level Switch **ID**: `6a174dd1e0b88ea3e925531c` | **Type**: `pool-level-sensor` Binary float switch, only transmits on state change (~11 events per month). Battery: **41% — needs replacement soon**. Cleaning reminder fired 2026-05-28. Input `6a174dd1e0b88ea3e925531e`, type 21. Raw passthrough: 0 = below threshold (fill needed), 1 = OK. ### 8.9 LRLEVEL-0D2ED9 — Pool Fill Valve Controller **ID**: `6a1c4b153d6c33ab843edc13` | **Serial**: `3A01020D2ED90001` | **Type**: `lr-niv` Controls pool fill valve automatically based on pool-level-sensor. Battery: 94%. | Input | ID | Type | Notes | |---|---|---|---| | Fill volume | `6a1c4b153d6c33ab843edc15` | 33 | L added per fill cycle; polynomial expression | | Level state | `6a1c4b153d6c33ab843edc16` | 149 | 1 = valve closed, 0 = open | Output `6a1c4b153d6c33ab843edc17` (Station 1, index 0) — fill valve. No scheduled program; triggered by level sensor. To query fill history: `GET /remote/input/getComputedSensorData?inputId=6a1c4b153d6c33ab843edc15` over wide date range, filter ticks where `value > 0`. --- ## 9. Write Endpoints Reference All mutation endpoints grouped by risk. All require session cookie unless noted. ### 9.1 Device Control (high impact — sends LoRa radio commands) ``` POST /remote/module/control pool controller linesControl (see section 6.1) POST /module/sendManualModuleCommand irrigation valve commands (see section 6.2) POST /canopy/{siteId}/sendModulesCommand flush commands to device POST /modules/sendDataViaLoRaWAN POST /agriculturalModuleGroup/sendDataViaLoRaWAN POST /wateringModuleGroup/sendDataViaLoRaWAN ``` ### 9.2 Programs (persistent — survives device reboot) ``` PUT /programs {"programs": [...], "module": "moduleId"} POST /programs create new program DELETE /programs PATCH /programs/name POST /program/update socket.io variant (immediate push to device) PUT /program/update REST variant POST /checkAvailableWindows check time conflicts before saving PUT /api/updatePrograms mobile API variant ``` ### 9.3 Module Configuration ``` POST /remote/module/configuration POST /remote/module/configuration/and/programs POST /input/update update input settings (name, thresholds, alerts) PUT /output/update update output settings (name, flow rate) GET /remote/module/name?moduleId=...&name=... rename via LoRa PUT /module/update PUT /module/update/address PUT /module/update/status POST /module/update/name POST /module/update/securityMode POST /remote/module/advertisingProfile POST /remote/module/dissociate {"relayMsn": "...", "modulesMsn": ["..."]} PUT /module/dissociate/{moduleId} ``` ### 9.4 Site / Pool Management ``` POST /canopy-sites PUT /pools/update/address POST /pools/admin/sendMessage POST /wateringModuleGroup/create PUT /wateringModuleGroup/update DELETE /wateringModuleGroup/delete POST /addAgriculturalGroupToUser PUT /teams/handleAccessGrantedPools POST /teams/members/add ``` ### 9.5 Journal / Notifications ``` PUT /journal/acknowledge/all PUT /journal/acknowledge/records POST /canopy-sites/{siteId}/notifications/acknowledge-all POST /journal/warnings/accepted ``` ### 9.6 Socket.io Write Calls These bypass the normal HTTP layer — not captured by Safari recordings: ``` socket.post /checkAvailableWindows socket.post /input/update socket.post /program/update socket.post /remote/module/configuration socket.post /remote/module/configuration/and/programs socket.post /remote/module/name socket.put /module/update socket.put /module/update/address socket.put /pools/update/address socket.put /program/update ``` --- ## 10. Reference Tables ### Module Types | Type string | Device name | Description | |---|---|---| | `lr-mb-10` | LRMB10 | LoRa gateway v10 | | `lr-mb-30` | LRMB30 | LoRa gateway v30 | | `lr-mb-100` | LRMB100 | LoRa gateway v100 | | `lr-ms` | LRMS | LoRa sensor station | | `lr-ms-eco` | LRMS_ECO | Eco sensor station | | `lr-ms-v2` | LRMS v2 | Sensor station v2 | | `lr-ms-rs485` | LRMSRS485 | RS485 sensor station | | `lr-ag` | LR6AG | LoRa agricultural valve (6 stations) | | `lr-mas` | LRPS | LoRa master valve / pool analyser | | `lr-pc` | LRPC | LoRa pool controller | | `lr-pc-vs` | LRPCVS | Pool controller with variable-speed pump | | `lr-ip-eco` | LR2IPECO | LoRa IP ECO (2-station + flow meter) | | `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-niv` | LRLEVEL | LoRa level controller (fill valve) | | `lr-pr` | LRPR | LoRa pressure sensor | | `lr-mv` | LRMV/LRPM1 | LoRa motor valve | | `lr-ol` | LROL4 | LoRa OL (lighting control) | | `lr-bst-100` | LRBST100 | BST 100 booster | | `lr-bst-react-4g` | LR-BST R4G | BST 4G reactive booster | | `pool-level-sensor` | POOL-LVL | Pool float level switch | | `wf-mb` | WiFi gateway | WiFi gateway | | `wf-is` / `bl-is` | WiFi/BT sensor | WiFi or Bluetooth sensor | | `bl-ip` / `bl-ip-v2` | BL6IP | Bluetooth IP | | `wf-ol` | WiFi OL | WiFi online | | `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 | | `ipx` | GEN_xxxx | IPX controller | ### Input Type Codes | Code | Meaning | Unit | Notes | |---|---|---|---| | `2` | TOR (on/off) | — | Rain sensor: 1=rain, 0=clear | | `5` | Air/soil temperature | °C | LRMS ambient sensor | | `6` | pH | — | Raw ÷ 100 = pH units | | `7` | ORP / Redox | mV | Raw = mV | | `8` | Pressure | bar | Expression: `(1.5×raw−500)/1000` | | `12` | Soil moisture | % (unit `10`) | LRMS capacitive probe | | `20` | Air moisture | — | | | `21` | Pool level TOR switch | 0/1 | 0=low/fill needed, 1=OK | | `33` | Volumetric / flow | L (unit `1`) | Cumulative, pulse-based | | `133` | Water temperature | °C (unit `2`) | LRPC/LRPS probe | | `149` | Fill valve / level state | 0/1 | LRLEVEL: 1=closed | ### linesControl Action Codes (LRPC) | action | meaning | params | verified | |---|---|---|---| | `0` | Stop pump (index 0 only) | — | Yes (LRLEVEL) | | `1` | Stop / cancel any running command | — | Yes (app capture) | | `2` | Timed impulse | `time: "MM:SS"` | Yes (app capture + live) | | `3` | Boost filtration | `time: "MM:SS"` | Yes (Safari capture) | | `4` | Start indefinitely | — | Yes (notebook pattern) | | `5` | Pause N days | `manualDuration: N` (days) | Yes (app capture) | | `11` | Backwash cycle | `time`, `speed` | Partial (JS analysis) | --- ## 11. Security Findings Assessment from black-box analysis. All findings verified on `qualif.mysolem.com`. Reported for awareness; not exploited beyond authorized testing scope. ### Critical | # | Endpoint | Finding | |---|---|---| | 1 | `POST /modules/search` | Empty query returns **4,607 modules** with name, serial, MAC, firmware, GPS — no ownership enforcement (BOLA) | | 2 | `POST /users/search` | Returns full user documents including **password hashes** (unsalted SHA-256), **active reset tokens** (direct account takeover), APNs/FCM push tokens — BOLA | | 3 | `POST /outputs/search` + `POST /programs/search` | Empty query returns 12,034 outputs and 6,073 programs platform-wide — no ownership enforcement | | 4 | `GET /remote/module/configuration` | Returns WiFi SSID, MACs, firmware for **any module ID** — no ownership check | | 5 | `GET /remote/input/getComputedSensorData` | Any `inputId` accepted — full sensor history exfiltration across tenants | | 6 | `POST /module/sendManualModuleCommand` | Takes `moduleSerialNumber` (obtainable from #1) — likely no ownership check, unconfirmed | ### High | # | Finding | |---|---| | 7 | Google Maps API key `AIzaSyDh8fCC9vTEuN9eKewxgqWXGP9ENb5an1c` hard-coded in HTML, no referrer restriction | | 8 | Session cookie missing `SameSite` attribute → CSRF possible on state-changing endpoints | | 9 | No rate limiting on `POST /login` — credential stuffing uninhibited | | 10 | MongoDB ObjectIds are sequentially guessable (timestamp + machine + counter), accelerating enumeration | ### Medium / Low | # | Finding | |---|---| | 11 | `Access-Control-Allow-Origin: ` (empty string) — CORS misconfiguration | | 12 | No `Content-Security-Policy` header on React frontend | | 13 | `X-Powered-By: Sails ` on every response — unnecessary fingerprinting | | 14 | Qualification environment appears to use live production data (real GPS, real addresses, live telemetry) | | 15 | Input IDs embedded as `data-input-id` HTML attributes — completes enumeration chain without guessing |