{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 4. Hardware-Software Communication\n", "\n", "```{warning}\n", "Recently, HiveMQ Cloud changed such that the Certificate Authority (CA) file, `hivemq-com-chain.der`, is not transferrable across different broker instances. The [latest `hivemq-com-chain.der` file](https://raw.githubusercontent.com/sparks-baird/self-driving-lab-demo/main/src/public_mqtt_sdl_demo/hivemq-com-chain.der) from [`self-driving-lab-demo`](https://github.com/sparks-baird/self-driving-lab-demo) will be hard-coded to the `self-driving-lab-demo` public test credentials (i.e., what is used in Module 1 - Running the Demo), so the tutorials should run without issue as long as you are using that file. However, the assignment requires you to create your own HiveMQ Cloud broker instance, so you will need to follow the instructions there to generate your a `hivemq-com-chain.der` file specific to your instance.\n", "```\n", "\n", "In this tutorial, you will learn how to send commands to and receive sensor data from a microcontroller using the MQTT protocol.\n", "\n", "\n", "\n", "*Workflow diagram of using a MQTT broker with the \"publish/subscribe\" model to pass temperature data from one client to another.*\n", "\n", "\n", "\n", "## Onboard LED using MQTT\n", "\n", "In an earlier module (blink and read), you learned how to blink the onboard LED every two seconds. In this section, you will control the onboard LED using MQTT, a standard protocol for internet-of-things communication. By definition, hardware/software communication implies that you are interfacing multiple devices (in this case, your microcontroller and a Jupyter notebook running on e.g., Google Colab). Let's start with a hands-on example.\n", "\n", "✅ Create a new file on your Pico W microcontroller called `mqtt_led.py` and copy the script below (based on [JayPalm's gist](https://gist.github.com/JayPalm/8bfd836b696c28ec5dc1f6b5b4dd18ea) and [`mqtt_as` README](https://github.com/peterhinch/micropython-mqtt/blob/master/mqtt_as/README.md)). Ensure that your microcontroller has been set up according to the instructions in the \"Running the demo\" module earlier in the course (only [`netman.py`](https://github.com/sparks-baird/self-driving-lab-demo/blob/main/src/public_mqtt_sdl_demo/lib/netman.py) [[permalink](https://github.com/sparks-baird/self-driving-lab-demo/blob/0ff0adec3e997c096990de594844d73a9ce18fd6/src/public_mqtt_sdl_demo/lib/netman.py)] and your filled-out `secrets.py` are required). You will also need to upload the [`mqtt_as.py` module](https://github.com/peterhinch/micropython-mqtt/blob/master/mqtt_as/mqtt_as.py) to either the main directory or the `lib` folder. Make sure to replace `` with your course ID (without the brackets). If you've forgotten your GitHub Classroom course ID, you can refer back to your quiz responses from the GitHub starter tutorial assignment. Once you've updated the file, save the file and click play." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from mqtt_as import MQTTClient, config\n", "from machine import Pin, ADC\n", "import asyncio\n", "from netman import connectWiFi\n", "import ssl\n", "import ntptime\n", "from time import time\n", "\n", "from secrets import (\n", " HIVEMQ_HOST,\n", " HIVEMQ_PASSWORD,\n", " HIVEMQ_USERNAME,\n", " PASSWORD,\n", " SSID,\n", ")\n", "\n", "connectWiFi(SSID, PASSWORD, country=\"US\")\n", "\n", "# usually would be a device-specific ID, but using course ID for now\n", "COURSE_ID = \"\" # UPDATE THIS TO YOUR ID\n", "\n", "# To validate certificates, a valid time is required\n", "ntptime.timeout = 30 # type: ignore\n", "ntptime.host = \"pool.ntp.org\"\n", "ntptime.settime()\n", "\n", "print(\"Obtaining CA Certificate\")\n", "# generated via https://colab.research.google.com/github/sparks-baird/self-driving-lab-demo/blob/main/notebooks/7.2.1-hivemq-openssl-certificate.ipynb # noqa: E501\n", "with open(\"hivemq-com-chain.der\", \"rb\") as f:\n", " cacert = f.read()\n", "f.close()\n", "\n", "# Local configuration\n", "config.update(\n", " {\n", " \"ssid\": SSID,\n", " \"wifi_pw\": PASSWORD,\n", " \"server\": HIVEMQ_HOST,\n", " \"user\": HIVEMQ_USERNAME,\n", " \"password\": HIVEMQ_PASSWORD,\n", " \"ssl\": True,\n", " \"ssl_params\": {\n", " \"server_side\": False,\n", " \"key\": None,\n", " \"cert\": None,\n", " \"cert_reqs\": ssl.CERT_REQUIRED,\n", " \"cadata\": cacert,\n", " \"server_hostname\": HIVEMQ_HOST,\n", " },\n", " \"keepalive\": 30,\n", " }\n", ")\n", "\n", "onboard_led = Pin(\"LED\", Pin.OUT) # Pico W is slightly different than Pico\n", "\n", "command_topic = f\"{COURSE_ID}/onboard_led\"\n", "sensor_data_topic = f\"{COURSE_ID}/onboard_temp\"\n", "\n", "adcpin = 4\n", "sensor = ADC(adcpin)\n", "\n", "\n", "def ReadTemperature():\n", " adc_value = sensor.read_u16()\n", " volt = (3.3 / 65535) * adc_value\n", " temperature = 27 - (volt - 0.706) / 0.001721\n", " # internal temp sensor has low precision, so round to 1 decimal place\n", " return round(temperature, 1)\n", "\n", "\n", "async def messages(client): # Respond to incoming messages\n", " async for topic, msg, retained in client.queue:\n", " try:\n", " topic = topic.decode()\n", " msg = msg.decode()\n", " retained = str(retained)\n", " print((topic, msg, retained))\n", "\n", " if topic == command_topic:\n", " if msg == \"on\":\n", " onboard_led.on()\n", " elif msg == \"off\":\n", " onboard_led.off()\n", " elif msg == \"toggle\":\n", " onboard_led.toggle()\n", " temperature = ReadTemperature()\n", " print(f\"Publish {temperature} to {sensor_data_topic}\")\n", " # If WiFi is down the following will pause for the duration.\n", " await client.publish(sensor_data_topic, f\"{temperature}\", qos=1)\n", " except Exception as e:\n", " print(e)\n", "\n", "\n", "async def up(client): # Respond to connectivity being (re)established\n", " while True:\n", " await client.up.wait() # Wait on an Event\n", " client.up.clear()\n", " await client.subscribe(command_topic, 1) # renew subscriptions\n", "\n", "\n", "async def main(client):\n", " await client.connect()\n", " for coroutine in (up, messages):\n", " asyncio.create_task(coroutine(client))\n", "\n", " start_time = time()\n", " # must have the while True loop to keep the program running\n", " while True:\n", " await asyncio.sleep(5)\n", " elapsed_time = round(time() - start_time)\n", " print(f\"Elapsed: {elapsed_time}s\")\n", "\n", "\n", "config[\"queue_len\"] = 2 # Use event interface with specified queue length\n", "MQTTClient.DEBUG = True # Optional: print diagnostic messages\n", "client = MQTTClient(config)\n", "del cacert # to free memory\n", "try:\n", " asyncio.run(main(client))\n", "finally:\n", " client.close() # Prevent LmacRxBlk:1 errors" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After running the above script, you should see a message something like the following printed to the Thonny command line:\n", "\n", "> ```\n", "> MPY: soft reboot\n", "> MAC address: <...>\n", "> connected\n", "> ip = <...>\n", "> Obtaining CA Certificate\n", "> Checking WiFi integrity.\n", "> Got reliable connection\n", "> Connecting to broker.\n", "> Connected to broker.\n", "> Elapsed: 10s\n", "> Elapsed: 20s\n", "> RAM free 119776 alloc 57504\n", "> Elapsed: 30s\n", "> ...\n", "> ```\n", "\n", "Then, navigate to the [companion Jupyter notebook](./1.4.1-onboard-led-temp.ipynb) and open it in a Jupyter IDE of your choice. For your convenience, an \"Open in Colab\" link is provided. If you are running it elsewhere, you will need to manually install the `paho-mqtt` and `matplotlib` packages (i.e., `pip install paho-mqtt matplotlib`). Update it with your course ID and run the notebook. You should see a plot of the temperature data being sent from your microcontroller to the Jupyter notebook. You should also see the onboard LED on your microcontroller blinking every few seconds in response to the commands sent by the Jupyter notebook.\n", "\n", "While there are simpler implementations of MQTT on a Pico W microcontroller, the current setup which uses [`micropython-mqtt`](https://github.com/peterhinch/micropython-mqtt) is secure, robust, resilient, and asynchronous. What this means is that:\n", "1. All messages you send are encrypted and private (assuming you created your own HiveMQ instance)\n", "2. Message delivery can be guaranteed\n", "3. Receiving multiple messages in a short time frame is handled smoothly\n", "4. The device automatically reconnects if WiFi gets spotty\n", "5. The microcontroller's CPU resources are used efficiently via multi-tasking\n", "\n", "For a hobbyist project, these might not be large concerns; however, when implementing an autonomous experimentation setup for research purposes, these are very important. Also keep in mind that much of this is \"boilerplate code\" that you can copy and paste into your own projects.\n", "\n", "## How MQTT Works\n", "\n", "✅ Read the following three pages from [this MQTT basics course](http://www.steves-internet-guide.com/mqtt-basics-course/):\n", "\n", "- [How MQTT Works](http://www.steves-internet-guide.com/mqtt-works/)\n", "- [Understanding MQTT Topics and Topic Structure](http://www.steves-internet-guide.com/understanding-mqtt-topics/)\n", "- [MQTT Publishing, Subscribing, and Message Exchange](http://www.steves-internet-guide.com/mqtt-publish-subscribe/)\n", "\n", "✅ Read [How to Use The Paho MQTT Python Client for Beginners](http://www.steves-internet-guide.com/into-mqtt-python-client/) from the [MQTT and Python For Beginners course](http://www.steves-internet-guide.com/mqtt-python-beginners-course/)\n", "\n", "\n", "\n", "✅ Review the [micropython-mqtt repository README](https://github.com/peterhinch/micropython-mqtt) and its [Asynchronous MQTT docs](https://github.com/peterhinch/micropython-mqtt/blob/master/mqtt_as/README.md)\n", "\n", "✅ Read [Receiving Messages with the Paho MQTT Python Client](http://www.steves-internet-guide.com/receiving-messages-mqtt-python-client/), paying special attention to the \"Use the Queue object\" section. Then read the Basic first-in first-out (FIFO) Queue section from [this page](https://pymotw.com/2/Queue/#basic-fifo-queue)\n", "\n", "## Additional Resources\n", "- [How to Send and Receive Data Using Raspberry Pi Pico W and MQTT](https://www.tomshardware.com/how-to/send-and-receive-data-raspberry-pi-pico-w-mqtt)\n", "- To better understand the `async` and `await` expressions in the code above, refer to [Make Your Microcontroller Multi-task With Asynchronous Programming](https://yiweimao.github.io/blog/async_microcontroller/)\n", "- A simpler, less robust alternative to `mqtt_as`: [`micropython-umqtt.simple2` repository](https://github.com/fizista/micropython-umqtt.simple2)\n", "- [HiveMQ's websocket client for MQTT testing](https://www.hivemq.com/demos/websocket-client/) (note that one is also available on your HiveMQ instance which can be used for your instance without as much setup)\n", "- HiveMQ has [a free public broker and browser client](https://www.hivemq.com/mqtt/public-mqtt-broker/) than can be useful for debugging and troubleshooting\n", "- Another popular MQTT framework is [Mosquitto](https://mosquitto.org/), which also has [a public (non-secure) broker](https://test.mosquitto.org/)\n", "\n", "## Alternative Frameworks\n", "\n", "MQTT is only one of many good protocols that can be used for hardware/software communication. Other popular protocols, especially in the context of laboratory information, include:\n", "- [Robot Operating System (ROS)](https://www.ros.org/)\n", "- [SiLA2](https://sila-standard.com/)\n", "- [OPC Unified Architecture (OPC UA)](https://opcfoundation.org/about/opc-technologies/opc-ua/)\n", "\n", "Workflow orchestration packages for laboratory automation are generally based on a standard communication protocol like MQTT and the ones mentioned above.\n", "\n", "See also https://github.com/AccelerationConsortium/awesome-self-driving-labs#software.\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once you've successfully finished the example from above and completed the reading material, you are done with this tutorial 🎉. Return to the course website to do a knowledge check." ] } ], "metadata": { "kernelspec": { "display_name": "ac-microcourses", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.5" }, "nbsphinx": { "execute": "never" } }, "nbformat": 4, "nbformat_minor": 2 }