Files
venus/watermaker/MQTT_API.md
dev 9756538f16 Initial commit: Venus OS boat addons monorepo
Organizes 11 projects for Cerbo GX/Venus OS into a single repository:
- axiom-nmea: Raymarine LightHouse protocol decoder
- dbus-generator-ramp: Generator current ramp controller
- dbus-lightning: Blitzortung lightning monitor
- dbus-meteoblue-forecast: Meteoblue weather forecast
- dbus-no-foreign-land: noforeignland.com tracking
- dbus-tides: Tide prediction from depth + harmonics
- dbus-vrm-history: VRM cloud history proxy
- dbus-windy-station: Windy.com weather upload
- mfd-custom-app: MFD app deployment package
- venus-html5-app: Custom Victron HTML5 app fork
- watermaker: Watermaker PLC control UI

Adds root README, .gitignore, project template, and per-project
.gitignore files. Sensitive config files excluded via .gitignore
with .example templates provided.

Made-with: Cursor
2026-03-16 17:04:16 +00:00

15 KiB

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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "mode": "dts",
  "timestamp": "2026-01-04T10:30:00"
}

watermaker/production/stopped

Published when production ends.

Retained: No | QoS: 1

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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

{
  "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.

{
  "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.

{}

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).

{}

watermaker/command/cancel_limit

Cancel production limit tracking without stopping production. No payload required.

{}

watermaker/command/response

Responses to commands are published here.

Success:

{
  "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:

{
  "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)

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');