Use MQTT in an app
MQTT is a lightweight publish/subscribe protocol that works well for badge apps talking to brokers on your network or the internet. The badge firmware includes MicroPython's umqtt.simple library.
EMF Camp MQTT broker
At EMF Camp there is an on-site MQTT broker at mqtt.emf.camp (port 1883, no authentication). Full connection details, topic rules, and wildcard tips are on the MQTT broker page.
You can publish and subscribe on any topic name except those starting with emf/, which are reserved for event infrastructure. You can still subscribe to emf/ topics to read live camp data. Known namespaces include:
emf/bar/for live stats from the barsemf/films/for EMF Films dataemf/weather/for live weather from HQ
For your own badge apps or village projects, pick a topic outside emf/, such as emfcamp/tildagonapp or myvillage/badge/status. Use a hierarchical name so others can subscribe with wildcards (see the developer docs for + and #).
The following examples use the public test broker test.mosquitto.org so you can try them at home. At the event, swap the hostname for mqtt.emf.camp.
Connect to WiFi first
MQTT needs a working network connection, like HTTP requests do. Check whether the badge is already connected to WiFi and if not try to connect and wait for the connection before opening a socket to your broker:
import wifi
def _connect_wifi():
try:
if wifi.status():
return True
wifi.disconnect()
wifi.connect()
if wifi.wait():
return True
return False
except OSError as e:
return False
If your badge is not connected to WiFi yet, see connect to Wi-Fi.
Connect to MQTTT
from umqtt.simple import MQTTClient
def _connect_mqtt():
try:
client = MQTTClient("tildagon-demo", "test.mosquitto.org")
client.connect()
return True
except OSError as e:
client = None
return False
Subscribe to a channel
The following code connects to a channel and listens for messages:
import app
from umqtt.simple import MQTTClient
import wifi
from app_components.tokens import line_height
from events.input import Buttons, BUTTON_TYPES
TOPIC = b"emfcamp/tildagonapp"
class MqttListenerApp(app.App):
def __init__(self):
self.button_states = Buttons(self)
self.message = "Starting..."
self.client = None
def _connect_wifi(self):
try:
if wifi.status():
self.message = "WiFi OK"
return True
wifi.disconnect()
wifi.connect()
if wifi.wait():
self.message = "WiFi OK"
return True
self.message = "WiFi failed"
return False
except OSError as e:
self.message = f"WiFi error: {e}"
return False
def _connect_mqtt(self):
try:
self.client = MQTTClient("tildagon-demo", "test.mosquitto.org")
self.client.set_callback(self.on_message)
self.client.connect()
self.client.subscribe(TOPIC)
self.message = "Waiting for messages..."
return True
except OSError as e:
self.message = f"MQTT error: {e}"
self.client = None
return False
def update(self, delta):
if self.button_states.get(BUTTON_TYPES["CANCEL"]):
self.button_states.clear()
self.minimise()
return
if self.message == "Starting...":
if self._connect_wifi():
self._connect_mqtt()
def on_message(self, topic, msg):
self.message = msg.decode()
print("Message received:", self.message)
def _reduce_text_until_fits(self, ctx, text, width_limit):
extra_text = ""
text_that_fits = text
while ctx.text_width(text_that_fits) > width_limit and text_that_fits:
character = text_that_fits[-1]
text_that_fits = text_that_fits[:-1]
extra_text = character + extra_text
return text_that_fits, extra_text
def _wrap_text(self, ctx, text, width_limit):
lines = []
remaining = text
while remaining:
line, remaining = self._reduce_text_until_fits(
ctx, remaining, width_limit
)
if not line:
line = remaining[:1]
remaining = remaining[1:]
lines.append(line)
return lines
def background_update(self, delta):
if self.client:
try:
self.client.check_msg()
except OSError:
self.message = "MQTT disconnected"
self.client = None
def draw(self, ctx):
ctx.save()
ctx.rgb(0.2, 0, 0).rectangle(-120, -120, 240, 240).fill()
ctx.rgb(1, 1, 1)
text = f"Message: {self.message}"
width_limit = 200 # 240px screen minus margins
lines = self._wrap_text(ctx, text, width_limit)
spacing = line_height * 16 # scale to your font size
start_y = -((len(lines) - 1) * spacing) / 2
for i, line in enumerate(lines):
y = start_y + i * spacing
line_width = ctx.text_width(line)
ctx.move_to(-line_width / 2, y).text(line)
ctx.restore()
__app_export__ = MqttListenerApp
In a Tildagon app, call client.check_msg() from update() or background_update() to check for new messages on subscribed topics.
To receive a message from a computer, install Mosquitto and use mosquitto_sub:
mosquitto_sub -h test.mosquitto.org -t emfcamp/tildagonapp
Send messages to a channel
The following code connects to a channel and sends a message "hello from a badge":
import app
from umqtt.simple import MQTTClient
import wifi
from app_components.tokens import line_height
from events.input import Buttons, BUTTON_TYPES
TOPIC = b"emfcamp/tildagonapp"
class MqttPublisherApp(app.App):
def __init__(self):
self.button_states = Buttons(self)
self.message = "Starting..."
self.client = None
def _connect_wifi(self):
try:
if wifi.status():
self.message = "WiFi OK"
return True
wifi.disconnect()
wifi.connect()
if wifi.wait():
self.message = "WiFi OK"
return True
self.message = "WiFi failed"
return False
except OSError as e:
self.message = f"WiFi error: {e}"
return False
def _connect_mqtt(self):
try:
self.client = MQTTClient("tildagon-demo", "test.mosquitto.org")
self.client.set_callback(self.on_message)
self.client.connect()
self.client.publish(b"emfcamp/tildagonapp", b"hello from a badge")
self.message = "Message sent"
self.client.disconnect()
return True
except OSError as e:
self.message = f"MQTT error: {e}"
self.client = None
return False
def update(self, delta):
if self.button_states.get(BUTTON_TYPES["CANCEL"]):
self.button_states.clear()
self.minimise()
return
if self.message == "Starting...":
if self._connect_wifi():
self._connect_mqtt()
def on_message(self, topic, msg):
self.message = msg.decode()
print("Message received:", self.message)
def _reduce_text_until_fits(self, ctx, text, width_limit):
extra_text = ""
text_that_fits = text
while ctx.text_width(text_that_fits) > width_limit and text_that_fits:
character = text_that_fits[-1]
text_that_fits = text_that_fits[:-1]
extra_text = character + extra_text
return text_that_fits, extra_text
def _wrap_text(self, ctx, text, width_limit):
lines = []
remaining = text
while remaining:
line, remaining = self._reduce_text_until_fits(
ctx, remaining, width_limit
)
if not line:
line = remaining[:1]
remaining = remaining[1:]
lines.append(line)
return lines
def draw(self, ctx):
ctx.save()
ctx.rgb(0.2, 0, 0).rectangle(-120, -120, 240, 240).fill()
ctx.rgb(1, 1, 1)
text = f"Message: {self.message}"
width_limit = 200 # 240px screen minus margins
lines = self._wrap_text(ctx, text, width_limit)
spacing = line_height * 16 # scale to your font size
start_y = -((len(lines) - 1) * spacing) / 2
for i, line in enumerate(lines):
y = start_y + i * spacing
line_width = ctx.text_width(line)
ctx.move_to(-line_width / 2, y).text(line)
ctx.restore()
__app_export__ = MqttPublisherApp
To send a message from a computer, install Mosquitto and use mosquitto_pub:
mosquitto_pub -h test.mosquitto.org -t emfcamp/tildagonapp -m "hello from my laptop"
Troubleshooting
OSError: -202 when connecting or publishing
If you see OSError: -202 while using MQTT (or other network code such as requests or socket), the badge is probably not connected to Wi-Fi yet and you are trying to do something that needs the network.
Call wifi.connect() and use wifi.wait() (or check wifi.status()) before client.connect() or similar calls. Wi-Fi connection is asynchronous; calling your network code too early is a common cause of this error.
If you are definitely connected and still see -202, it can also indicate a DNS problem resolving the hostname. Try your broker's IP address instead to narrow it down.