notes:mqtt
Differences
This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
| notes:mqtt [2024/04/18 01:42] – created discord | notes:mqtt [2024/04/20 20:46] (current) – discord | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | Written by Jacob Haip on April 17, 2024 | + | ====== MQTT and Microcontroller Folk Integrations ====== |
| + | Jacob Haip, April 2024 | ||
| - | I am interested in integrating more physical hardware and sensors into Folk Computer to help expand | + | I am interested in integrating more physical hardware and sensors into Folk Computer to expand |
| WiFi-connected microcontrollers are the primary tool I have been using to integrate sensors and actuators with Folk. Boards I have used include the [[https:// | WiFi-connected microcontrollers are the primary tool I have been using to integrate sensors and actuators with Folk. Boards I have used include the [[https:// | ||
| Line 8: | Line 9: | ||
| Lately I have stated using MQTT as the protocol for microcontrollers to talk to the Folk System. It is a lighter weight Pub/Sub messaging protocol that is primarily done on top of a TCP connection. Microcontrollers talk to a MQTT Broker running alongside Folk and then a Folk program talks to the broker as well. | Lately I have stated using MQTT as the protocol for microcontrollers to talk to the Folk System. It is a lighter weight Pub/Sub messaging protocol that is primarily done on top of a TCP connection. Microcontrollers talk to a MQTT Broker running alongside Folk and then a Folk program talks to the broker as well. | ||
| + | |||
| + | {{mqtt-arch.png}} | ||
| I am using the [[https:// | I am using the [[https:// | ||
| Line 70: | Line 73: | ||
| </ | </ | ||
| - | === Example code using the Adafruit MagTag E-Ink Microcontroller | + | ===== Getting a button input into Folk ===== |
| - | [[https:// | + | {{micro-button-press.gif}} |
| - | < | + | |
| - | import | + | < |
| + | from machine import Pin, I2C | ||
| + | import network | ||
| + | import | ||
| import time | import time | ||
| - | import ssl | + | from umqtt.simple |
| - | import socketpool | + | |
| - | import wifi | + | |
| - | import adafruit_minimqtt.adafruit_minimqtt as MQTT | + | |
| - | from adafruit_magtag.magtag | + | |
| - | magtag | + | wlan = network.WLAN(network.STA_IF) |
| - | magtag.add_text( | + | wlan.active(True) |
| - | | + | wlan.config(pm = 0xa11140) # Diable powersave mode |
| - | 50, | + | wlan.connect("XXX", "XXX") |
| - | (magtag.graphics.display.height // 2) - 1, | + | |
| - | ), | + | |
| - | text_scale=3, | + | |
| - | ) | + | |
| - | magtag.set_text("Hello World") | + | |
| - | print(f" | + | max_wait = 10 |
| - | print(f" | + | while max_wait > 0: |
| - | wifi.radio.connect(os.getenv(" | + | if wlan.status() < 0 or wlan.status() >= 3: |
| - | print(f" | + | |
| + | max_wait -= 1 | ||
| + | | ||
| + | utime.sleep(1) | ||
| - | sending_feed | + | #Handle connection error |
| - | message_feed | + | if wlan.status() != 3: |
| + | raise RuntimeError(' | ||
| + | else: | ||
| + | print(' | ||
| + | status | ||
| + | | ||
| - | def connected(client, | + | def connectMQTT(): |
| - | client.subscribe(message_feed) | + | |
| + | server=b" | ||
| + | port=1883, | ||
| + | | ||
| + | client.connect() | ||
| + | return client | ||
| - | def disconnected(client, userdata, rc): | + | def reconnect(): |
| - | print(" | + | print(' |
| + | time.sleep(5) | ||
| + | machine.reset() | ||
| - | def message(client, topic, message): | + | client |
| - | | + | MQTT_BUTTON1_TOPIC = "pico/ |
| - | | + | button1 = Pin(16, Pin.IN, Pin.PULL_UP) |
| + | button1_last_time = time.ticks_ms() | ||
| - | pool = socketpool.SocketPool(wifi.radio) | + | def mqtt_send_with_reset(topic, msg): |
| - | ssl_context = ssl.create_default_context() | + | try: |
| - | mqtt_client = MQTT.MQTT( | + | |
| - | broker=" | + | |
| - | | + | |
| - | | + | |
| - | | + | |
| - | ) | + | |
| - | # Setup the callback methods above | + | def publish_mqtt_button1_msg(t): |
| - | mqtt_client.on_connect = connected | + | |
| - | mqtt_client.on_disconnect | + | if time.ticks_diff(time.ticks_ms(), |
| - | mqtt_client.on_message = message | + | |
| + | button1_last_time | ||
| - | # Connect the client to the MQTT broker. | + | button1.irq(publish_mqtt_button1_msg, |
| - | print(" | + | |
| - | mqtt_client.connect() | + | |
| - | test_val = 0 | ||
| while True: | while True: | ||
| - | | + | |
| - | | + | |
| + | utime.sleep(30) | ||
| + | </ | ||
| - | # Send a new message | + | <code tcl show-button-press.folk> |
| - | print(f"Sending value: | + | When MQTT claims |
| - | mqtt_client.publish(sending_feed, | + | Wish $this is labelled |
| - | | + | } |
| - | | + | |
| </ | </ | ||
| - | A folk program | + | My favorite demo of this is a set of 4 buttons that my toddler can press to play a song on Spotify. |
| - | <code tcl crude-clock.folk> | + | |
| + | {{button-kid-press.jpeg}} | ||
| + | |||
| + | <code tcl spotify-button-press.folk> | ||
| + | Claim button 1 uri is " | ||
| + | Claim button 2 uri is " | ||
| + | Claim button 3 uri is " | ||
| + | Claim button 4 uri is " | ||
| + | |||
| + | When MQTT claims message /message/ topic " | ||
| + | Wish spotify plays uri $uri at time $t | ||
| + | } | ||
| + | When MQTT claims message /message/ topic " | ||
| + | Wish spotify plays uri $uri at time $t | ||
| + | } | ||
| + | When MQTT claims message /message/ topic " | ||
| + | Wish spotify plays uri $uri at time $t | ||
| + | } | ||
| + | When MQTT claims message /message/ topic " | ||
| + | Wish spotify plays uri $uri at time $t | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== Output from Folk to Microcontroller ===== | ||
| + | |||
| + | I have this cool [[https:// | ||
| + | |||
| + | <code c matrix-portal.c> | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | #include < | ||
| + | |||
| + | char ssid[] = " | ||
| + | char pass[] = " | ||
| + | |||
| + | const char* mqttServer = " | ||
| + | const int mqttPort = 1883; | ||
| + | const char* mqttTopic = "/ | ||
| + | |||
| + | WiFiClient espClient; | ||
| + | PubSubClient client(espClient); | ||
| + | |||
| + | uint8_t rgbPins[] = {7, 8, 9, 10, 11, 12}; | ||
| + | uint8_t addrPins[] = {17, 18, 19, 20}; | ||
| + | uint8_t clockPin = 14; | ||
| + | uint8_t latchPin = 15; | ||
| + | uint8_t oePin = 16; | ||
| + | |||
| + | Adafruit_Protomatter matrix( | ||
| + | 64, 4, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, false); | ||
| + | |||
| + | uint64_t pixelPalette[] = { | ||
| + | matrix.color565(0, | ||
| + | matrix.color565(157, | ||
| + | matrix.color565(255, | ||
| + | matrix.color565(190, | ||
| + | matrix.color565(224, | ||
| + | matrix.color565(73, | ||
| + | matrix.color565(164, | ||
| + | matrix.color565(235, | ||
| + | matrix.color565(247, | ||
| + | matrix.color565(47, | ||
| + | matrix.color565(68, | ||
| + | matrix.color565(163, | ||
| + | matrix.color565(27, | ||
| + | matrix.color565(0, | ||
| + | matrix.color565(49, | ||
| + | matrix.color565(178, | ||
| + | matrix.color565(255, | ||
| + | |||
| + | |||
| + | //--------- WIFI ------------------------------------------- | ||
| + | |||
| + | void wifi_connect() { | ||
| + | Serial.print(" | ||
| + | delay(10); | ||
| + | WiFi.begin(ssid, | ||
| + | while (WiFi.status() != WL_CONNECTED) { | ||
| + | delay(500); | ||
| + | Serial.print(" | ||
| + | } | ||
| + | Serial.println(" | ||
| + | Serial.println(" | ||
| + | Serial.println(WiFi.localIP()); | ||
| + | } | ||
| + | |||
| + | // | ||
| + | void mqtt_setup() { | ||
| + | client.setServer(mqttServer, | ||
| + | client.setBufferSize(64*32 + 64); | ||
| + | client.setCallback(callback); | ||
| + | Serial.println(" | ||
| + | while (!client.connected()) { | ||
| + | String clientId = " | ||
| + | clientId += String(random(0xffff), | ||
| + | if (client.connect(clientId.c_str())) { | ||
| + | Serial.println(" | ||
| + | } else { | ||
| + | Serial.print(" | ||
| + | Serial.println(client.state()); | ||
| + | delay(2000); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | client.subscribe(mqttTopic); | ||
| + | } | ||
| + | |||
| + | void callback(char* topic, byte* payload, unsigned int length) { | ||
| + | |||
| + | Serial.print(" | ||
| + | Serial.println(topic); | ||
| + | |||
| + | String byteRead = ""; | ||
| + | Serial.print(" | ||
| + | for (int i = 0; i < length; i++) { | ||
| + | byteRead += (char)payload[i]; | ||
| + | } | ||
| + | Serial.println(byteRead); | ||
| + | |||
| + | matrix.fillScreen(matrix.color565(0, | ||
| + | |||
| + | for (int i = 0; i < byteRead.length(); | ||
| + | { | ||
| + | uint64_t pixelColor = matrix.color565(0, | ||
| + | String pixelChar = String(byteRead[i]); | ||
| + | int colorIndex = (int)strtol(& | ||
| + | if (colorIndex >= 0 && colorIndex < 16) | ||
| + | { | ||
| + | pixelColor = pixelPalette[colorIndex]; | ||
| + | } | ||
| + | matrix.drawPixel(i % 64, i / 64, pixelColor); | ||
| + | } | ||
| + | matrix.show(); | ||
| + | } | ||
| + | |||
| + | |||
| + | void setup() | ||
| + | { | ||
| + | Serial.begin(9600); | ||
| + | |||
| + | // Initialize matrix... | ||
| + | ProtomatterStatus pmstatus = matrix.begin(); | ||
| + | Serial.print(" | ||
| + | Serial.println((int)pmstatus); | ||
| + | if (pmstatus != PROTOMATTER_OK) | ||
| + | { | ||
| + | for (;;) | ||
| + | ; | ||
| + | } | ||
| + | |||
| + | matrix.println(" | ||
| + | matrix.show(); | ||
| + | |||
| + | wifi_connect(); | ||
| + | |||
| + | matrix.println(" | ||
| + | matrix.show(); | ||
| + | |||
| + | mqtt_setup(); | ||
| + | } | ||
| + | |||
| + | void loop() | ||
| + | { | ||
| + | client.loop(); | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | And with this you can make a crude clock that fills up the screen horizontally as time passes. | ||
| + | |||
| + | {{crude-clock.gif}} | ||
| + | |||
| + | <code tcl crude-matrixportal-clock.folk> | ||
| When /node/ has step count /c/ { | When /node/ has step count /c/ { | ||
| - | if {[expr {int($c) % 5000}] == 0} { | + | if {[expr {int($c) % 1000}] == 0} { |
| - | set ts [clock milliseconds] | + | set currentTimeMillis [clock milliseconds] |
| - | Wish MQTT publish $ts on topic jhaip/feeds/onoff at timestamp $ts | + | set millisSinceMinuteStart [expr {$currentTimeMillis % 60000}] |
| + | set fillValue [expr {$millisSinceMinuteStart * 64 / 60000}] | ||
| + | |||
| + | set matrixportaloutput "" | ||
| + | for {set y 0} {$y < 32} {incr y} { | ||
| + | for {set x 0} {$x < 64} {incr x} { | ||
| + | if {$x < $fillValue} { | ||
| + | set matrixportaloutput " | ||
| + | } else { | ||
| + | set matrixportaloutput " | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | set c [clock milliseconds] | ||
| + | Wish MQTT publish | ||
| } | } | ||
| } | } | ||
| </ | </ | ||
| - | A folk program | + | Additionally, |
| + | |||
| + | {{matrixportal-live-edit.gif}} | ||
| + | |||
| + | ===== Using MQTT on web pages for bidirectional communication with Folk ===== | ||
| + | |||
| + | Making simple web pages using the [[https:// | ||
| + | |||
| + | First we make a web app that plays a sound when it receives an MQTT messages: | ||
| + | |||
| + | <code html play-sound.html> | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | < | ||
| + | <audio id=" | ||
| + | <source src=" | ||
| + | <source src=" | ||
| + | Your browser does not support | ||
| + | </ | ||
| + | <script src=" | ||
| + | < | ||
| + | const clientId = ' | ||
| + | const host = ' | ||
| + | const options = { | ||
| + | keepalive: 60, | ||
| + | clientId: clientId, | ||
| + | protocolId: ' | ||
| + | protocolVersion: | ||
| + | clean: true, | ||
| + | reconnectPeriod: | ||
| + | connectTimeout: | ||
| + | will: { | ||
| + | topic: ' | ||
| + | payload: ' | ||
| + | qos: 0, | ||
| + | retain: false | ||
| + | }, | ||
| + | } | ||
| + | console.log(' | ||
| + | const client = mqtt.connect(host, | ||
| + | |||
| + | client.on(' | ||
| + | console.log(' | ||
| + | client.end() | ||
| + | }) | ||
| + | |||
| + | client.on(' | ||
| + | console.log(' | ||
| + | }) | ||
| + | |||
| + | client.on(' | ||
| + | console.log(' | ||
| + | client.subscribe('/ | ||
| + | }); | ||
| + | |||
| + | client.on(' | ||
| + | console.log(' | ||
| + | document.getElementById(" | ||
| + | document.getElementById(" | ||
| + | }); | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | And then in folk you can make every phone and laptop with that web page open play a sound when a button is pressed: | ||
| + | |||
| + | {{button-horse.mp4}} | ||
| - | <code tcl show-message.folk> | + | <code tcl trigger-sound.folk> |
| - | When MQTT claims message /message/ topic "/feeds/test" timestamp /t/ { | + | When MQTT claims message /message/ topic "/button1" timestamp /t/ { |
| - | Wish $this is labelled "MagTag value: $message" | + | Wish $this is labelled "BUTTON 1 PRESSED" |
| + | set ts [clock milliseconds] | ||
| + | Wish MQTT publish "make some noise" on topic "/ | ||
| } | } | ||
| </ | </ | ||
notes/mqtt.1713404561.txt.gz · Last modified: by discord
