Skip to main content

MQTT and Data

A Kamoji Mini App can show data from outside Kamoji: today over MQTT, a small publish/subscribe protocol that smart-home systems (Home Assistant, Node-RED, Mosquitto and others) speak. Kamoji connects to your MQTT broker while the mini app is on screen, and disconnects when it leaves.

MQTT needs Kamoji on Wi-Fi with a route to the broker. In a car without Wi-Fi the mini app simply shows no data; data through the phone is coming later.

MQTT sources​

Declare net.mqtt and list the topics in the manifest's sources:

"permissions": ["net.mqtt"],
"params": [
{ "key": "broker", "type": "string", "label": "Broker", "default": "mqtt://broker.hivemq.com:1883" },
{ "key": "user", "type": "string", "label": "User name", "optional": true },
{ "key": "pass", "type": "secret", "label": "Password", "optional": true }
],
"sources": [
{ "id": "power", "type": "mqtt", "broker": "{{param.broker}}",
"username": "{{param.user}}", "password": "{{param.pass}}",
"topic": "home/solar/power" },
{ "id": "temp", "type": "mqtt", "broker": "{{param.broker}}",
"topic": "home/livingroom/climate", "json_path": "sensor.temp" }
]
FieldMeaning
idRequired. The message lands in src.<id>
type"mqtt"
brokerBroker URL: mqtt://host:1883, mqtts://host:8883 (TLS), ws://… or wss://… (WebSocket)
topicRequired. The topic to subscribe to; + and # wildcards work
json_pathOptional. Take one value out of a JSON payload (below)
qosOptional. 0 (default), 1 or 2
username, passwordOptional. Log-in for the broker

Every string may use {{ }} with param.*, so users can point the mini app at their own broker and topics from its settings. Make the password a secret setting so it never leaves Kamoji.

  • One broker per mini app. The first source's broker, username and password are used for all of them; a source naming another broker is connected to the first one, with a warning in the log.
  • mqtts:// and wss:// check the broker's certificate against the usual public certificate authorities. A broker with a self-signed certificate cannot be used over TLS.
  • Kamoji connects with a new client id for every connection, of the form kamoji-<MAC>-<app id>-<random>, so it never clashes with another client on a shared public broker. It uses a 30-second keep-alive, and retries every 5 seconds when the connection drops.
  • For quick tests, the public broker broker.hivemq.com works well. Anyone can read and write its topics, so never send anything private there.

Reading the values​

Each message on a source's topic sets:

ValueMeaning
src.<id>The payload: a number if the whole payload is a number (21.5), else the text. With json_path, the value found there (number, string, boolean or table)
src.<id>_tWhen the last message arrived, in seconds since Kamoji started; compare with device.uptime_s to see how old it is
src.mqtt_connectedtrue while connected to the broker, false after a drop
{ "type": "label", "font": "large", "align": "center", "text": "{{ src.power or '--' }} W" }
{ "type": "label", "align": "center", "y": 50, "text": "{{ src.temp and fmt('%.1f °C', src.temp) or '' }}" }
{ "type": "label", "align": "bottom_mid", "y": -30,
"text": "{{ src.power_t and fmt('%ds ago', device.uptime_s - src.power_t) or 'no data yet' }}" }

Until a first message arrives, src.<id> is nil, so always give a fallback (src.power or '--'). Retained messages arrive right after connecting, which makes them a good fit for values that change rarely.

json_path​

For a payload like {"sensor": {"temp": 21.5, "hum": 40}, "list": [{"v": 1}, {"v": 2}]}:

json_pathValue
sensor.temp21.5
$.sensor.hum40 (a leading $. is allowed)
list[1].v2 (array indexes start at 0)
sensorThe table { temp = 21.5, hum = 40 }

A payload that is not valid JSON sets src.<id> to nil and logs a warning. A path that does not exist gives nil.

Limits​

  • Payloads are cut at 2048 bytes; larger messages are not reassembled.
  • Up to 32 messages are queued between two loops of the firmware; when more arrive, the oldest are dropped.

Lua: on_mqtt, mqtt.publish, mqtt.subscribe​

on_mqtt(topic, payload) in main.lua is called for every message the mini app receives, on any of its topics, with the raw payload as a string. Use it for anything src cannot do:

function on_mqtt(topic, payload)
local data = json.decode(payload)
if data and data.alarm then state.alarm = data.alarm end
end

mqtt.publish(topic, payload [, qos [, retain]]) sends a message. The payload is converted to text (use json.encode for tables). It returns true when the message was queued; the firmware sends it in the background, and holds it while the connection is down.

"on": { "top_tap": "mqtt.publish('home/kamoji/button', 'pressed')" }

mqtt.subscribe(topic [, qos]) adds a topic at run time. Its messages only go to on_mqtt, not to src. It is kept across reconnections.

Both need the net.mqtt permission (they raise an error without it) and an MQTT client, which Kamoji only starts when the manifest has at least one mqtt source, right after on_enter. So:

  • mqtt.subscribe in main.lua or on_enter returns false, but the topic is still subscribed as soon as the client connects.
  • mqtt.publish in main.lua or on_enter, or in a mini app without any mqtt source, returns false and sends nothing. Publish from gestures, events or on_mqtt.

Coming later​

These data sources are designed but not available yet:

  • Phone: the Kamoji app fetches data on the mini app's behalf and sends it over Bluetooth, so it works in a car without Wi-Fi.
  • HTTP: polling a URL with headers and a JSON path.
  • A per-source age value; for now use src.<id>_t.

See the MQTT example for a complete mini app.