MQTT API reference
The novolto P2300(G)/P3000(G) and Sense provide an MQTT API for interacting with the device, including receiving sensor data, sending commands and changing configuration.
API versioning
Section titled “API versioning”The device provides the version of the API in the {base_topic}/mqtt/status topic as a major.minor version number to allow clients to detect the version of the API in use.
The major version is incremented on any breaking changes.
The minor number is incremented on non-breaking additions and improvements, and reset to zero
when the major version is incremented. Breaking changes in the MQTT API will only be made in firmware releases that increase the firmware version’s major version, following SemVer.
The current API version is 1.0.
Non-breaking changes
Section titled “Non-breaking changes”New fields may be added to the message types, and new variants may be added to any enum types at any time. Similarly, new settings may be added, and new enum variants may be added to enum-type settings. These additions are not considered breaking changes. Clients should ignore unknown fields, settings and enum variants or display them as “Unknown” or similar. Default values may also be changed at any time, and restrictions, such as minimum and maximum values, may be relaxed. Finally, any topics marked as unstable or experimental are exempt from versioning and may change at any time.
About Topics
Section titled “About Topics”This section lists the different topics provided by the MQTT API.
Base topic
Section titled “Base topic”To make sure that different devices do not use the same topics, all MQTT topics are relative to a configured base topic. By default, the base topic is the devices serial number, but can be changed via the mqtt.base_topic setting.
All topics listed in the Topics section are relative to this topic.
Payloads
Section titled “Payloads”Payloads are encoded as JSON unless otherwise noted, with the formats given as TypeScript type definitions. Clients should ignore unknown fields or enum variants.
The exact contents of error strings are not guaranteed and should only be used for display to users, not matched programmatically.
Request topics
Section titled “Request topics”Topics that handle external requests, e.g. settings/set, currently do not provide a reference to what request a reply belongs to.
Callers should attempt to only perform one request per topic at a time.
In future releases, support for MQTT5 correlation data will be added to these topics.
List of topics
Section titled “List of topics”mqtt/status
Section titled “mqtt/status”Contains information about the devices MQTT connection itself. Device posts retained on connect and on disconnect (via LWT).
Format
Section titled “Format”type MqttStatusMsg = { /** true if device is online, false if device is offline (via LWT) */ online: boolean, /** Version of the MQTT API offered by this device. * Only present when device is online. * The current API version is { major: 1, minor: 0 }. */ mqtt_api_version?: { major: number, minor: number }}Examples
Section titled “Examples”{ "online": true, "mqtt_api_version": { "major": 1, "minor": 0 }}{ "online": false}device/info
Section titled “device/info”Contains information about the device itself. Device posts retained on connect.
Format
Section titled “Format”type DeviceInfoMsg = { /** The devices serial number */ serial: string, /** The devices hardware revision */ hardware_revision: string, /** The version of the currently running firmware */ firmware_revision: string}device/temperature
Section titled “device/temperature”Only available on P2300(G)/P3000(G).
Temperature of the device.
Device posts periodically, the frequency is controlled by the device_temp_ivl setting.
The setting has an interval of 0 by default, which disables posting to this topic.
Format
Section titled “Format”type DeviceTempMsg = { /** Temperature of the MCU in °C. `null` if sensor malfunctions */ mcu: number | null, /** Air temperature in the chassis in °C. `null` if sensor malfunctions */ chassis: number | null,}device/reboot
Section titled “device/reboot”Request the device to reboot. The device immediately reboots, no reply is given. Device subscribes on connect.
Request Format
Section titled “Request Format”An empty JSON object: {}
ota/update
Section titled “ota/update”Request the device to perform a firmware update.
Device subscribes on connect.
The response is posted to the ota/reply topic.
Request Format
Section titled “Request Format”type OtaUpdateRequest = { /** An HTTP(S) URL pointing to a firmware binary to download and update to e.g. https://example.org:443/firmware-heater-2.0.0.bin */ url: string}Response Format
Section titled “Response Format”type OtaUpdateResponse = { result: "started" | "busy"}ota/rollback
Section titled “ota/rollback”Request Format
Section titled “Request Format”type OtaRollbackRequest = {}Response Format
Section titled “Response Format”type OtaRollbackResponse = { result: "ok" | "error"}settings/get
Section titled “settings/get”Request settings from the device. The response is posted to the settings/reply topic.
Device subscribes on connect.
Request Format
Section titled “Request Format”A description of what settings to return. Namespace and setting names are case-sensitive.
type SettingsGetRequest = /** Empty object, return all settings */ | {} /** Empty array, return all settings */ | [] /** Return all settings in the given namespace */ | string // single namespace /** Return all settings in the given namespaces*/ | string[] // multiple namespaces /** Return specific settings in the given namespaces */ | { [namespace: string]: string[] }Response Format
Section titled “Response Format”type SettingsGetResponse = | { result: "ok" settings: Settings } | { result: "error" error: string }
type Settings = { [namespace_name: string]: { [setting_name: string]: Setting }}
type Setting = SettingTypeData & { description: string}
type SettingTypeData = | { type: "boolean", default: boolean, value: boolean } | { type: "int32", min: number, max: number, default: number, value: number } | { type: "string", default: string, /** Present if the value is hidden (e.g. password), omitted otherwise. If present, the value will be `null`. */ sensitive?: true, value: string | null } | { type: "enum", allowed_values: string[], default: string, value: string }Examples
Section titled “Examples”Request all settings by posting an empty object (or array):
{}Response:
{ "result": "ok", "settings": { "mqtt": { "broker_uri": { "description": "The brokers URI, e.g. \"mqtts://broker.hivemq.com:8883\"", "type": "string", "default": "mqtts://mqtt.novolto.de:8883", "value": "mqtts://mqtt.novolto.de:8883" }, "base_topic": { "description": "The topic to run the MQTT API under. All topics the device subscribes/posts to are relative to this topic.\nThe tokens {serial} and {default_username} are replaced with the devices serial number and the factory-assigned default username, respectively.", "type": "string", "default": "{default_username}", "value": "{default_username}" }, "client_id": { "description": "Client ID to use when connecting to the broker.\nThe tokens {serial} and {default_username} are replaced with the devices serial number and the factory-assigned default username, respectively.", "type": "string", "default": "{default_username}", "value": "{default_username}" }, "username": { "description": "Username to authenticate to the broker with.\nThe tokens {serial} and {default_username} are replaced with the devices serial number and the factory-assigned default username, respectively.", "type": "string", "default": "{default_username}", "value": "{default_username}" }, "password": { "description": "Password to authenticate to the broker with.\nThe token {default_password} is replaced with the factory-assigned default password.", "type": "string", "default": "{default_password}", "sensitive": true, "value": null }, "ha_discovery": { "description": "HomeAssistant MQTT discovery prefix (usually 'homeassistant').\nIf set, the MQTT client sends a discovery message to '{configured prefix}/device/{serial number}/config' to tell HomeAssistant about the device and make it available without any HomeAssistant-side configuration.\nLeave empty to disable HomeAssistant discovery.", "type": "string", "default": "", "value": "" }, "heat_msmt_ivl": { "description": "Interval in seconds for publishing measurements to the `{base_topic}/heating/measurements` topic.\nThe actual interval can vary to match the internal sensor interval of 5s.", "type": "int32", "min": 5, "max": 2147483647, "default": 5, "value": 5 }, "legacy_info_ivl": { "description": "Interval in seconds for publishing legacy API `{base_topic}/info` topic.\nSet to 0 to disable legacy API publishing.", "type": "int32", "min": 0, "max": 2147483647, "default": 0, "value": 0 }, "device_temp_ivl": { "description": "Interval in seconds for publishing measurements to the `{base_topic}/device/temperature` topic.\nSet to 0 to disable publishing device temperature measurements.\nThe actual interval can vary to match the internal sensor interval of 15s.", "type": "int32", "min": 0, "max": 2147483647, "default": 0, "value": 0 } }, "wifi": { "ssid": { "description": "SSID of the access point to connect to", "type": "string", "default": "", "value": "Example Access Point" }, "pw": { "description": "Password of the access point to connect to", "type": "string", "default": "", "sensitive": true, "value": null } }, "heating": { "mode": { "description": "The mode of the heating system.\n'off' disables heating, 'manual' enables heating.", "type": "enum", "allowed_values": [ "off", "manual" ], "default": "off", "value": "off" }, "target_temp": { "description": "The target water temperature for the heating system in °C.\nHeating will attempt to reach and maintain this temperature.", "type": "int32", "min": 0, "max": 80, "default": 0, "value": 0 }, "temp_hyst": { "description": "Hysteresis around the target temperature in °C.\nControls distance between where heating turns off due to high temperature, and where heating turns back on due to low temperature.\nLower values increase how often the heater switches on and off, potentially increasing hardware wear.", "type": "int32", "min": 4, "max": 10, "default": 5, "value": 5 }, "temp_hyst_mode": { "description": "Where hysteresis on and off temperatures should be located.\nIf set to `symmetric`, heating turns on below `target_temp - temp_hyst/2`, and off above `target_temp + temp_hyst/2`.\nIf set to `below_target`, heating turns on below `target_temp - temp_hyst`, and off above `target_temp`.", "type": "enum", "allowed_values": [ "symmetric", "below_target" ], "default": "symmetric", "value": "symmetric" }, "target_power": { "description": "The target power for the heating system in watts.", "type": "int32", "min": 0, "max": 3000, "default": 0, "value": 0 }, "require_mqtt": { "description": "Only perform heating if connected to an MQTT broker.\nEnabling this can avoid heating continuing forever if the network or MQTT broker goes down. \nHowever, it does not disable heating if the controller stops posting control messages to the MQTT broker.", "type": "boolean", "default": true, "value": true } } }}Request all settings in wifi and broker_uri in mqtt:
{ "wifi": [], "mqtt": ["broker_uri"]}Response:
{ "result": "ok", "settings": { "wifi": { "ssid": { "description": "SSID of the access point to connect to", "type": "string", "default": "", "value": "Example Access Point" }, "pw": { "description": "Password of the access point to connect to", "type": "string", "default": "", "sensitive": true, "value": null } }, "mqtt": { "broker_uri": { "description": "The brokers URI, e.g. \"mqtts://broker.hivemq.com:8883\"", "type": "string", "default": "mqtts://mqtt.novolto.de:8883", "value": "mqtts://mqtt.novolto.de:8883" } } }}settings/set
Section titled “settings/set”Change settings on the device. The response is posted to the settings/reply topic.
Device subscribes on connect.
Request Format
Section titled “Request Format”A description of what settings to assign what values to. Namespace and setting names are case-sensitive.
type SettingsSetRequest = { [namespace: string]: { [setting: string]: boolean | number | string }}Response Format
Section titled “Response Format”type SettingsSetResponse = /** On success and/or if only namespace/setting-level errors occurred, e.g. wrong settings type */ | { result: "ok", /** Per-namespace list of what settings were updated successfully */ updated?: { [namespace: string]: string[] } /** Per-namespace description of errors that occurred */ errors?: { [namespace: string]: SetNamespaceError } } /** If the entire request failed */ | { result: "error", /** A description of the error */ error: string }
type SetNamespaceError = /** Some settings in the namespace failed to be updated */ | { /** The settings that failed to update and the associated error */ [setting: string]: string } /** The entire namespace failed to be updated, e.g. because it couldn't be found */ | stringExample
Section titled “Example”Request to set a mix of correct settings, wrong types on settings, and non-existing settings and namespaces.
{ "heating": { "mode": "off", "target_temp": "not_a_number", "non_existing_setting": false }, "non_existing_namespace": { "foo": true }}Response:
{ "result": "ok", "updated": { "heating": ["mode"] }, "errors": { "heating": { "target_temp": "wrong_type", "non_existing_setting": "setting_not_found" }, "non_existing_namespace": "namespace_not_found" }}heating/status
Section titled “heating/status”Only available on P2300(G)/P3000(G).
Contains information about what heating status the device is in, and what heating parameters are currently active. Device posts retained on status or parameter change.
Format
Section titled “Format”type HeatingStatusMsg = { /** The currently configured heating mode */ mode: HeatingMode, /** The current heating state, e.g. heating or max temperature reached" */ state: HeatingState, /** The error that is currently preventing heating. Present if state is "error" or "initializing" */ error?: string /** The currently active target water temperature */ target_temp: number, /** The currently active target power */ target_power: number}
type HeatingMode = | "off" | "manual"
type HeatingState = /** Heating is disabled */ | "disabled" /** Some issue caused by the heater still starting up is preventing heating. */ | "initializing" /** The device is actively heating */ | "heating" /** Heating is enabled, but paused because the target water temperature has been reached */ | "max_temperature_reached" /** Some issue, e.g. hardware problem, is preventing heating */ | "error"heating/measurements
Section titled “heating/measurements”Only available on P2300(G)/P3000(G).
Measurements related to heating.
Device posts periodically, the wanted interval can be configured via the mqtt.heat_msmt_ivl setting.
Format
Section titled “Format”type HeatingMeasurementsMsg = { /** Electricity metering data. `null` on sensor malfunction */ electricity: { /** The voltage at the device in V. `null` if measurement fails. */ voltage: number | null, /** The power the device is currently using in W. Average over the last 5 seconds. `null` if measurement fails */ power: number | null, } | null, /** Water temperature in °C. `null` on sensor malfunction */ water_temp: number | null,}meter/status
Section titled “meter/status”Only available on Sense devices.
Contains status and metadata of the connection to the electricity meter. Device posts retained on connection to broker, and on status change.
Format
Section titled “Format”type MeterStatusMsg = { /** Info about the connected meter. `null` if no meter is connected */ meter: { /** Manufacturer Id sent by the meter. `null` if not found */ manufacturer_id: string | null, } | null, /** The protocol currently in use */ protocol: "sml" | "d0" /** Baud rate the meter connection currently uses */ baud_rate: number}meter/data
Section titled “meter/data”Received and processed data from the meter.
Device posts when data is received and the time since the last posted message is longer than the
mqtt.meter_data_ivl setting value.
Format
Section titled “Format”type MeterDataMsg = { /** Active power import in W. `null` if meter doesn't provide power */ power: number | null, /** Active energy import in Wh. `null` if meter doesn't provide energy */ energy: number | null}Only available on P2300(G)/P3000(G).
Provides device information and measurements.
Reimplementation of a subset of the legacy MQTT API present in firmware versions before v2.0, for backwards compatibility.
Only a subset (documented below) of values that were present before v2.0 in the info topic is supported.
Device posts periodically, the wanted interval can be configured via the mqtt.legacy_info_ivl setting.
Format
Section titled “Format”type LegacyInfoMsg = { /** The devices serial number */ serial: string, /** The configured value of the `mqtt.legacy_info_ivl` setting */ msi: number, /** Chassis temperature in °C */ avt1: number, /** Measured temperature in the water tank in °C*/ avtw: number, /** The configured value of the `heating.target_temp` setting */ sptw: number, /** The configured value of the `heating.temp_hyst` setting */ sptwh: number, /** The configured value of the `heating.target_power` setting */ spp: number, /** Bitflags of different warnings and errors. Each set bit in the value corresponds to a specific warning or error. * These bits are mapped to firmware v2.0 errors (in heating/status) on a best effort basis, and may not match perfectly. * 0x0001: A sensor is missing * 0x0002: Water temperature reading failed. * 0x0004: Internal electric meter readouts outside expected range * 0x0008: Fan RPM outside expected range * 0x0010: Board temperature above allowed range * 0x0020: Board temperature reading failed * 0x0040: Internal electric meater reading failed * 0x0080: Connection to MQTT broker lost. * 0x0100: Electrical AC frequency outside expected range. * 0x0200: Device is missing expected internal settings * 0x0400: Safety tempeature breaker presumed to be tripped. */ st: number, /** Internally measured voltage in Volts. */ avv: number, /** Internally measured electric power in Watts. Average over the last 5 seconds. */ avp: number, /** Internally measured electrical current. Average over the last 5 seconds. */ avi: number}control
Section titled “control”Only available on P2300(G)/P3000(G).
Allows running commands and changing settings.
Reimplementation of a subset of the legacy MQTT API resent in firmware versions before v2.0, for backwards compatibility.
Only a subset of settings (documented below) that were present before v2.0 in the control topic is supported.
The API accepts key-value pairs to assign new values to settings.
Settings are namespaced in modules, and are documented below in a <module>.<setting> format.
These settings do not correspond to the List of Settings, but are instead
settings that were present in the legacy firmware.
The following subset of legacy firmware settings is supported in the control topic:
core.reboot: Reboots the device if the value is set totruecore.msi: Setsmqtt.legacy_info_ivlto the given valuesensor.spp: Setsheating.target_powerto the given value, andheating.modetooffif the value is 0, tomanualotherwise.sensor.sptw: Setsheating.target_tempto the given valuesensor.sptwh: Setsheating.temp_hystto the given value
Request format
Section titled “Request format”type LegacyControlRequest = { [module: string]: [ { /** The settings name */ name: string, /** The value to assign to the setting */ value: boolean | number | string } ]}Response format
Section titled “Response format”type LegacyControlResponse = { serial: string, /** Result code, 0 on success */ ret: number, /** Error description if ret is nonzero */ s_err?: string}Examples
Section titled “Examples”To reboot, send:
{ "core": [ { "name": "reboot", "value": true } ]}To adjust heating power and temperature, send:
{ "sensor": [ { "name": "spp", "value": 100 }, { "name": "sptw", "value": 70 } ]}