[{"element_type":"user_image","content":"https://cdn-learn.adafruit.com/user_assets/assets/000/001/728/original/zphqst-03-demo.jpeg?1743119278","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eOverview\u003c/h2\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003eThis guide shows how to make an IoT toggle switch with an Adafruit Feather TFT ESP32-S3, Zephyr, and Adafruit IO. Key features include: GPIO input for Boot button, LVGL graphics, MQTT over WiFi with TLSv1.2, and USB serial shell commands for saving WiFi and MQTT configuration settings to NVM flash. This guide is intended for people who want to learn how to write applications in C using Zephyr APIs.\u003c/p\u003e\n\u003cp\u003eDemo video: \u003ca href=\"https://youtu.be/N23GkiPNxvA\"\u003eIoT toggle switch: Zephyr + Feather TFT + Adafruit IO\u003c/a\u003e\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"embed","content":"https://www.youtube.com/watch?v=N23GkiPNxvA"},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003ePreviously in this series of guides about using Zephyr on Adafruit hardware, I focused on setting up developer tools and writing Devicetree board definitions. This time, I'm moving up the stack to show how to build an application tying together several Zephyr APIs along with a custom board definition.\u003c/p\u003e\n\u003cp\u003eBuilding an IoT app with WiFi, TLS, and graphics is unavoidably a bit complicated. It took me about three weeks to write the code, which totals a bit over 2100 lines. Listing all of that here would be awkward. If you want the details, you can browse the code in my \u003ca href=\"https://github.com/samblenny/zphqst-03\" target=\"_blank\"\u003ezphqst-03\u003c/a\u003e GitHub repo. The code has lots of comments, including citations for the references I used while learning to use the Zephyr APIs.\u003c/p\u003e\n\u003cp\u003eThis guide will focus on:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eHow to build, run, and configure the IoT toggle switch app\u003c/li\u003e\n\u003cli\u003eHigh level tour of the source code with GitHub links: which files do what?\u003c/li\u003e\n\u003cli\u003eUnderstanding C language features that you'll need to use Zephyr APIs effectively: structs, function pointers, etc.\u003c/li\u003e\n\u003cli\u003eZephyr troubleshooting tips: diagnose and fix memory allocation issues, enable various types of debug logging, etc.\u003c/li\u003e\n\u003cli\u003eMQTT testing with \u003ccode\u003eopenssl\u003c/code\u003e and the\u0026nbsp;\u003ccode\u003emosquitto\u003c/code\u003e MQTT broker with its companion command line tools, \u003ccode\u003emosquitto_pub\u003c/code\u003e\u0026nbsp;and \u003ccode\u003emosquitto_sub\u003c/code\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eParts\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"product","content":"https://www.adafruit.com/product/5483","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eBuild \u0026amp; Run\u003c/h2\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003ePrepare Dev Tools\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eTo prepare for building this project:\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eTo run Zephyr's \u003ccode class=\"inline\"\u003ewest\u003c/code\u003e commandline tool, you need to set up a Zephyr\nproject, including a Python virtual environment and the zephyr git repo.\nIn my examples, the zephyr project directory is \u003ccode class=\"inline\"\u003e~/code/zephyr-workspace\u003c/code\u003e.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eTo build for ESP32-S3, you need to install the Zephyr SDK including the\n\u003ccode class=\"inline\"\u003extensa-espressif_esp32s3_zephyr-elf\u003c/code\u003e toolchain. The basic getting started\nguide instructions install all the toolchains. You only need to pay\nattention to specific toolchains if you want to do a custom install to save\ndisk space and bandwidth.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eThe first time you run \u003ccode class=\"inline\"\u003ewest flash\u003c/code\u003e on a board that had CircuitPython\ninstalled, you may need to activate the board's ESP32-S3 built in bootloader\nusing the button sequence: hold BOOT, press and release RESET, release BOOT.\nOnce you have installed the Zephyr bootloader, \u003ccode class=\"inline\"\u003ewest flash\u003c/code\u003e should work\nwithout needing to press any buttons.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eTo build with WiFi support, you will need to fetch the \u003ccode class=\"inline\"\u003ehal_espressif\u003c/code\u003e blobs\nif you have not already done so:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003ewest blobs fetch hal_espressif\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eClone the \u003ca href=\"https://github.com/samblenny/zphqst-03\"\u003ezphqst-03\u003c/a\u003e repository \ninto your Zephyr project directory. For example:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003ecd ~/code/zephyr-workspace\ngit clone https://github.com/samblenny/zphqst-03.git\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003cp\u003eTo learn more about setting up a Zephyr project directory, you can read the\nZephyr Project\n\u003ca href=\"https://docs.zephyrproject.org/latest/develop/getting_started/index.html\"\u003eGetting Started Guide\u003c/a\u003e\nor my\n\u003ca href=\"https://adafruit-playground.com/u/SamBlenny/pages/getting-started-with-zephyr-on-linux\"\u003eGetting Started with Zephyr on Linux\u003c/a\u003e\nPlayground guide.\u003c/p\u003e\n\n\u003cp\u003eThe directions here were written and tested for a terminal shell on Debian 12\nLinux. Probably they will work about the same on recent versions of Ubuntu.\nFor other operating systems, you may need to adapt the instructions to suit\nyour local setup.\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eZephyr version\u003c/strong\u003e: I've been keeping my local zephyr repo more or less up to\ndate with the current development version. I most recently tested the code for\nthis project with zephyrprojet-rtos/zephyr commit \u003ccode class=\"inline\"\u003ec60ffe1e1bc\u003c/code\u003e,\nwhich is a bit after their Zephyr 4.1.0 release.\u003c/p\u003e\n","metadata":{"markdown":"To prepare for building this project:\n\n1. To run Zephyr's `west` commandline tool, you need to set up a Zephyr\n   project, including a Python virtual environment and the zephyr git repo.\n   In my examples, the zephyr project directory is `~/code/zephyr-workspace`.\n\n2. To build for ESP32-S3, you need to install the Zephyr SDK including the\n   `xtensa-espressif_esp32s3_zephyr-elf` toolchain. The basic getting started\n   guide instructions install all the toolchains. You only need to pay\n   attention to specific toolchains if you want to do a custom install to save\n   disk space and bandwidth.\n\n3. The first time you run `west flash` on a board that had CircuitPython\n   installed, you may need to activate the board's ESP32-S3 built in bootloader\n   using the button sequence: hold BOOT, press and release RESET, release BOOT.\n   Once you have installed the Zephyr bootloader, `west flash` should work\n   without needing to press any buttons.\n\n4. To build with WiFi support, you will need to fetch the `hal_espressif` blobs\n   if you have not already done so:\n\n    ```\n    west blobs fetch hal_espressif\n    ```\n\n5. Clone the [zphqst-03](https://github.com/samblenny/zphqst-03) repository \n    into your Zephyr project directory. For example:\n\n    ```\n    cd ~/code/zephyr-workspace\n    git clone https://github.com/samblenny/zphqst-03.git\n    ```\n\nTo learn more about setting up a Zephyr project directory, you can read the\nZephyr Project\n[Getting Started Guide](https://docs.zephyrproject.org/latest/develop/getting_started/index.html)\nor my\n[Getting Started with Zephyr on Linux](https://adafruit-playground.com/u/SamBlenny/pages/getting-started-with-zephyr-on-linux)\nPlayground guide.\n\nThe directions here were written and tested for a terminal shell on Debian 12\nLinux. Probably they will work about the same on recent versions of Ubuntu.\nFor other operating systems, you may need to adapt the instructions to suit\nyour local setup.\n\n**Zephyr version**: I've been keeping my local zephyr repo more or less up to\ndate with the current development version. I most recently tested the code for\nthis project with zephyrprojet-rtos/zephyr commit `c60ffe1e1bc`,\nwhich is a bit after their Zephyr 4.1.0 release.\n"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eBuild \u0026amp; Flash Firmware\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eTo get started, you need to activate your Python venv that contains \u003ccode class=\"inline\"\u003ewest\u003c/code\u003e and\nchange into the \u003ccode class=\"inline\"\u003ezphqst-03\u003c/code\u003e directory within your Zephyr project directory.\u003c/p\u003e\n\n\u003cp\u003eFor example:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e$ cd ~/code/zephyr-workspace\n$ source .venv/bin/activate\n(.venv) $ cd zphqst-03\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eThe examples below use \u003ccode class=\"inline\"\u003emake\u003c/code\u003e in a terminal on Debian 12 to run commands for\nmake targets defined in the zphqst-03 \u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/Makefile\"\u003eMakefile\u003c/a\u003e.\nUsing \u003ccode class=\"inline\"\u003emake\u003c/code\u003e\navoids a lot of typing that would otherwise be required to provide commandline\noptions to \u003ccode class=\"inline\"\u003ewest\u003c/code\u003e.\u003c/p\u003e\n\n\u003cp\u003eTo build and flash the \u003ca href=\"https://github.com/samblenny/zphqst-03/tree/v0.3.0/app\"\u003eIoT toggle switch\u003c/a\u003e\napp:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003emake clean\nmake app\nmake flash\n\u003c/pre\u003e\u003c/code\u003e","metadata":{"markdown":"To get started, you need to activate your Python venv that contains `west` and\nchange into the `zphqst-03` directory within your Zephyr project directory.\n\nFor example:\n\n```\n$ cd ~/code/zephyr-workspace\n$ source .venv/bin/activate\n(.venv) $ cd zphqst-03\n```\n\nThe examples below use `make` in a terminal on Debian 12 to run commands for\nmake targets defined in the zphqst-03 [Makefile](https://github.com/samblenny/zphqst-03/blob/v0.3.0/Makefile).\nUsing `make`\navoids a lot of typing that would otherwise be required to provide commandline\noptions to `west`.\n\nTo build and flash the [IoT toggle switch](https://github.com/samblenny/zphqst-03/tree/v0.3.0/app)\napp:\n\n```\nmake clean\nmake app\nmake flash\n```"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eProvision WiFi \u0026amp; MQTT Settings\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eWhen you first install the app, in order to connect to the network, you must\nfirst provision the board with WiFi and MQTT login credentials. To do that,\nyou connect by USB serial to the Zephyr shell and use the \u003ccode class=\"inline\"\u003esettings\u003c/code\u003e command.\u003c/p\u003e\n\n\u003cp\u003eDepending on how you've used your Feather TFT board previously, it might\nalready have data written to the settings partition used by Zephyr's\nNon-Volatile Storage (NVS) subsystem. In that case, you'll need to erase the\npartition before writing the settings. (see Erasing the NVM Flash Partition section\nbelow)\u003c/p\u003e\n\n\u003cp\u003eYou can access the Zephyr shell with the command:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003emake monitor\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eThe \u003ccode class=\"inline\"\u003emake monitor\u003c/code\u003e command runs \u003ccode class=\"inline\"\u003ewest espressif monitor\u003c/code\u003e (keyboard shortcut to\nexit the serial monitor is \u003cstrong\u003eCtrl+]\u003c/strong\u003e). If you prefer a different serial\nmonitor program, like \u003ccode class=\"inline\"\u003etio\u003c/code\u003e or \u003ccode class=\"inline\"\u003escreen\u003c/code\u003e, you could try one of these:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003etio /dev/ttyACM0\nscreen -fn /dev/ttyACM0 115200\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eOnce you are in the Zephyr shell, you should see the \u003ccode class=\"inline\"\u003euart:~$\u003c/code\u003e prompt. If not,\npress the Enter key on your keyboard a time or two. At the prompt, to\nprovision your network credentials, you can write values to NVM flash using the\n\u003ccode class=\"inline\"\u003esettings\u003c/code\u003e shell command provided by Zephyr's\n\u003ca href=\"https://docs.zephyrproject.org/latest/services/storage/settings/index.html\"\u003eSettings\u003c/a\u003e\nsubsystem.\u003c/p\u003e\n\n\u003cp\u003eThe Zephyr Settings subsystem is a key-value store. For this application, I've\nconfigured it to use the\n\u003ca href=\"https://docs.zephyrproject.org/latest/services/storage/nvs/nvs.html\"\u003eNon-Volatile Storage (NVS)\u003c/a\u003e\nsubsystem for persistent storage. These three settings keys store the network\nprovisioning details for WiFi and MQTT:\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003e\n\u003ccode class=\"inline\"\u003ezq3/ssid\u003c/code\u003e: WiFi ssid (use quotes if it has spaces)\u003c/li\u003e\n\u003cli\u003e\n\u003ccode class=\"inline\"\u003ezq3/psk\u003c/code\u003e: WiFi WPA2-PSK passphrase (use quotes if it has spaces)\u003c/li\u003e\n\u003cli\u003e\n\u003ccode class=\"inline\"\u003ezq3/url\u003c/code\u003e: MQTT broker url: \u003ccode class=\"inline\"\u003emqtt[s]://\u0026lt;user\u0026gt;:\u0026lt;pass\u0026gt;@\u0026lt;hostname\u0026gt;/\u0026lt;topic\u0026gt;\u003c/code\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\n\u003cp\u003eHere is an example provisioning for a private test network with a local MQTT\nbroker listening on port 1883 of 192.168.0.100, with no encryption and\nanonymous connections enabled (username and password can be blank):\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003euart:~$ settings write string zq3/ssid MySSID\nuart:~$ settings write string zq3/psk \"my wifi passphrase\"\nuart:~$ settings write string zq3/url mqtt://:@192.168.0.100/test\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eThis second example is for an authenticated TLSv1.2 connection to Adafruit IO.\nNote how there's an \"s\" in \u003ccode class=\"inline\"\u003emqtts://\u003c/code\u003e, the username is 'User', the API key is\n\u003ccode class=\"inline\"\u003ekey\u003c/code\u003e, and the topic is \u003ccode class=\"inline\"\u003eUser/f/test\u003c/code\u003e:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003euart:~$ settings write string zq3/ssid MySSID\nuart:~$ settings write string zq3/psk \"my wifi passphrase\"\nuart:~$ settings write string zq3/url mqtts://User:Key@io.adafruit.com/User/f/test\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eIf you try writing the settings and get an error, check the section below\nabout erasing the NVM flash partition.\u003c/p\u003e\n","metadata":{"markdown":"When you first install the app, in order to connect to the network, you must\nfirst provision the board with WiFi and MQTT login credentials. To do that,\nyou connect by USB serial to the Zephyr shell and use the `settings` command.\n\nDepending on how you've used your Feather TFT board previously, it might\nalready have data written to the settings partition used by Zephyr's\nNon-Volatile Storage (NVS) subsystem. In that case, you'll need to erase the\npartition before writing the settings. (see Erasing the NVM Flash Partition section\nbelow)\n\nYou can access the Zephyr shell with the command:\n\n```\nmake monitor\n```\n\nThe `make monitor` command runs `west espressif monitor` (keyboard shortcut to\nexit the serial monitor is **Ctrl+]**). If you prefer a different serial\nmonitor program, like `tio` or `screen`, you could try one of these:\n\n```\ntio /dev/ttyACM0\nscreen -fn /dev/ttyACM0 115200\n```\n\nOnce you are in the Zephyr shell, you should see the `uart:~$` prompt. If not,\npress the Enter key on your keyboard a time or two. At the prompt, to\nprovision your network credentials, you can write values to NVM flash using the\n`settings` shell command provided by Zephyr's\n[Settings](https://docs.zephyrproject.org/latest/services/storage/settings/index.html)\nsubsystem.\n\nThe Zephyr Settings subsystem is a key-value store. For this application, I've\nconfigured it to use the\n[Non-Volatile Storage (NVS)](https://docs.zephyrproject.org/latest/services/storage/nvs/nvs.html)\nsubsystem for persistent storage. These three settings keys store the network\nprovisioning details for WiFi and MQTT:\n\n- `zq3/ssid`: WiFi ssid (use quotes if it has spaces)\n- `zq3/psk`: WiFi WPA2-PSK passphrase (use quotes if it has spaces)\n- `zq3/url`: MQTT broker url: `mqtt[s]://\u0026lt;user\u0026gt;:\u0026lt;pass\u0026gt;@\u0026lt;hostname\u0026gt;/\u0026lt;topic\u0026gt;`\n\nHere is an example provisioning for a private test network with a local MQTT\nbroker listening on port 1883 of 192.168.0.100, with no encryption and\nanonymous connections enabled (username and password can be blank):\n\n```\nuart:~$ settings write string zq3/ssid MySSID\nuart:~$ settings write string zq3/psk \"my wifi passphrase\"\nuart:~$ settings write string zq3/url mqtt://:@192.168.0.100/test\n```\n\nThis second example is for an authenticated TLSv1.2 connection to Adafruit IO.\nNote how there's an \"s\" in `mqtts://`, the username is 'User', the API key is\n`key`, and the topic is `User/f/test`:\n\n```\nuart:~$ settings write string zq3/ssid MySSID\nuart:~$ settings write string zq3/psk \"my wifi passphrase\"\nuart:~$ settings write string zq3/url mqtts://User:Key@io.adafruit.com/User/f/test\n```\n\nIf you try writing the settings and get an error, check the section below\nabout erasing the NVM flash partition."}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eConnect to WiFi \u0026amp; MQTT\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eOnce you have written the settings, you need to load them from NVM flash. You\ncan do this by either resetting the board (settings are loaded at boot) or by\nrunning the \u003ccode class=\"inline\"\u003eaio reload\u003c/code\u003e Zephyr shell command.\u003c/p\u003e\n\n\u003cp\u003eOnce the settings are loaded, you should see a \"Press BOOT button to connect\"\nmessage on the Feather TFT's screen.\u003c/p\u003e\n\n\u003cp\u003ePress the Feather TFT's Boot button. You should see a \"Connecting...\" message.\nOnce WiFi is up, the WiFi icon in the statusbar (top right) should turn from\ngray to green. When MQTT connects, you should see a large toggle switch widget\nin the center of the screen.\u003c/p\u003e\n\n\u003cp\u003eIf there are Wifi or MQTT connection errors, you should see an error message\non the Feather TFT's screen. To troubleshoot the problem, it's best to connect\nto the serial shell so you can see more detailed error messages.\u003c/p\u003e\n\n\u003cp\u003eTroubleshooting Checklist:\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\u003cp\u003eIs your WiFi router working? Can you connect to it with another device?\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eDoes your WiFi router use WPA2-PSK? If you need to use a different type of\nauthentication or encryption, you will need to change the code as it is\nhardcoded for WPA2-PSK.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eDoes your WiFi use a captive portal? In that case, it won't work.\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eAre the WiFi SSID and PSK passphrase settings correct? You can check this\nin the serial shell with:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003euart:~$ settings read zq3/ssid\n00000000: 4d 79 53 53 49 44 00                             |MySSID.          |\nuart:~$ settings read zq3/psk\n00000000: 6d 79 20 77 69 66 69 20  70 61 73 73 70 68 72 61 |my wifi  passphra|\n00000010: 73 65 00                                         |se.              |\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eIs your MQTT broker working? You can check this with the \u003ccode class=\"inline\"\u003emosquitto_pub\u003c/code\u003e\nand \u003ccode class=\"inline\"\u003emosquitto_sub\u003c/code\u003e MQTT command line tools for Linux. (check the following\nsections for more details on using mosquitto as a local MQTT broker).\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eIs the MQTT settings URL correct? The URL format is meant to match the\nformat of \u003ccode class=\"inline\"\u003emosquitto_pub -L \u0026lt;URL\u0026gt; ...\u003c/code\u003e and \u003ccode class=\"inline\"\u003emosquitto_sub -L \u0026lt;URL\u0026gt; ...\u003c/code\u003e\n(except this app always requires the \u003ccode class=\"inline\"\u003e:\u003c/code\u003e and \u003ccode class=\"inline\"\u003e@\u003c/code\u003e).\u003c/p\u003e\u003c/li\u003e\n\u003cli\u003e\u003cp\u003eIs your account being throttled by the MQTT broker because of exceeding\nrate limits? For example, if you use Adafruit IO, you can read about\nthrottling in the\n\u003ca href=\"https://io.adafruit.com/api/docs/mqtt.html#adafruit-io-mqtt-api\"\u003eAdafruit IO MQTT API\u003c/a\u003e\nweb docs.\u003c/p\u003e\u003c/li\u003e\n\u003c/ol\u003e\n","metadata":{"markdown":"Once you have written the settings, you need to load them from NVM flash. You\ncan do this by either resetting the board (settings are loaded at boot) or by\nrunning the `aio reload` Zephyr shell command.\n\nOnce the settings are loaded, you should see a \"Press BOOT button to connect\"\nmessage on the Feather TFT's screen.\n\nPress the Feather TFT's Boot button. You should see a \"Connecting...\" message.\nOnce WiFi is up, the WiFi icon in the statusbar (top right) should turn from\ngray to green. When MQTT connects, you should see a large toggle switch widget\nin the center of the screen.\n\nIf there are Wifi or MQTT connection errors, you should see an error message\non the Feather TFT's screen. To troubleshoot the problem, it's best to connect\nto the serial shell so you can see more detailed error messages.\n\nTroubleshooting Checklist:\n\n1. Is your WiFi router working? Can you connect to it with another device?\n\n2. Does your WiFi router use WPA2-PSK? If you need to use a different type of\n   authentication or encryption, you will need to change the code as it is\n   hardcoded for WPA2-PSK.\n\n3. Does your WiFi use a captive portal? In that case, it won't work.\n\n4. Are the WiFi SSID and PSK passphrase settings correct? You can check this\n   in the serial shell with:\n\n    ```\n    uart:~$ settings read zq3/ssid\n    00000000: 4d 79 53 53 49 44 00                             |MySSID.          |\n    uart:~$ settings read zq3/psk\n    00000000: 6d 79 20 77 69 66 69 20  70 61 73 73 70 68 72 61 |my wifi  passphra|\n    00000010: 73 65 00                                         |se.              |\n    ```\n\n5. Is your MQTT broker working? You can check this with the `mosquitto_pub`\n   and `mosquitto_sub` MQTT command line tools for Linux. (check the following\n   sections for more details on using mosquitto as a local MQTT broker).\n\n6. Is the MQTT settings URL correct? The URL format is meant to match the\n   format of `mosquitto_pub -L \u0026lt;URL\u0026gt; ...` and `mosquitto_sub -L \u0026lt;URL\u0026gt; ...`\n   (except this app always requires the `:` and `@`).\n\n7. Is your account being throttled by the MQTT broker because of exceeding\n   rate limits? For example, if you use Adafruit IO, you can read about\n   throttling in the\n   [Adafruit IO MQTT API](https://io.adafruit.com/api/docs/mqtt.html#adafruit-io-mqtt-api)\n   web docs.\n"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eErasing the NVM Flash Partition\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eThe settings API uses the NVM backend with the \u003ccode class=\"inline\"\u003estorage\u003c/code\u003e partition that is\ndefined as one of Espressif's default partitions in\n\u003ccode class=\"inline\"\u003ezephyr/dts/common/espressif/partitions_0x0_amp_4M.dtsi\u003c/code\u003e. My app's\nDevicetree configuration gives this partition the label of \"settings\".\u003c/p\u003e\n\n\u003cp\u003eDepending on how you used your Feather TFT ESP32-S3 board previously, there\nmay be existing data in the storage partition. In that case, you might need\nto erase it.\u003c/p\u003e\n\n\u003cp\u003eOne option to erase the NVM partition would be to erase all the flash with the\n\u003ca href=\"https://adafruit.github.io/Adafruit_WebSerial_ESPTool/\"\u003eAdafruit ESPTool\u003c/a\u003e\nESP32 web flasher tool, then re-program the bootloader and firmware using\n\u003ccode class=\"inline\"\u003ewest flash\u003c/code\u003e.\u003c/p\u003e\n\n\u003cp\u003eYou could also use Zephyr's \u003ccode class=\"inline\"\u003eflash_map list\u003c/code\u003e shell command to find the\npartition labeled \"settings\" and determine its start offset and size. In the\nexample below, those are 0x3b0000 and 0x30000. Then, you could use the\n\u003ccode class=\"inline\"\u003eflash erase\u003c/code\u003e shell command to erase the flash blocks for that partition.\u003c/p\u003e\n\n\u003cp\u003eFor example:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003euart:~$ flash_map list\nID | Device     | Device Name               | Label          | Offset   | Size\n----------------------------------------------------------------------------------\n 0   0x3c0a91e8   flash-controller@60002000   mcuboot          0x0        0x20000\n 1   0x3c0a91e8   flash-controller@60002000   image-0          0x20000    0x150000\n 2   0x3c0a91e8   flash-controller@60002000   image-1          0x170000   0x150000\n 3   0x3c0a91e8   flash-controller@60002000   image-0-appcpu   0x2c0000   0x70000\n 4   0x3c0a91e8   flash-controller@60002000   image-1-appcpu   0x330000   0x70000\n 5   0x3c0a91e8   flash-controller@60002000   image-0-lpcore   0x3a0000   0x8000\n 6   0x3c0a91e8   flash-controller@60002000   image-1-lpcore   0x3a8000   0x8000\n 7   0x3c0a91e8   flash-controller@60002000   settings         0x3b0000   0x30000\n 8   0x3c0a91e8   flash-controller@60002000   image-scratch    0x3e0000   0x1f000\n 9   0x3c0a91e8   flash-controller@60002000   coredump         0x3ff000   0x1000\nuart:~$\nuart:~$ flash erase flash-controller@60002000 0x3b0000 0x30000\nErase success.\nuart:~$ settings list\nuart:~$\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eIf you want to explore the various flash related shell commands, you can try\nreading their help messages:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003euart:~$ flash_map -h\nuart:~$ flash -h\nuart:~$ settings -h\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eTo see the Kconfig options that enable those shell commands, check out\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/prj.conf\"\u003eapp/prj.conf\u003c/a\u003e.\u003c/p\u003e\n","metadata":{"markdown":"The settings API uses the NVM backend with the `storage` partition that is\ndefined as one of Espressif's default partitions in\n`zephyr/dts/common/espressif/partitions_0x0_amp_4M.dtsi`. My app's\nDevicetree configuration gives this partition the label of \"settings\".\n\nDepending on how you used your Feather TFT ESP32-S3 board previously, there\nmay be existing data in the storage partition. In that case, you might need\nto erase it.\n\nOne option to erase the NVM partition would be to erase all the flash with the\n[Adafruit ESPTool](https://adafruit.github.io/Adafruit_WebSerial_ESPTool/)\nESP32 web flasher tool, then re-program the bootloader and firmware using\n`west flash`.\n\nYou could also use Zephyr's `flash_map list` shell command to find the\npartition labeled \"settings\" and determine its start offset and size. In the\nexample below, those are 0x3b0000 and 0x30000. Then, you could use the\n`flash erase` shell command to erase the flash blocks for that partition.\n\nFor example:\n\n```\nuart:~$ flash_map list\nID | Device     | Device Name               | Label          | Offset   | Size\n----------------------------------------------------------------------------------\n 0   0x3c0a91e8   flash-controller@60002000   mcuboot          0x0        0x20000\n 1   0x3c0a91e8   flash-controller@60002000   image-0          0x20000    0x150000\n 2   0x3c0a91e8   flash-controller@60002000   image-1          0x170000   0x150000\n 3   0x3c0a91e8   flash-controller@60002000   image-0-appcpu   0x2c0000   0x70000\n 4   0x3c0a91e8   flash-controller@60002000   image-1-appcpu   0x330000   0x70000\n 5   0x3c0a91e8   flash-controller@60002000   image-0-lpcore   0x3a0000   0x8000\n 6   0x3c0a91e8   flash-controller@60002000   image-1-lpcore   0x3a8000   0x8000\n 7   0x3c0a91e8   flash-controller@60002000   settings         0x3b0000   0x30000\n 8   0x3c0a91e8   flash-controller@60002000   image-scratch    0x3e0000   0x1f000\n 9   0x3c0a91e8   flash-controller@60002000   coredump         0x3ff000   0x1000\nuart:~$\nuart:~$ flash erase flash-controller@60002000 0x3b0000 0x30000\nErase success.\nuart:~$ settings list\nuart:~$\n```\n\nIf you want to explore the various flash related shell commands, you can try\nreading their help messages:\n\n```\nuart:~$ flash_map -h\nuart:~$ flash -h\nuart:~$ settings -h\n```\n\nTo see the Kconfig options that enable those shell commands, check out\n[app/prj.conf](https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/prj.conf)."}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eSource Code Quick Tour\u003c/h2\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n        \u003cp\u003e\u003cstrong\u003eDevicetree:\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/boards/adafruit/feather_tft_esp32s3/buttons.dtsi\" target=\"_blank\"\u003eboards/adafruit/feather_tft_esp32s3/buttons.dtsi\u003c/a\u003e: Button config\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/boards/adafruit/feather_tft_esp32s3/mipi_st7789v.dtsi\" target=\"_blank\"\u003eboards/adafruit/feather_tft_esp32s3/mipi_st7789v.dtsi\u003c/a\u003e: Display config including comments explaining 4 hardware rotation options\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/boards/adafruit/feather_tft_esp32s3/feather_tft_esp32s3_procpu.dts\" target=\"_blank\"\u003eboards/adafruit/feather_tft_esp32s3/feather_tft_esp32s3_procpu.dts\u003c/a\u003e: Peripheral config including \"gpio-hog\" property for display and backlight power\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/boards/adafruit/feather_tft_esp32s3/feather_tft_esp32s3-pinctrl.dtsi\" target=\"_blank\"\u003eboards/adafruit/feather_tft_esp32s3/feather_tft_esp32s3-pinctrl.dtsi\u003c/a\u003e: GPIO config for UART, I2C, and SPI\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/boards/adafruit/feather_tft_esp32s3/feather_connector.dtsi\" target=\"_blank\"\u003eboards/adafruit/feather_tft_esp32s3/feather_connector.dtsi\u003c/a\u003e: GPIO config for Feather header pins\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/app.overlay\" target=\"_blank\"\u003eapp/app.overlay\u003c/a\u003e: Enables NVM storage partition and random number generator\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eKConfig \u0026amp; CMake:\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/CMakeLists.txt\" target=\"_blank\"\u003eapp/CMakeLists.txt\u003c/a\u003e: Selects additional C files (besides \u003ccode\u003emain.c\u003c/code\u003e) to include in the build\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/prj.conf\" target=\"_blank\"\u003eapp/prj.conf\u003c/a\u003e: Main config file for Zephyr features. This has many memory allocation tuning settings that were critical for getting the networking to run reliably. See comments for how to enable debug logging, stack usage monitoring, and heap usage monitoring.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eC Code:\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/main.c\" target=\"_blank\"\u003eapp/src/main.c\u003c/a\u003e: \u003cstrong\u003eHardware initialization\u003c/strong\u003e, \u003cstrong\u003eevent handler callback\u003c/strong\u003e functions (WiFi, MQTT, LVLG, settings), and\u0026nbsp;\u003cstrong\u003emain event loop\u003c/strong\u003e with \u003cstrong\u003estate machine\u003c/strong\u003e to track network status\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/zq3.h\" target=\"_blank\"\u003eapp/src/zq3.h\u003c/a\u003e: \u003cstrong\u003eEnum and struct definitions\u003c/strong\u003e\u0026nbsp;used by callbacks and event loop state machine.\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/zq3_cert.h\" target=\"_blank\"\u003eapp/src/zq3_cert.h\u003c/a\u003e: String literals for compiling PEM format \u003cstrong\u003eTLS CA certificates\u003c/strong\u003e into the firmware. This includes my self-signed CA test certificate (which you can replace with one of your own) along with the \"DigiCert Global Root G2\" and \"GeoTrust TLS RSA CA G1\" certificates for use with Adafruit IO.\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/zq3_dns.c\" target=\"_blank\"\u003eapp/src/zq3_dns.c\u003c/a\u003e + \u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/zq3_dns.h\" target=\"_blank\"\u003ezq3_dns.h\u003c/a\u003e: \u003cstrong\u003eResolve DNS hostname\u003c/strong\u003e string for MQTT broker to an IPv4 address (also works for converting IP address string to the IPv4 address struct format)\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/zq3_lvgl.c\" target=\"_blank\"\u003eapp/src/zq3_lvgl.c\u003c/a\u003e + \u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/zq3_lvgl.h\" target=\"_blank\"\u003eapp/src/zq3_lvgl.h\u003c/a\u003e: \u003cstrong\u003eLVGL graphical user interface\u003c/strong\u003e: background color, WiFi status icon, text status message, large toggle switch widget, etc.\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/zq3_mqtt.c\" target=\"_blank\"\u003eapp/src/zq3_mqtt.c\u003c/a\u003e + \u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/zq3_mqtt.h\" target=\"_blank\"\u003eapp/src/zq3_mqtt.h\u003c/a\u003e: \u003cstrong\u003eMQTT\u003c/strong\u003e broker config, TLS certificate registration, connection setup, publish and subscribe, etc.\u003c/li\u003e\n\u003cli\u003e\u0026nbsp;\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/zq3_url.c\" target=\"_blank\"\u003eapp/src/zq3_url.c\u003c/a\u003e + \u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/zq3_url.h\" target=\"_blank\"\u003eapp/src/zq3_url.h\u003c/a\u003e: MQTT broker \u003cstrong\u003eURL string parser\u003c/strong\u003e. This is meant to work with the Zephyr Settings API. URL format is meant to match the format used by \u003ccode\u003emosquitto_sub -L\u003c/code\u003e and \u003ccode\u003emosquitto_pub -L\u003c/code\u003e to assist with testing on a private network, with Adafruit IO, or using some other MQTT broker (might need to change CA cert).\u003c/li\u003e\n\u003cli\u003e\n\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/zq3_wifi.c\" target=\"_blank\"\u003eapp/src/zq3_wifi.c\u003c/a\u003e + \u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/src/zq3_wifi.h\" target=\"_blank\"\u003eapp/src/zq3_wifi.h\u003c/a\u003e: \u003cstrong\u003eWiFi network connect and disconnect\u003c/strong\u003e using Zephyr's \u003cstrong\u003eNetwork Manager\u003c/strong\u003e API.\u003c/li\u003e\n\u003c/ul\u003e\n      \n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eBackground Knowledge\u003c/h2\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eC Language Features\u003c/h3\u003e\n\u003cp\u003eTo use Zephyr APIs, you need to understand some C features and patterns for combining them: structs, functions, pointers, function pointers, struct pointers, typedef, compound literals, etc.\u003c/p\u003e\n\u003cp\u003eExperienced C programmers will probably be familiar with this stuff already. But, for people new to C, these are some concepts and search terms you may want to explore:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cstrong\u003eStruct\u003c/strong\u003e: Structs are a way of grouping related data, kind of like a Python class minus the methods. The main utility of structs is they make it easier to pass data around between functions. Struct definitions commonly happen in header files (\u003ccode\u003e.h\u003c/code\u003e), and they might look like\u0026nbsp;\u003ccode\u003estruct {int foo; char buf[32];};\u003c/code\u003e or perhaps \u003ccode\u003etypedef struct {int foo; char buf[32];} foo_t;\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003ePointer\u003c/strong\u003e: Basically, a pointer is a special kind of integer that holds a memory address where some other larger thing is stored. Instead of spending lots of memory and CPU time to copy the larger thing, functions can take pointers as arguments. \"Dereferencing\" a pointer is how C uses a pointer to find the thing it points to. Pointer operations are commonly spelled with\u0026nbsp;\u003ccode\u003e*\u003c/code\u003e, \u003ccode\u003e\u0026amp;\u003c/code\u003e, and \u003ccode\u003e-\u0026gt;\u003c/code\u003e.\u0026nbsp;Also, the names of arrays, like \u003ccode\u003egreet\u003c/code\u003e in \u003ccode\u003echar greet[] = \"hello\";\u003c/code\u003e, are pointers. So it wouldn't make sense to say \u003ccode\u003e\u0026amp;greet\u003c/code\u003e, because it's already a pointer.\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003eFunction\u003c/strong\u003e: Functions take arguments (int, float, pointer, or whatever) and return a result. The number and type of arguments, and the type of the return value, make a kind of fingerprint that the C compiler uses to check that function calls match function declarations. Trying to call a function with the wrong type or number of arguments is an error. Some related terms are \"function signature\" and \"function prototype\".\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003eFunction Pointer\u003c/strong\u003e: When you write C code like \u003ccode\u003eprintf(\"hello, world\\n\");\u003c/code\u003e, you're making a call to the function named \u003ccode\u003eprintf\u003c/code\u003e. But you can also use use \u003ccode\u003eprintf\u003c/code\u003e as the argument to a function, like \u003ccode\u003efoo(printf);\u003c/code\u003e. In that case, \u003ccode\u003efoo()\u003c/code\u003e is a function that takes a function pointer as its argument, and \u003ccode\u003eprintf\u003c/code\u003e is the function pointer. A common use for function pointers is to tell some library code that you want it to call a certain function when something happens in the future. Effectively, it's a way to pass an algorithm or procedure (as opposed to just data) as the argument to a function.\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003eTypedef\u003c/strong\u003e: C allows you to create named types that act as an alias for types like structs, enums, function pointers, and so on. It's common to use \u003ccode\u003etypedef\u003c/code\u003e when the spelling out the full type each time you want to use it would be awkwardly long, as is often the case for structs and function pointers.\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003ePreprocessor Macro\u003c/strong\u003e: Macros are another C feature that can be used to avoid typing out awkwardly long or complicated things. Zephyr APIs sometimes use macros for registering callback functions. Macro names are often spelled in all caps, like, \u003ccode\u003eI_AM_A_MACRO(and, these, are, my, arguments);\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003eEvent Hander Callback Function\u003c/strong\u003e: Event based library API's, such as the ones Zephyr provides for networking and graphics, allow you to register \"callback\" functions that should be called to handle events that may happen in the future. The API function for registering event handlers will take a function pointer, often with a name ending in \u003ccode\u003e_cb\u003c/code\u003e, which is expected to match a particular function signature (check the library API docs). Usually, the callback function signature will provide a pointer to a struct as one of its arguments. Your code is expected to dereference the struct pointer and examine its contents to get data related to the event.\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003eCompound Literals\u003c/strong\u003e: Sometimes, to use Zephyr API's (e.g. making an MQTT connection), you are expected to create structs and initialize them with fairly elaborate configurations. The C99 feature called \"\u003ca href=\"https://gcc.gnu.org/onlinedocs/gcc/Compound-Literals.html\" target=\"_blank\"\u003ecompound literals\u003c/a\u003e\" can be a helpful way to do this.\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003eScope and Use After Free\u003c/strong\u003e: When using library APIs that take pointers, it's very important to consider the scope and lifetime of structs or array buffers if you use them with pointers. For example, suppose you have a function called \u003ccode\u003efoo()\u003c/code\u003e that declares a struct called \u003ccode\u003ebar\u003c/code\u003e as a local variable. What happens if \u003ccode\u003efoo()\u003c/code\u003e saves a pointer to \u003ccode\u003ebar\u003c/code\u003e (\u003ccode\u003e\u0026amp;bar\u003c/code\u003e) somewhere and then returns? That pointer to \u003ccode\u003ebar\u003c/code\u003e will now be dangerously pointing to the stack address where \u003ccode\u003ebar\u003c/code\u003e used to be located, but since \u003ccode\u003efoo()\u003c/code\u003e has returned, that location on the stack is probably being used for something else (not \u003ccode\u003ebar\u003c/code\u003e!). Similar problems can happen with dynamic memory allocation (\u003ccode\u003emalloc()\u003c/code\u003e, \u003ccode\u003efree()\u003c/code\u003e, etc).\u003c/li\u003e\n\u003cli\u003e\n\u003cstrong\u003eNull Pointers\u003c/strong\u003e: A pointer variable that contains the special value, \u003ccode\u003eNULL\u003c/code\u003e, is called a null pointer. Many C bugs are caused by attempting to dereference null pointers. It's important for C functions that take pointer arguments to consider how they should behave if they receive a null pointer. Failing to check for null pointers may lead to the Zephyr kernel stopping your application with a hard fault error.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eTo learn more about the C language, the authoritative reference book is the \"C Programming Language, 2nd Edition\" by Kernighan and Ritchie, sometimes called \"K\u0026amp;R\", or the K\u0026amp;R C book. It's very good for understanding basic concepts of the C language, although some of the examples may show usage patterns that are not considered best practice now. To learn about newer C99 features, the GNU \u003ca href=\"https://gcc.gnu.org/onlinedocs/gcc/index.html\" target=\"_blank\"\u003egcc compiler documentation\u003c/a\u003e might be useful (but it's a long read).\u003c/p\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n        \u003ch3\u003eZephyr Troubleshooting\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003eTL;DR:\u003c/strong\u003e To get a Zephyr IoT app working reliably, you will probably need to carefully tune various stack, heap, and buffer sizes in your Kconfig options. That will be easier if you learn how to use Zephyr Shell diagnostic commands and how to turn on debug logging (more complex than it sounds).\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eLong Version:\u003c/strong\u003e While developing this app over the last month, I spent a significant amount of time diagnosing and debugging issues that turned out to be ultimately caused by memory allocation failures. Zephyr is built to use several threads, each with their own stack, along with various buffers and heaps reserved for different purposes. To figure out what's going on with memory allocation, Zephyr provides configuration features to enable measurements and Zephyr Shell commands for checking the measurements. There are also some configuration options that enable debug logging for memory allocation failures.\u003c/p\u003e\n\u003cp\u003eIf you want to build a moderately complex app in Zephyr, you will probably need to get good at tuning memory allocations and enabling the different types of debug logging:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eZephyr's\u0026nbsp;\u003ca href=\"https://docs.zephyrproject.org/latest/build/kconfig/menuconfig.html\" target=\"_blank\"\u003eMenuconfig\u003c/a\u003e tool for interactively setting build configuration options has a search function that works like the one in the vim editor (type \u003ccode\u003e/\u003c/code\u003e to begin the search). For example, you can do searches like \u003ccode\u003e/log_level\u003c/code\u003e,\u0026nbsp; \u003ccode\u003e/mbedtls\u003c/code\u003e, \u003ccode\u003e/stack\u003c/code\u003e, or \u003ccode\u003e/mem_pool\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eMenuconfig has a help feature for reading descriptions of config options. When you have an option selected in the menuconfig tree view, you can press \u003ccode\u003e?\u003c/code\u003e to bring up the help text for the selected option.\u003c/li\u003e\n\u003cli\u003eLook in my\u0026nbsp;\u003ca href=\"https://github.com/samblenny/zphqst-03/blob/v0.3.0/app/prj.conf\" target=\"_blank\"\u003eapp/prj.conf\u003c/a\u003e file for comments about configuration features and shell commands that are useful for enabling debug logging and tuning memory use.\u003c/li\u003e\n\u003cli\u003eIt can be helpful to use a text search tool that can recursively search all the files in a directory for regular expression patterns. Many IDEs and programming editors have such search features. I'm partial to using\u0026nbsp;\u003ccode\u003egrep\u003c/code\u003e. For example, I often used\u0026nbsp;\u003ccode\u003egrep -r 'some regex pattern' *\u003c/code\u003e in the top level of the zephyr repo directory to find samples, tests, or kernel code that mentioned particular config options I was trying to understand.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eAt this point, you might be wondering, \"Why worry about memory allocation on an ESP32-S3 soc that has 2MB of PSRAM?\" Good question. Indeed, it would be great if we could use the PSRAM for network buffers. But, alas, that is not currently practical.\u003c/p\u003e\n\u003cp\u003eThere are Zephyr configuration options for enabling \"SPIRAM\" support on the ESP32-S3 and for using SPIRAM for heap and network buffers. However, those features don't currently work. When I tried it, the WiFi system failed to initialize due to memory allocation errors. At the time I'm writing this (late March 2025), there are at least two \u003ca href=\"https://github.com/zephyrproject-rtos/zephyr/issues/86722\" target=\"_blank\"\u003eopen\u003c/a\u003e \u003ca href=\"https://github.com/zephyrproject-rtos/zephyr/issues/67759\" target=\"_blank\"\u003eissues\u003c/a\u003e and one \u003ca href=\"https://github.com/zephyrproject-rtos/zephyr/pull/86049\" target=\"_blank\"\u003epull request\u003c/a\u003e mentioning ESP32-S3 SPIRAM problems.\u003c/p\u003e\n      \n\n","metadata":{}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch2\u003eMQTT Testing with Mosquitto \u0026amp; OpenSSL\u003c/h2\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eMosquitto is the name of an open source project that provides an MQTT broker\nand MQTT client programs to publish and subscribe from the command line.\u003c/p\u003e\n","metadata":{"markdown":"Mosquitto is the name of an open source project that provides an MQTT broker\nand MQTT client programs to publish and subscribe from the command line.\n"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eEasy Unencrypted Version\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eFor developing an MQTT application on Zephyr, it can be helpful to run your own\nMQTT broker locally on a private network (Raspberry Pi, Debian box, or\nwhatever).\u003c/p\u003e\n\n\u003cp\u003eWriting the Zephyr code for an MQTT client application involves many steps.\nAttempting to do all of that at once is challenging. You might find it\neasier to start with basic unencrypted (non-TLS) MQTT connections on a private\nnetwork, then incrementally add encryption and authentication support.\u003c/p\u003e\n\n\u003cp\u003e\u003cstrong\u003eCAUTION: Using this type of configuration on public WiFi is not safe. In the\nexample here, I'm using a private Ethernet LAN (no internet gateway) with a\ndedicated WiFi AP that I use for testing.\u003c/strong\u003e\u003c/p\u003e\n\n\u003cp\u003eThis is how I set up a Debian box with the \u003ccode class=\"inline\"\u003emosquitto\u003c/code\u003e MQTT broker for\nunencrypted and unauthenticated access on my WiFi test network:\u003c/p\u003e\n\n\u003cp\u003eInstall packages and configure the mosquitto server for manual start:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e$ sudo apt install mosquitto mosquitto-clients\n$ sudo systemctl stop mosquitto\n$ sudo systemctl disable mosquitto\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eCheck the IP address assigned to my WiFi interface with \u003ccode class=\"inline\"\u003ehostname -I\u003c/code\u003e. In this\ncase, I have a IP 10.0.x.x address for the private test LAN and a 192.168.0.x\naddress for the main internet connected WiFi router:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e$ hostname -I\n10.0.0.10 192.168.0.100\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eReconfigure \u003ccode class=\"inline\"\u003emosquitto\u003c/code\u003e MQTT broker to listen only on the private LAN:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e$ cat \u0026lt;\u0026lt;EOF | sudo tee /etc/mosquitto/conf.d/LAN-listener.conf\npersistence false\nallow_anonymous true\nlistener 1883 10.0.0.10\nEOF\n$ sudo systemctl start mosquitto\n\u003c/pre\u003e\u003c/code\u003e","metadata":{"markdown":"For developing an MQTT application on Zephyr, it can be helpful to run your own\nMQTT broker locally on a private network (Raspberry Pi, Debian box, or\nwhatever).\n\nWriting the Zephyr code for an MQTT client application involves many steps.\nAttempting to do all of that at once is challenging. You might find it\neasier to start with basic unencrypted (non-TLS) MQTT connections on a private\nnetwork, then incrementally add encryption and authentication support.\n\n**CAUTION: Using this type of configuration on public WiFi is not safe. In the\nexample here, I'm using a private Ethernet LAN (no internet gateway) with a\ndedicated WiFi AP that I use for testing.**\n\nThis is how I set up a Debian box with the `mosquitto` MQTT broker for\nunencrypted and unauthenticated access on my WiFi test network:\n\nInstall packages and configure the mosquitto server for manual start:\n```\n$ sudo apt install mosquitto mosquitto-clients\n$ sudo systemctl stop mosquitto\n$ sudo systemctl disable mosquitto\n```\n\nCheck the IP address assigned to my WiFi interface with `hostname -I`. In this\ncase, I have a IP 10.0.x.x address for the private test LAN and a 192.168.0.x\naddress for the main internet connected WiFi router:\n```\n$ hostname -I\n10.0.0.10 192.168.0.100\n```\n\nReconfigure `mosquitto` MQTT broker to listen only on the private LAN:\n```\n$ cat \u0026lt;\u0026lt;EOF | sudo tee /etc/mosquitto/conf.d/LAN-listener.conf\npersistence false\nallow_anonymous true\nlistener 1883 10.0.0.10\nEOF\n$ sudo systemctl start mosquitto\n```"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eUsing \u003ccode\u003emosquitto_pub\u003c/code\u003e and \u003ccode\u003emosquitto_sub\u003c/code\u003e\n\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eOn Debian, if you install the \u003ccode class=\"inline\"\u003emosquitto-clents\u003c/code\u003e package, you can publish and\nsubscribe to MQTT topics from the command line. For example, assuming you were\nrunning an MQTT broker listening on IP address 192.168.0.100, you could start\ntwo terminal windows (or use \u003ccode class=\"inline\"\u003etmux\u003c/code\u003e), and do this:\u003c/p\u003e\n\n\u003cp\u003eTerminal 1 (use Ctrl-C to disconnect \u003ccode class=\"inline\"\u003emosquitto_sub\u003c/code\u003e when done):\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e$ mosquitto_sub --debug -v -L mqtt://192.168.0.100/test\nClient (null) sending CONNECT\nClient (null) received CONNACK (0)\nClient (null) sending SUBSCRIBE (Mid: 1, Topic: test, QoS: 0, Options: 0x00)\nClient (null) received SUBACK\nSubscribed (mid: 1): 0\nClient (null) received PUBLISH (d0, q0, r0, m0, 'test', ... (11 bytes))\ntest hello world\n^CClient (null) sending DISCONNECT\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eTerminal 2:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e$ mosquitto_pub --debug -L mqtt://192.168.0.100/test -m \"hello world\"\nClient (null) sending CONNECT\nClient (null) received CONNACK (0)\nClient (null) sending PUBLISH (d0, q0, r0, m1, 'test', ... (11 bytes))\nClient (null) sending DISCONNECT\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eIf you wanted to test an authenticated and TLS encrypted connection to the\nAdafruit IO MQTT broker, you could do something like this (replacing \u003ccode class=\"inline\"\u003e$USER\u003c/code\u003e\nand \u003ccode class=\"inline\"\u003e$KEY\u003c/code\u003e with your AIO username and API key):\u003c/p\u003e\n\n\u003cp\u003eTerminal 1:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e$ mosquitto_sub -L mqtts://$USER:$KEY@io.adafruit.com/$USER/f/test\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eTerminal 2:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e$ mosquitto_pub -L mqtts://$USER:$KEY@io.adafruit.com/$USER/f/test -m 1\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003e\u003cstrong\u003eNote:\u003c/strong\u003e In the MQTT broker URL, the URL scheme, \u003ccode class=\"inline\"\u003emqtt://\u003c/code\u003e or \u003ccode class=\"inline\"\u003emqtts://\u003c/code\u003e, implicitly selects the port. The \u003ccode class=\"inline\"\u003emqtt\u003c/code\u003e scheme defaults to port 1883. The \u003ccode class=\"inline\"\u003emqtts\u003c/code\u003e\nscheme defaults to port 8883.\u003c/p\u003e\n","metadata":{"markdown":"On Debian, if you install the `mosquitto-clents` package, you can publish and\nsubscribe to MQTT topics from the command line. For example, assuming you were\nrunning an MQTT broker listening on IP address 192.168.0.100, you could start\ntwo terminal windows (or use `tmux`), and do this:\n\nTerminal 1 (use Ctrl-C to disconnect `mosquitto_sub` when done):\n```\n$ mosquitto_sub --debug -v -L mqtt://192.168.0.100/test\nClient (null) sending CONNECT\nClient (null) received CONNACK (0)\nClient (null) sending SUBSCRIBE (Mid: 1, Topic: test, QoS: 0, Options: 0x00)\nClient (null) received SUBACK\nSubscribed (mid: 1): 0\nClient (null) received PUBLISH (d0, q0, r0, m0, 'test', ... (11 bytes))\ntest hello world\n^CClient (null) sending DISCONNECT\n```\n\nTerminal 2:\n```\n$ mosquitto_pub --debug -L mqtt://192.168.0.100/test -m \"hello world\"\nClient (null) sending CONNECT\nClient (null) received CONNACK (0)\nClient (null) sending PUBLISH (d0, q0, r0, m1, 'test', ... (11 bytes))\nClient (null) sending DISCONNECT\n```\n\nIf you wanted to test an authenticated and TLS encrypted connection to the\nAdafruit IO MQTT broker, you could do something like this (replacing `$USER`\nand `$KEY` with your AIO username and API key):\n\nTerminal 1:\n```\n$ mosquitto_sub -L mqtts://$USER:$KEY@io.adafruit.com/$USER/f/test\n```\n\nTerminal 2:\n```\n$ mosquitto_pub -L mqtts://$USER:$KEY@io.adafruit.com/$USER/f/test -m 1\n```\n\n**Note:** In the MQTT broker URL, the URL scheme, `mqtt://` or `mqtts://`, implicitly selects the port. The `mqtt` scheme defaults to port 1883. The `mqtts`\nscheme defaults to port 8883."}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eLevel up to MQTT over TLS\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eTo upgrade your local mosquitto MQTT broker to TLS, first you need to make\nsure you have the \u003ccode class=\"inline\"\u003eopenssl\u003c/code\u003e command line tool installed. Try \u003ccode class=\"inline\"\u003eopenssl version\u003c/code\u003e\nfrom a terminal. If that doesn't work, you can install openssl with:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003esudo apt install openssl\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eOnce you have openssl, you'll need to make a CA certificate and private key,\nthen use those to sign a server key, then install those in the mosquitto\nconfiguration directory.\u003c/p\u003e\n\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eChange to a temporary working directory\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003emkdir ~/ca-cert\ncd ~/ca-cert\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eCreate a Certificate Authority (CA) private key and certificate file\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eopenssl req -newkey rsa:2048 -noenc -x509 -days 730 -extensions v3_ca \\\n -subj \"/C=US/O=MyCA/CN=My Self-Signed CA\" -keyout ca.key -out ca.crt\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eCAUTION: The mbed TLS certificate parser appears to care about the contents\nof the subject fields and perhaps the validity period. If your connection\ncloses mysteriously and you see a \u003ccode class=\"inline\"\u003e-0x2180\u003c/code\u003e error code from the mbed TLS\ndebug logging, it may be unhappy with the subject or days. Or, it's also possible\nthe problem could be due to a memory allocation failure. Certificate parsing uses\nmbed TLS heap space.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eCreate a server private key and Certificate Signing Request (CSR). The\nstuff with \u003ccode class=\"inline\"\u003e$(hostname -I|awk '{print $1}')\u003c/code\u003e is a way to automatically\nfill in the first hostname reported by \u003ccode class=\"inline\"\u003ehostname -I\u003c/code\u003e. You could just type\nin \u003ccode class=\"inline\"\u003e10.0.0.10\u003c/code\u003e or whatever instead if you wanted.\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eopenssl req -newkey rsa:2048 -noenc \\\n -subj \"/CN=$(hostname -I|awk '{print $1}')\" \\\n -keyout server.key -out server.csr\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eUse your CA's certificate and private key to sign the CSR\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eopenssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \\\n -CAcreateserial -copy_extensions copy -days 365 -out server.crt\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eCheck contents of the PEM files\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eopenssl x509 -in ca.crt -text -noout\nopenssl req -in server.csr -text -noout\nopenssl x509 -in server.crt -text -noout\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eMove the files into the /etc/mosquitto configuration directory and change\ntheir permissions. The point of this is to make the certificates publicly\nvisible so you can use them with \u003ccode class=\"inline\"\u003emosquitto_pub\u003c/code\u003e, etc. while the private\nkeys can only be used root or the mosquitto server.\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003ecd ~/ca-certs\nchmod 600 ca.* server.*\nsudo mv ca.* server.* /etc/mosquitto/ca_certificates/\ncd /etc/mosquitto/ca_certificates/\nsudo chown root:root ca.* server.*\nsudo chmod 600 ca.* server.*\nsudo chmod 644 *.crt\nsudo chown root:mosquitto server.key\nsudo chmod 640 server.key\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eThe end result should have permissions that look like this:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003e$ ls -l /etc/mosquitto/ca_certificates/\ntotal 28\n-rw-r--r-- 1 root root      1135 Mar 22 12:00 ca.crt\n-rw------- 1 root root      1704 Mar 22 12:00 ca.key\n-rw------- 1 root root        41 Mar 22 12:00 ca.srl\n-rw-r--r-- 1 root root        73 Sep 30  2023 README\n-rw-r--r-- 1 root root      1131 Mar 22 12:00 server.crt\n-rw------- 1 root root       944 Mar 22 12:00 server.csr\n-rw-r----- 1 root mosquitto 1704 Mar 22 12:00 server.key\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eModify mosquitto config to use TLS (the \u003ccode class=\"inline\"\u003e$(hostname -I|awk '{print $1}')\u003c/code\u003e\nthing evaluates to the first IP address returned by \u003ccode class=\"inline\"\u003ehostname -I\u003c/code\u003e, in the\ncase of this example, that would be \u003ccode class=\"inline\"\u003e10.0.0.10\u003c/code\u003e)\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003ecat \u0026lt;\u0026lt;EOF | sudo tee /etc/mosquitto/conf.d/LAN-listener.conf\n# To read the log, do: sudo tail -f /var/log/mosquitto/mosquitto.log\nlog_type all\nper_listener_settings true\n\nlistener 1883 $(hostname -I|awk '{print $1}')\nallow_anonymous true\npersistence false\n\nlistener 8883 $(hostname -I|awk '{print $1}')\nallow_anonymous true\npersistence false\ncertfile /etc/mosquitto/ca_certificates/server.crt\nkeyfile /etc/mosquitto/ca_certificates/server.key\nEOF\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eRestart mosquitto\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003esudo systemctl restart mosquitto\n\u003c/pre\u003e\u003c/code\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\n\u003cp\u003eIf all that worked, you can use \u003ccode class=\"inline\"\u003emosquitto_sub\u003c/code\u003e and \u003ccode class=\"inline\"\u003emosquito_pub\u003c/code\u003e over TLS by\nchanging to \u003ccode class=\"inline\"\u003e-L mqtts://\u003c/code\u003e (note the \"s\") and adding a \u003ccode class=\"inline\"\u003e--cafile ...\u003c/code\u003e option.\u003c/p\u003e\n\n\u003cp\u003eFor example to start a subscriber:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003emosquitto_sub --cafile /etc/mosquitto/ca_certificates/ca.crt \\\n  -L mqtts://10.0.3.17/test\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eAnd to publish:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003emosquitto_pub --cafile /etc/mosquitto/ca_certificates/ca.crt \\\n  -L mqtts://10.0.3.17/test -m 1\n\u003c/pre\u003e\u003c/code\u003e","metadata":{"markdown":"To upgrade your local mosquitto MQTT broker to TLS, first you need to make\nsure you have the `openssl` command line tool installed. Try `openssl version`\nfrom a terminal. If that doesn't work, you can install openssl with:\n\n```\nsudo apt install openssl\n```\n\nOnce you have openssl, you'll need to make a CA certificate and private key,\nthen use those to sign a server key, then install those in the mosquitto\nconfiguration directory.\n\n1. Change to a temporary working directory\n\n    ```\n    mkdir ~/ca-cert\n    cd ~/ca-cert\n    ```\n\n2. Create a Certificate Authority (CA) private key and certificate file\n\n    ```\n    openssl req -newkey rsa:2048 -noenc -x509 -days 730 -extensions v3_ca \\\n     -subj \"/C=US/O=MyCA/CN=My Self-Signed CA\" -keyout ca.key -out ca.crt\n    ```\n\n    CAUTION: The mbed TLS certificate parser appears to care about the contents\n    of the subject fields and perhaps the validity period. If your connection\n    closes mysteriously and you see a `-0x2180` error code from the mbed TLS\n    debug logging, it may be unhappy with the subject or days. Or, it's also possible\n    the problem could be due to a memory allocation failure. Certificate parsing uses\n    mbed TLS heap space.\n\n3. Create a server private key and Certificate Signing Request (CSR). The\n   stuff with `$(hostname -I|awk '{print $1}')` is a way to automatically\n   fill in the first hostname reported by `hostname -I`. You could just type\n   in `10.0.0.10` or whatever instead if you wanted.\n\n    ```\n    openssl req -newkey rsa:2048 -noenc \\\n     -subj \"/CN=$(hostname -I|awk '{print $1}')\" \\\n     -keyout server.key -out server.csr\n    ```\n\n4. Use your CA's certificate and private key to sign the CSR\n\n    ```\n    openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \\\n     -CAcreateserial -copy_extensions copy -days 365 -out server.crt\n    ```\n\n5. Check contents of the PEM files\n\n    ```\n    openssl x509 -in ca.crt -text -noout\n    openssl req -in server.csr -text -noout\n    openssl x509 -in server.crt -text -noout\n    ```\n\n6. Move the files into the /etc/mosquitto configuration directory and change\n   their permissions. The point of this is to make the certificates publicly\n   visible so you can use them with `mosquitto_pub`, etc. while the private\n   keys can only be used root or the mosquitto server.\n\n    ```\n    cd ~/ca-certs\n    chmod 600 ca.* server.*\n    sudo mv ca.* server.* /etc/mosquitto/ca_certificates/\n    cd /etc/mosquitto/ca_certificates/\n    sudo chown root:root ca.* server.*\n    sudo chmod 600 ca.* server.*\n    sudo chmod 644 *.crt\n    sudo chown root:mosquitto server.key\n    sudo chmod 640 server.key\n    ```\n\n    The end result should have permissions that look like this:\n\n    ```\n    $ ls -l /etc/mosquitto/ca_certificates/\n    total 28\n    -rw-r--r-- 1 root root      1135 Mar 22 12:00 ca.crt\n    -rw------- 1 root root      1704 Mar 22 12:00 ca.key\n    -rw------- 1 root root        41 Mar 22 12:00 ca.srl\n    -rw-r--r-- 1 root root        73 Sep 30  2023 README\n    -rw-r--r-- 1 root root      1131 Mar 22 12:00 server.crt\n    -rw------- 1 root root       944 Mar 22 12:00 server.csr\n    -rw-r----- 1 root mosquitto 1704 Mar 22 12:00 server.key\n    ```\n\n7. Modify mosquitto config to use TLS (the `$(hostname -I|awk '{print $1}')`\n   thing evaluates to the first IP address returned by `hostname -I`, in the\n   case of this example, that would be `10.0.0.10`)\n\n    ```\n    cat \u0026lt;\u0026lt;EOF | sudo tee /etc/mosquitto/conf.d/LAN-listener.conf\n    # To read the log, do: sudo tail -f /var/log/mosquitto/mosquitto.log\n    log_type all\n    per_listener_settings true\n\n    listener 1883 $(hostname -I|awk '{print $1}')\n    allow_anonymous true\n    persistence false\n\n    listener 8883 $(hostname -I|awk '{print $1}')\n    allow_anonymous true\n    persistence false\n    certfile /etc/mosquitto/ca_certificates/server.crt\n    keyfile /etc/mosquitto/ca_certificates/server.key\n    EOF\n    ```\n\n8. Restart mosquitto\n\n    ```\n    sudo systemctl restart mosquitto\n    ```\n\nIf all that worked, you can use `mosquitto_sub` and `mosquito_pub` over TLS by\nchanging to `-L mqtts://` (note the \"s\") and adding a `--cafile ...` option.\n\nFor example to start a subscriber:\n```\nmosquitto_sub --cafile /etc/mosquitto/ca_certificates/ca.crt \\\n  -L mqtts://10.0.3.17/test\n```\n\nAnd to publish:\n```\nmosquitto_pub --cafile /etc/mosquitto/ca_certificates/ca.crt \\\n  -L mqtts://10.0.3.17/test -m 1\n```"}},{"element_type":"text","content":"\n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n  \n        \u003ch3\u003eNotes on Adafruit IO TLS Config\u003c/h3\u003e\n      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"element_type":"markdown","content":"\u003cp\u003eTo check the Adafruit IO certificate chain with the \u003ccode class=\"inline\"\u003eopenssl\u003c/code\u003e command line tool\nfrom a terminal shell on Debian 12:\u003c/p\u003e\n\u003ccode class=\"\"\u003e\u003cpre\u003eopenssl s_client -showcerts -connect io.adafruit.com:8883\n\u003c/pre\u003e\u003c/code\u003e\n\u003cp\u003eThe output is long, but the main relevant points are:\u003c/p\u003e\n\n\u003cul\u003e\n\u003cli\u003eFirst cert: \u003ccode class=\"inline\"\u003eO = Adafruit Industries LLC,  CN = *.adafruit.com\u003c/code\u003e\nwith \u003ccode class=\"inline\"\u003ea:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256\u003c/code\u003e and\n\u003ccode class=\"inline\"\u003eNotAfter: Aug  2 23:59:59 2025 GMT\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003eSecond cert: \u003ccode class=\"inline\"\u003eOU = www.digicert.com, CN = GeoTrust TLS RSA CA G1\u003c/code\u003e\nwith \u003ccode class=\"inline\"\u003ea:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256\u003c/code\u003e and\n\u003ccode class=\"inline\"\u003eNotAfter: Nov  2 12:23:37 2027 GMT\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003eThird cert: \u003ccode class=\"inline\"\u003eOU = www.digicert.com, CN = DigiCert Global Root G2\u003c/code\u003e\n\u003c/li\u003e\n\u003cli\u003eNegotiated connection used \u003ccode class=\"inline\"\u003eTLSv1.3, Cipher is TLS_AES_256_GCM_SHA384\u003c/code\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n","metadata":{"markdown":"To check the Adafruit IO certificate chain with the `openssl` command line tool\nfrom a terminal shell on Debian 12:\n\n```\nopenssl s_client -showcerts -connect io.adafruit.com:8883\n```\n\nThe output is long, but the main relevant points are:\n\n- First cert: `O = Adafruit Industries LLC,  CN = *.adafruit.com`\n  with `a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256` and\n  `NotAfter: Aug  2 23:59:59 2025 GMT`\n- Second cert: `OU = www.digicert.com, CN = GeoTrust TLS RSA CA G1`\n  with `a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256` and\n  `NotAfter: Nov  2 12:23:37 2027 GMT`\n- Third cert: `OU = www.digicert.com, CN = DigiCert Global Root G2`\n- Negotiated connection used `TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384`\n"}}]