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>
48 KiB
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
- Platforms & Architecture
- Authentication
- Key IDs — Domicile Site
- Web API — Endpoints
- Mobile API — /api/ Endpoints
- Device Control
- MyIndygo — Pool Platform
- Device Reference — Domicile
- Write Endpoints Reference
- Reference Tables
- 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.
# 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:
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:
{
"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:
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:
{
"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": <raw>, "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):
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:
{
"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):
{
"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.
{
"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.1000000705320001childSerial6= 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:
{
"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 withaction 2, time: "00:01". - Action 5
manualDurationis 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):
{
"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:
# 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:
commandaccepts an output ID string,"allStations", or a program ID.agriculturalModuleIdis required for stop and cycling station commands.commandType=manualStoptakes nooutputIndexorcommandParams.
JSON body variant (also accepted):
{
"moduleSerialNumber": "6606010709170001",
"commandType": "station",
"outputIndex": 0,
"command": null,
"commandParams": {"hours": 0, "minutes": 15, "on": 0, "off": 0}
}
Rain delay example (suspend station 0 indefinitely):
{
"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 <sailsjs.org> 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 |