# Watermaker MQTT API Reference Reference for UI/dashboard developers integrating with the watermaker system over MQTT. ## Connection | Parameter | Default | Environment Variable | |-----------|---------|---------------------| | Broker host | `198.18.4.108` | `MQTT_BROKER_HOST` | | Broker port | `1883` | `MQTT_BROKER_PORT` | | SSL/TLS | `false` | `MQTT_BROKER_SSL` | | Topic prefix | `watermaker` | `MQTT_TOPIC_PREFIX` | All topics below use the default `watermaker` prefix. ### Availability (Last Will and Testament) Subscribe to `watermaker/availability` to detect when the watermaker API is online or offline. | Value | Meaning | |-------|---------| | `online` | API is connected and publishing | | `offline` | API disconnected (set automatically by broker via LWT) | This topic is **retained** -- you receive the last known state immediately on subscribe. --- ## Data Topics (Server to Dashboard) Real-time PLC data. Payloads are JSON. ### `watermaker/status` System status. Published when status changes (mode, step, operating mode, valve positioning, or active timer changes). **Retained: Yes | QoS: 0** ```json { "system": { "mode": 7, "mode_name": "DTS", "operating_mode": "dts", "status": 3, "status_name": "Running", "valve_positioning": false, "active_timer": null }, "connection": { "connected": true, "last_update": "2026-01-04T10:30:00Z" } } ``` | Field | Type | Description | |-------|------|-------------| | `system.mode` | int | Raw R1000 mode register | | `system.mode_name` | string | Human-readable mode name | | `system.operating_mode` | string | `idle`, `single_pass`, `double_pass`, `dts`, `fw_flush` | | `system.status` | int | Production step (R1036): 0=Idle, 1=Prime, 2=Init, 3=Run, 4=Stop, 5=Flush | | `system.status_name` | string | Human-readable step name | | `system.valve_positioning` | bool | True during valve positioning phase | | `system.active_timer` | object/null | Currently active timer info | | `connection.connected` | bool | PLC connection status | --- ### `watermaker/sensors` Sensor readings. Published every 1-2s during operation, every 60s when idle. **Retained: No | QoS: 0** ```json { "pressure": { "feed": 45.2, "membrane": 820.5, "product": 12.3 }, "temperature": { "feed": 75.4, "system": 78.2 }, "flow": { "brine": 2.8, "product_1": 1.2, "product_2": 1.0 }, "quality": { "ppm": 245 } } ``` | Field | Type | Unit | Description | |-------|------|------|-------------| | `pressure.feed` | float | PSI | Feed water pressure | | `pressure.membrane` | float | PSI | Membrane pressure | | `pressure.product` | float | PSI | Product water pressure | | `temperature.feed` | float | F | Feed water temperature | | `temperature.system` | float | F | System temperature | | `flow.brine` | float | GPM | Brine flow rate | | `flow.product_1` | float | GPM | First pass product flow | | `flow.product_2` | float | GPM | Second pass product flow | | `quality.ppm` | int | PPM | Total dissolved solids | --- ### `watermaker/timers` Timer values. Published every polling cycle. **Retained: No | QoS: 0** ```json { "production_timer": { "priming": 165.5, "initialize": 6553.5, "ppm_stabilization": 6553.5, "stop": 6553.5, "flush": 6553.5, "sw_valve_positioning": 6553.5, "dts_valve_positioning": 6553.5, "fwf": 6553.5 }, "fwf_timer": { "countdown": 6553.5 } } ``` A value of `6553.5` indicates the timer is inactive. --- ### `watermaker/counters` Water production counters. Published every 30s during production, 60s when idle. **Retained: Yes | QoS: 0** ```json { "single_pass_since_last": 125.4, "double_pass_since_last": 0.0, "dts_since_last": 450.2, "total_product": 12500.8, "total_brine": 25001.6 } ``` | Field | Type | Unit | Description | |-------|------|------|-------------| | `single_pass_since_last` | float | gallons | Single pass production since last reset | | `double_pass_since_last` | float | gallons | Double pass production since last reset | | `dts_since_last` | float | gallons | DTS production since last reset | | `total_product` | float | gallons | Lifetime product water total | | `total_brine` | float | gallons | Lifetime brine total | --- ### `watermaker/outputs` Digital output states. Published every polling cycle. **Retained: Yes | QoS: 0** ```json { "low_pressure_pump": 1, "high_pressure_pump": 1, "boost_pump": 0, "product_solenoid_1": 1, "product_solenoid_2": 0, "brine_solenoid": 1 } ``` Values: `1` = on, `0` = off. --- ### `watermaker/tank_update` Fresh water tank levels from Victron Venus OS. Published every 10 seconds. **Retained: Yes | QoS: 0** ```json { "connected": true, "portal_id": "abc123", "tanks": { "bow": { "name": "Water Bow", "level_percent": 72.5, "capacity_gallons": 90.0, "gallons_stored": 65.25, "last_update": 1704361800.0 }, "stern": { "name": "Water Stern", "level_percent": 45.0, "capacity_gallons": 90.0, "gallons_stored": 40.5, "last_update": 1704361800.0 } }, "total_gallons": 105.75, "total_capacity": 180.0, "total_percent": 58.75, "timestamp": "2026-01-04T10:30:00" } ``` --- ## Event Topics (Server to Dashboard) One-time event notifications. Payloads are JSON. ### `watermaker/production/started` Published when production begins. **Retained: No | QoS: 1** ```json { "mode": "dts", "timestamp": "2026-01-04T10:30:00" } ``` ### `watermaker/production/stopped` Published when production ends. **Retained: No | QoS: 1** ```json { "mode": "dts", "timestamp": "2026-01-04T10:30:00" } ``` ### `watermaker/production/limit` Production limit tracking update. Published every cycle during active production. **Retained: No | QoS: 0** ```json { "is_tracking": true, "mode": "dts", "run_type": "fill", "gallons_produced": 23.5, "elapsed_seconds": 720, "gallon_limit": 50.0, "time_limit_seconds": null, "progress_percent": 47.0, "eta_seconds": 800, "flow_rate_gpm": 2.1, "tank_fill": { "bow_level": 72.5, "stern_level": 45.0, "total_remaining_gallons": 64.25, "target_level": 90, "eta_seconds": 1830 } } ``` ### `watermaker/production/limit_reached` Published when a production limit is reached. **Retained: No | QoS: 1** ```json { "type": "gallon", "value": 50.0, "reason": "Gallon limit of 50.0 gallons reached", "timestamp": "2026-01-04T11:00:00" } ``` --- ### `watermaker/verification/state_verified` Published when a predicted state transition was correct. **Retained: No | QoS: 1** ```json { "command_type": "start", "verification_time_ms": 150.25, "timestamp": "2026-01-04T10:30:00Z" } ``` ### `watermaker/verification/state_correction` Published when the actual state differs from the prediction. **Retained: No | QoS: 1** ```json { "expected": { "status": 0, "status_name": "Valve Positioning", "operating_mode": "dts" }, "actual": { "status": 1, "status_name": "Priming", "operating_mode": "dts" }, "command_type": "start", "timestamp": "2026-01-04T10:30:00Z" } ``` ### `watermaker/verification/timer_correction` Published when a timer drifts more than 2 seconds from its expected value. **Retained: No | QoS: 0** ```json { "timer_id": "priming", "actual_value": 165.5, "expected_value": 168.0, "drift_seconds": 2.5, "timestamp": "2026-01-04T10:30:00Z" } ``` --- ### `watermaker/remote_change` Published when a state change is detected from the physical HMI panel (not from API or dashboard). **Retained: No | QoS: 1** ```json { "type": "remote_start", "previous_step": 0, "current_step": 1, "previous_mode": null, "current_mode": "single_pass", "message": "Production started externally (single_pass)", "timestamp": "2026-01-04T10:30:00" } ``` | `type` Value | Description | |-------------|-------------| | `remote_start` | Production started from HMI | | `remote_stop` | Production stopped from HMI | | `remote_skip` | Phase skipped from HMI | | `remote_mode_change` | Operating mode changed from HMI | | `remote_step_change` | Step changed from HMI | | `flush_complete` | Flush completed | --- ### `watermaker/consumable_warning` Published when a consumable approaches or exceeds its replacement limit. **Retained: No | QoS: 1** ```json { "item": "pre_filter", "name": "Pre-Filter", "level": 85.2, "remaining": 14.8, "unit": "gallons", "severity": "warning", "timestamp": "2026-01-04T10:30:00" } ``` | `severity` | Trigger | |-----------|---------| | `warning` | 80%+ of lifespan consumed | | `critical` | 100%+ of lifespan consumed | --- ### `watermaker/alarm` Published when a PLC alarm is detected. **Retained: No | QoS: 1** ```json { "alarm_code": "HIGH_PRESSURE", "message": "High pressure alarm triggered", "timestamp": "2026-01-04T10:30:00Z" } ``` ### `watermaker/error` Published on system errors. **Retained: No | QoS: 1** ```json { "message": "PLC communication timeout", "timestamp": "2026-01-04T10:30:00" } ``` --- ## Command Topics (Dashboard to Server) Publish to these topics to control the watermaker. Commands are only processed when `MQTT_COMMANDS_ENABLED=true` (disabled by default for safety). Responses are published to `watermaker/command/response` with QoS 1. ### `watermaker/command/start` Start a watermaker production sequence. ```json { "mode": "dts", "run_type": "fill", "gallon_limit": 50.0, "time_limit": 3600, "expected_gallons": 45.0 } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `mode` | string | **Yes** | `single_pass`, `double_pass`, `dts`, or `fw_flush` | | `run_type` | string | No | `fill`, `gallons`, `timer`, `boat_wash`, or `manual` | | `gallon_limit` | float | No | Max gallons before auto-stop (not valid for `fw_flush`) | | `time_limit` | int | No | Max seconds before auto-stop | | `expected_gallons` | float | No | Pre-safety-margin estimate for Fill mode | **Mode descriptions:** | Mode | Description | |------|-------------| | `single_pass` | Seawater through single membrane | | `double_pass` | Seawater through both membranes | | `dts` | Dockside Treatment System (freshwater source) | | `fw_flush` | Fresh water flush (maintenance cycle) | **Run type descriptions:** | Run Type | Description | |----------|-------------| | `fill` | Run until tanks reach target level (uses `expected_gallons`) | | `gallons` | Run until `gallon_limit` is reached | | `timer` | Run until `time_limit` is reached | | `boat_wash` | Short run for deck wash water | | `manual` | Manual run, no auto-stop | ### `watermaker/command/stop` Stop current production. No payload required. ```json {} ``` The API automatically selects the correct stop command based on the current production step (priming/init stop, running stop, or flush stop). ### `watermaker/command/skip` Skip the current phase. No payload required. Only valid during priming (step 1) or initializing (step 2). ```json {} ``` ### `watermaker/command/cancel_limit` Cancel production limit tracking without stopping production. No payload required. ```json {} ``` ### `watermaker/command/response` Responses to commands are published here. **Success:** ```json { "success": true, "command": "start", "mode": "dts", "message": "DTS sequence started successfully", "predicted_state": { "system": { "status": 0, "status_name": "Valve Positioning", "operating_mode": "dts" } }, "timestamp": "2026-01-04T10:30:00Z" } ``` **Error:** ```json { "success": false, "error": "INVALID_MODE", "message": "Invalid mode. Valid modes: single_pass, double_pass, dts, fw_flush", "timestamp": "2026-01-04T10:30:00Z" } ``` --- ## Update Frequencies | Topic | Idle | Operating | Notes | |-------|------|-----------|-------| | `sensors` | 60s | 1-2s | Every 60s when idle for monitoring; spread polling when operating | | `timers` | 5s | 1-2s | Spread polling alternates even/odd seconds | | `status` | On change | On change | Only published when fields change | | `counters` | 60s | 30s | | | `outputs` | 5s | 1-2s | | | `tank_update` | 10s | 10s | From Victron Venus OS | | `production/limit` | -- | Every cycle | Only during active production | --- ## WebSocket to MQTT Migration For UI developers migrating from Socket.IO to MQTT. ### Event Mapping | Socket.IO Event | MQTT Topic | Retained | |-----------------|------------|----------| | `status` | `watermaker/status` | Yes | | `sensors` | `watermaker/sensors` | No | | `timers` | `watermaker/timers` | No | | `counters` | `watermaker/counters` | Yes | | `outputs` | `watermaker/outputs` | Yes | | `state_verified` | `watermaker/verification/state_verified` | No | | `state_correction` | `watermaker/verification/state_correction` | No | | `timer_correction` | `watermaker/verification/timer_correction` | No | | `remote_change` | `watermaker/remote_change` | No | | `production_started` | `watermaker/production/started` | No | | `production_stopped` | `watermaker/production/stopped` | No | | `production_limit` | `watermaker/production/limit` | No | | `limit_reached` | `watermaker/production/limit_reached` | No | | `tank_update` | `watermaker/tank_update` | Yes | | `consumable_warning` | `watermaker/consumable_warning` | No | | `alarm` | `watermaker/alarm` | No | | `error` | `watermaker/error` | No | ### Key Differences from WebSocket - **Retained messages** replace the on-connect full state push. New MQTT subscribers automatically receive the last known value for retained topics (status, counters, outputs, tank_update). - **No explicit connect/subscribe handshake.** Subscribe to the topics you need. Use `watermaker/#` to receive everything. - **Wildcard subscriptions** are supported: `watermaker/production/#` for all production events, `watermaker/verification/+` for all verification events. - **Commands** go through MQTT publish instead of REST POST endpoints. The payload and validation logic are identical. - **Availability monitoring** via `watermaker/availability` (retained LWT) replaces Socket.IO connect/disconnect events. ### Quick Start (JavaScript with MQTT.js) ```javascript import mqtt from 'mqtt'; const client = mqtt.connect('mqtt://198.18.4.108:1883'); client.on('connect', () => { client.subscribe('watermaker/#'); }); client.on('message', (topic, message) => { const data = JSON.parse(message.toString()); switch (topic) { case 'watermaker/status': updateStatus(data); break; case 'watermaker/sensors': updateSensors(data); break; case 'watermaker/timers': updateTimers(data); break; case 'watermaker/counters': updateCounters(data); break; case 'watermaker/tank_update': updateTanks(data); break; case 'watermaker/availability': updateConnectionStatus(message.toString()); break; } }); // Start production function startProduction(mode, options) { client.publish('watermaker/command/start', JSON.stringify({ mode: mode, run_type: options.run_type, gallon_limit: options.gallon_limit, time_limit: options.time_limit, expected_gallons: options.expected_gallons })); } // Listen for command responses client.subscribe('watermaker/command/response'); ```