WomoNET Protocol

Version 1

The WomoNET network uses MQTT as the primary protocol for communication between various devices. This setup enables efficient and flexible data exchange within the network.

Structure

The MQTT topics in the WomoNET network are systematically organized into the following main categories:

c (config)

conf (configuration)

This topic stores configuration settings, including user interface preferences, device parameters, and other important configurations. The key feature of this topic is that settings are persistent, meaning they remain intact even after a system reboot.

  • ui: Contains UI layout from the WomoNET.app. Layouts are written as JSON for every available layout
  • driver: Can be used to store configurations for drivers

dev (virtual devices, auto-discovery)

This topic is reserved for drivers to publish their auto-discovery configuration. The configuration data for device discovery is defined here, as described in detail below.

s (state)

Each MQTT client within the WomoNET network has a dedicated state topic where it publishes messages reflecting its current state or data. All data has to be human-readable UTF-8 data. Numeric values have to be sent as UTF-8 string. Decimal point is . (dot). We parse with Dart’s double.tryParse.

State updates and commands should use QoS 1.

Structure

The state tree is structured as follows: s/<client_id>/<device_id>/<object_id>/[set/]<state/value/command>, whereas:

  • client_id is the MQTT client ID
  • device_id is the identifier of the virtual device
  • object_id is the object unit of a type’s value
  • set (optional) is a constant which is only provided if this is a command
  • state/value/command is the actual state, value or command

The structure may be different. Use the auto discovery mechanism to find the actual path for the needed state/value/command.

Open Questions

  • TODO Errorhandling: We could introduce s/error/<dev-id> for error messages
  • TODO Devices goes Offline: Last Will Messages for clients. Actively push availability for groups? Ideas:
    • Delete retained Auto-Discovery messages
    • available_t for every auto-discovery type

Auto-discovery

The configuration data needs to be published as MQTT messages in a specific JSON format. Each entity requires a unique topic name and a JSON configuration. The topic format is c/dev/<client_id>/<device_id>/<type>/<entity>, where:

  • <client_id> is the MQTT client id (globally unique)
  • <device_id> is an identifier for the virtual device (driver level). This id has to be unique for the client to avoid collisions
  • <type> is the type of entity. See below for available types
  • <entity> is the name of the entity. It has to be unique across all entities for the given type of this virtual device

When publishing auto discovery configs, the retain flag has to be set for the MQTT message.

Auto discovery is organized different to Home Assistant: We use dev_id and then type instead the other way around. This enables us to use WidgetGroups in the app for each dev_id.

Available Types

toggle

A switchable unit which can be turned on or off.

NameDescription
state_tTopic holding the current state, only true and false are allowed
cmd_tCommand topic for setting the toggle’s state. false, 0, off (all case insensitive) are interpreted as false, everything else is interpreted as true. If allow_timed is true, setting this value will cancel any running timer
allow_timedtrue or false, representing whether the value can be triggered in a timed manner by writing a CSV string representing the state and the duration it should be held in milliseconds (e.g., true,5000, setting the output on for 5000 milliseconds). If the timed state is already the requested state (e.g true,5000 is sent, but the state is already true) the command is ignored

actuator

An actuator which can be switched in two directions e.g. push/pull, forward/reverse and also has a neutral stop state. Important: The driver has to make sure to automatically stop the motion, when no more valid messages are received.

NameDescription
state_tTopic holding the numeric value representing the actuator’s state. Can be 1 (push/forward), 0 (neutral/stop), or -1 (pull/reverse)
cmd_tCommand topic for setting the actuator’s state. Can be 1 (push/forward), 0 (neutral/stop), or -1 (pull/reverse). If allow_timed is true, setting this value will cancel any running timer
allow_timedOptional (defaults to false). true or false, representing whether the value can be triggered in a timed manner by writing a CSV string representing the state and the duration it should be held in milliseconds (e.g., -1,5000, setting the direction to pull on for 5000 milliseconds). If the timed state is already the requested state (e.g -1,5000 is sent, but the state is already -1), the command is ignored

binary

A boolean sensor with states on and off.

NameDescription
state_tTopic holding the current state, only true and false are allowed

sensor

A sensor publishing a value.

NameDescription
state_tTopic holding the value. The value is UTF-8 (e.g 42.1234 or heating)
unit_of_measurementOptional. Specifies the unit of the sensor’s value for display purposes. Examples include °C for temperature, % for humidity, m/s for speed, kWh for energy, etc. If not provided, the value is treated as unitless. Use standardized units (SI) whenever possible to ensure compatibility

mode

Selectable options for a dropdown.

NameDescription
state_tTopic holding the current mode. The value is a UTF-8 string representing the current selection (e.g. heat, cool, auto, etc.)
cmd_tTopic used to send commands to change the mode. The value should be a UTF-8 string corresponding to one of the selectable options (e.g. heat, cool, off, etc.). NOTE: for translations, we could define common english options and provide translations via a lookup table for them.
optionsList of available modes for the dropdown, provided as a JSON array of strings (e.g. ["off", "heat", "cool", "auto"]). User interfaces should respect the order of the options

slider

A controllable level value (slider in UI), which can be changed between a min and max value with a specified number of steps, e.g. for controlling heater/cooler temperatures, light dimmers, power consumption, charger output, …

NameDescription
state_tTopic holding the current value
cmd_tTopic used to send commands for setting the target value
min_valueThe minimum value for the slider
max_valueThe maximum value for the slider
step_sizeThe size of a step, e.g. 0.5

Example Structure

c/
├── conf/
│   ├── ui/
│   │   ├── 1 = "{ layout JSON }"
│   │   ├── main = "{ layout JSON }"
│   │   └── heater_only = "{ layout JSON }"
│   └── driver/
│       ├── core-gpio = "{ driver configuration JSON }"
│       └── core-adc = "{ driver configuration JSON }"
└── dev/
    ├── womonet-core-123.../
    │   ├── adc/
    │   │   └── sensor/
    │   │       ├── vin0 = "{'unit_of_measurement': 'V', 'state_t': 's/womonet-core-123.../adc/0/state'}"
    │   │       ├── vin1 = "{'unit_of_measurement': 'V', 'state_t': 's/womonet-core-123.../adc/1/state'}"
    │   │       └── ...
    │   ├── extrelay/
    │   │   └── toggle/
    │   │       ├── relay0 = "{'state_t': 's/womonet-core-123.../extrelay/0/state', 'cmd_t': 's/womonet-core-123.../extrelay/0/set/state', 'allow_timed': true}"
    │   │       ├── relay1 = "{'state_t': 's/womonet-core-123.../extrelay/1/state', 'cmd_t': 's/womonet-core-123.../extrelay/1/set/state', 'allow_timed': true}"
    │   │       └── ...
    │   ├── truma-d4e-651.../
    │   │   ├── mode/
    │   │   │   └── heat = "{'state_t': 's/womonet-core-123.../truma-d4e-651.../heat/state', 'cmd_t': 's/womonet-core-123.../truma-d4e-651.../heat/set/state', 'options': '[\"off\", \"heating\", \"venting\"]'}"
    │   │   ├── sensor/
    │   │   │   ├── current_temp = "{'state_t': 's/womonet-core-123.../truma-d4e-651.../current_temp/state'}"
    │   │   │   └── boiler_temp = "{'state_t': 's/womonet-core-123.../truma-d4e-651.../boiler_temp/state'}"
    │   │   └── slider/
    │   │       └── target_temp = "{'state_t': 's/womonet-core-123.../truma-d4e-651.../target_temp/state', 'cmd_t': 's/womonet-core-123.../truma-d4e-651.../target_temp/set/state', 'min_value': 16, 'max_value': 36, 'step_size': 0.5}"
    │   └── ...
    └── womonet-power-524.../
        ├── toggle/
        │   └── pump = "{'state_t': 's/womonet-power-524.../pump/state', 'cmd_t': 's/womonet-power-524.../pump/set/state'}"
        ├── actuator/
        │   ├── step = "{'state_t': 's/womonet-power-524.../step/state', 'cmd_t': 's/womonet-power-524.../step/set/state', 'allow_timed': true}"
        │   ├── awning = "{'state_t': 's/womonet-power-524.../awning/state', 'cmd_t': 's/womonet-power-524.../awning/set/state'}"
        │   ├── valve0 = "{'state_t': 's/womonet-power-524.../valve0/state', 'cmd_t': 's/womonet-power-524.../valve0/set/state', 'allow_timed': true}"
        │   └── valve1 = "{'state_t': 's/womonet-power-524.../valve1/state', 'cmd_t': 's/womonet-power-524.../valve1/set/state', 'allow_timed': true}"
        └── ...

s/
├── womonet-core-123.../
│   ├── adc/
│   │   ├── 0/
│   │   │   └── state = "1.33"
│   │   ├── 1/
│   │   │   └── state = "0.452"
│   │   └── ...
│   ├── extrelay/
│   │   ├── 0/
│   │   │   ├── state = "ON"
│   │   │   └── set/
│   │   │       └── state (writable only, no retain)
│   │   ├── 1/
│   │   │   ├── state = "OFF"
│   │   │   └── set/
│   │   │       └── state (writable only, no retain)
│   │   └── ...
│   ├── truma-d4e-651.../
│   │   ├── heat/
│   │   │   ├── state = "heating"
│   │   │   └── set/
│   │   │       └── state (writable only, no retain)
│   │   ├── current_temp/
│   │   │   └── state = "18.62"
│   │   └── target_temp/
│   │       ├── state = "22.0"
│   │       └── set/
│   │           └── state (writable only, no retain)
│   └── ...
└── womonet-power-524.../
    ├── pump/
    │   ├── state = "ON"
    │   └── set/
    │       └── state (writable only, no retain)
    ├── step/
    │   ├── state = "0"
    │   └── set/
    │       └── state (writable only, no retain)
    └── ...

Cloud Connectivity

For cloud connectivity, a service is syncing the desired topics (states/) between the local and a cloud broker. This is only done when a sub topic in sync_requested exists. Each client connecting to the cloud broker writes its name into the sync_requested topic and defines a Last Will Message to delete it.

When the last client deletes its sync topic, the broker in the cloud deletes all state topics, after a predefined timeout. (Maybe by not accepting the retain flag)

An active connection between the local and cloud broker is signaled by a flag synced (with a timestamp of the last message from the local broker.)

All states should be synced in a given timeframe to ensure consistency between local and cloud environments.