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" }
]
| Field | Meaning |
|---|---|
id | Required. The message lands in src.<id> |
type | "mqtt" |
broker | Broker URL: mqtt://host:1883, mqtts://host:8883 (TLS), ws://… or wss://… (WebSocket) |
topic | Required. The topic to subscribe to; + and # wildcards work |
json_path | Optional. Take one value out of a JSON payload (below) |
qos | Optional. 0 (default), 1 or 2 |
username, password | Optional. 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,usernameandpasswordare used for all of them; a source naming another broker is connected to the first one, with a warning in the log. mqtts://andwss://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.comworks 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:
| Value | Meaning |
|---|---|
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>_t | When the last message arrived, in seconds since Kamoji started; compare with device.uptime_s to see how old it is |
src.mqtt_connected | true 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_path | Value |
|---|---|
sensor.temp | 21.5 |
$.sensor.hum | 40 (a leading $. is allowed) |
list[1].v | 2 (array indexes start at 0) |
sensor | The 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.subscribeinmain.luaoron_enterreturnsfalse, but the topic is still subscribed as soon as the client connects.mqtt.publishinmain.luaoron_enter, or in a mini app without anymqttsource, returnsfalseand sends nothing. Publish from gestures, events oron_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.