KEMBAR78
MQTT Micropython PDF | PDF | Device Driver | Computer Engineering
100% found this document useful (1 vote)
543 views29 pages

MQTT Micropython PDF

This two-part tutorial teaches how to get started with MicroPython on an ESP32 development board. In part one, the reader will learn how to: - Flash the ESP32 with the MicroPython firmware - Connect to the MicroPython REPL over USB serial - Connect the ESP32 to WiFi - Create a boot.py file to configure WiFi connection on startup The second part will show how to publish sensor data collected on the ESP32 over MQTT. Required hardware includes an ESP32 board, temperature sensor, jumper wires, and breadboard. Necessary software includes MicroPython firmware, esptool to flash the board, and a serial terminal program.

Uploaded by

Vlad Timisoara
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
543 views29 pages

MQTT Micropython PDF

This two-part tutorial teaches how to get started with MicroPython on an ESP32 development board. In part one, the reader will learn how to: - Flash the ESP32 with the MicroPython firmware - Connect to the MicroPython REPL over USB serial - Connect the ESP32 to WiFi - Create a boot.py file to configure WiFi connection on startup The second part will show how to publish sensor data collected on the ESP32 over MQTT. Required hardware includes an ESP32 board, temperature sensor, jumper wires, and breadboard. Necessary software includes MicroPython firmware, esptool to flash the board, and a serial terminal program.

Uploaded by

Vlad Timisoara
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 29

Get on the Good Foot with MicroPython on

the ESP32, Part 1 of 2


by Christopher Hiller
I’m going to show you how to turn on your funk motor get started with MicroPython on an
Espressif ESP32 development board. In this first part of this tutorial, I’ll show you how to:
• Get up & running with MicroPython on the ESP32
• Connect to WiFi
• Upload scripts to the board
• Read the ambient temperature (everyone loves that, right?)
In the forthcoming second part of this tutorial, I’ll show you publish the data you’ve
collected with MQTT.

This guide expects you to possess:


• …familiarity with the command-line
• …basic experience interfacing with development boards (like Arduino)
• …a basic understanding of programming in Python
If I’ve glossed over something I shouldn’t have, please let me know!
Before we begin, you will need some stuff.

Bill of Stuff
You need Stuff in the following categories.

Hardware

Not necessarily this stuff, but same idea. Photo by Alexandra Cárdenas

• One (1) ESP32 development board such as the SparkFun ESP32 Thing (any kind will do;
they are all roughly the same)
• One (1) DS18B20 digital thermometer (datasheet) in its TO-92 package
• One (1) 4.7kꭥ resistor
• Four (4) jumper wires
• One (1) 400-point or larger breadboard
• One (1) USB Micro-B cable
If you need to solder header pins on your dev board: do so.
If you have a DS18B20 “breakout board”: these typically have the resistor built-in, so you won’t
need it. You will need to figure out which pin is which, however.
Software
You will need to download and install some software. Some of these things you may have installed
already. Other things may need to be upgraded. This guide assumes you ain’t got jack squat.
I apologize that I don't have much information for Windows users! However, I assure
you that none of this is impossible.

VCP Driver
If you're running macOS or Windows, you may need to download and install a Virtual COM Port
(VCP) driver, if you haven't done so already. Typically, the USB-to-serial chip on these boards is a
CP210x or FT232RL; check the datasheet for your specific board or just squint at the IC near the
USB port.
Newer Linux kernels have support for these chips baked-in, so driver installation is
unnecessary.

Here's an example of a CP2104 on an ESP32 dev board of mine:


A SiLabs CP2104. Thanks, macro lens!

To assert the driver is working, plug your dev board into your computer. If you’re on Linux, check
for /dev/ttyUSB0:
$ ls -l /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 0 Dec 19 17:04 /dev/ttyUSB0

Bash
Copy
Or /dev/tty.SLAB_USBtoUART on macOS:
$ ls -l /dev/tty.SLAB_USBtoUART
crw-rw-rw- 1 root wheel 21, 20 Dec 19 17:10 /dev/tty.SLAB_USBtoUART

Bash
Copy

Serial Terminal
A free, cross-platform, GUI terminal is CoolTerm. Linux & macOS users can get away with using
screen on the command line. More purpose-built solutions include miniterm, which ships with
Python 3, and can be launched via python3 -m serial.tools.miniterm, and minicom.

Python, Etc.
You will also need:
• Python v3.6.x
• For extra libraries, a clone or archive of micropython/micropython-lib (git clone
https://github.com/micropython/micropython-lib)

How you install these will vary per your installation of Python:
• To flash the board, esptool (version 2.2 or newer)
• To manage files on the board, adafruit-ampy
You could try pip3 install esptool adafruit-ampy. This worked for me on macOS
with Homebrew; YMMV. You might need to preface that with sudo if not using Homebrew.

MicroPython Firmware
Finally, you’ll need to download the latest MicroPython firmware for ESP32.
Now that our tools are at hand, we can begin by flashing the ESP32 board with MicroPython.

Flashing MicroPython & First Steps


Unless MicroPython is already installed on your ESP32, you will want to start by connecting it to
your computer via USB, and erasing its flash:
In the below examples, replace /dev/tty.SLAB_USBtoUART with the appropriate
device or COM port for your system.

$ esptool.py --chip esp32 -p /dev/tty.SLAB_USBtoUART erase_flash


esptool.py v2.2
Connecting........___
Chip is ESP32D0WDQ6 (revision 1)
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
Chip erase completed successfully in 4.6s
Hard resetting...

Bash
Copy
Now, we can flash it with the firmware we downloaded earlier:
$ esptool.py --chip esp32 -p /dev/tty.SLAB_USBtoUART write_flash \
-z 0x1000 ~/Downloads/esp32-20171219-v1.9.2-445-g84035f0f.bin
esptool.py v2.2
Connecting........_
Chip is ESP32D0WDQ6 (revision 1)
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 936288 bytes to 587495...
Wrote 936288 bytes (587495 compressed) at 0x00001000 in 51.7 seconds (effective
144.8 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting...

Bash
Copy
If you’re feeling dangerous, you can increase the baud rate when flashing by using the
--baud option.
If that worked, you should be able to enter a MicroPython REPL by opening up the port:
# 115200 is the baud rate at which the REPL communicates
$ screen /dev/tty.SLAB_USBtoUART 115200

>>>

Bash
Copy
Congratulations, >>> is your REPL prompt. This works similarly to a normal Python REPL (e.g.
just running python3 with no arguments). Try the help() function:
>>> help()
Welcome to MicroPython on the ESP32!

For generic online docs please visit http://docs.micropython.org/

For access to the hardware use the 'machine' module:

import machine
pin12 = machine.Pin(12, machine.Pin.OUT)
pin12.value(1)
pin13 = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_UP)
print(pin13.value())
i2c = machine.I2C(scl=machine.Pin(21), sda=machine.Pin(22))
i2c.scan()
i2c.writeto(addr, b'1234')
i2c.readfrom(addr, 4)

Basic WiFi configuration:

import network
sta_if = network.WLAN(network.STA_IF); sta_if.active(True)
sta_if.scan() # Scan for available access points
sta_if.connect("<AP_name>", "<password>") # Connect to an AP
sta_if.isconnected() # Check for successful connection

Control commands:
CTRL-A -- on a blank line, enter raw REPL mode
CTRL-B -- on a blank line, enter normal REPL mode
CTRL-C -- interrupt a running program
CTRL-D -- on a blank line, do a soft reset of the board
CTRL-E -- on a blank line, enter paste mode

For further help on a specific object, type help(obj)


For a list of available modules, type help('modules')

Plain
Copy
If you’ve never seen this before on an MCU: I know, crazy, right?
You can type in the commands from “Basic WiFi configuration” to connect. You will see a good
deal of debugging information from the ESP32 (this can be suppressed, as you’ll see):
>>> import network
>>> sta_if = network.WLAN(network.STA_IF)
I (323563) wifi: wifi firmware version: 111e74d
I (323563) wifi: config NVS flash: enabled
I (323563) wifi: config nano formating: disabled
I (323563) system_api: Base MAC address is not set, read default base MAC
address from BLK0 of EFUSE
I (323573) system_api: Base MAC address is not set, read default base MAC
address from BLK0 of EFUSE
I (323593) wifi: Init dynamic tx buffer num: 32
I (323593) wifi: Init data frame dynamic rx buffer num: 64
I (323593) wifi: Init management frame dynamic rx buffer num: 64
I (323603) wifi: wifi driver task: 3ffe1584, prio:23, stack:4096
I (323603) wifi: Init static rx buffer num: 10
I (323613) wifi: Init dynamic rx buffer num: 0
I (323613) wifi: Init rx ampdu len mblock:7
I (323623) wifi: Init lldesc rx ampdu entry mblock:4
I (323623) wifi: wifi power manager task: 0x3ffe84b0 prio: 21 stack: 2560
W (323633) phy_init: failed to load RF calibration data (0x1102), falling back
to full calibration
I (323793) phy: phy_version: 362.0, 61e8d92, Sep 8 2017, 18:48:11, 0, 2
I (323803) wifi: mode : null
>>> sta_if.active(True)
I (328553) wifi: mode : sta (30:ae:a4:27:d4:88)
I (328553) wifi: STA_START
True
>>> sta_if.scan()
I (389423) network: event 1
[(b'SON OF ZOLTAR', b"`\xe3'\xcf\xf4\xf5", 1, -57, 4, False),
(b'CenturyLink6105', b'`1\x97%\xd9t', 1, -96, 4, False)]
>>> sta_if.connect('SON OF ZOLTAR', '<REDACTED>')
>>> I (689573) wifi: n:1 0, o:1 0, ap:255 255, sta:1 0, prof:1
I (690133) wifi: state: init -> auth (b0)
I (690133) wifi: state: auth -> assoc (0)
I (690143) wifi: state: assoc -> run (10)
I (690163) wifi: connected with SON OF ZOLTAR, channel 1
I (690173) network: event 4
I (691723) event: sta ip: 10.0.0.26, mask: 255.255.255.0, gw: 10.0.0.1
I (691723) network: GOT_IP
I (693143) wifi: pm start, type:0

>>> sta_if.isconnected()
True

Plain
Copy
Cool, huh?
Now that we know we can connect to WiFi, let’s have the board connect every time it powers up.

Creating a MicroPython Module


To perform tasks upon boot, MicroPython wants you to put code in a file named boot.py, which
is a MicroPython module.
Let’s create boot.py with code modified from the MicroPython ESP8266 docs, replacing where
indicated:
def connect():
import network
sta_if = network.WLAN(network.STA_IF)
if not sta_if.isconnected():
print('connecting to network...')
sta_if.active(True)
sta_if.connect('<YOUR WIFI SSID>', '<YOUR WIFI PASS>')
while not sta_if.isconnected():
pass
print('network config:', sta_if.ifconfig())

Python
Copy
We can also create a function to disable debugging output. Append to boot.py:
def no_debug():
import esp
# this can be run from the REPL as well
esp.osdebug(None)

Python
Copy
These functions will be defined at boot, but not called automatically. Let’s test them before making
them automatically execute.
To do this, we can upload boot.py. You’ll need to close the connection to the serial port. If you’re
using screen, type Ctrl-A Ctrl-\, then y to confirm; otherwise disconnect or just quit your
terminal program.

Uploading a MicroPython Module


Though there are other ways to do this, I’ve found the most straightforward for the ESP32 is to use
ampy, a general-purpose tool by Adafruit. Here’s what it can do:
$ ampy --help

Usage: ampy [OPTIONS] COMMAND [ARGS]...

ampy - Adafruit MicroPython Tool

Ampy is a tool to control MicroPython boards over a serial


connection. Using ampy you can manipulate files on the board's
internal filesystem and even run scripts.

Options:
-p, --port PORT Name of serial port for connected board. Can
optionally specify with AMPY_PORT environemnt
variable. [required]
-b, --baud BAUD Baud rate for the serial connection (default
115200). Can optionally specify with AMPY_BAUD
environment variable.
--version Show the version and exit.
--help Show this message and exit.

Commands:
get Retrieve a file from the board.
ls List contents of a directory on the board.
mkdir Create a directory on the board.
put Put a file or folder and its contents on the...
reset Perform soft reset/reboot of the board.
rm Remove a file from the board.
rmdir Forcefully remove a folder and all its...
run Run a script and print its output.
Bash
Copy
MicroPython stores files (scripts or anything else that fits) in a very basic filesystem. By default, an
empty boot.py should exist already. To list the files on your board, execute:
$ ampy -p /dev/tty.SLAB_USBtoUART ls
boot.py

Bash
Copy
Using the get command will echo a file’s contents to your shell (which could be piped to a file, if
you wish):
$ ampy -p /dev/tty.SLAB_USBtoUART get boot.py
# This file is executed on every boot (including wake-boot from deepsleep)

Bash
Copy
We can overwrite it with our own boot.py:
$ ampy -p /dev/tty.SLAB_USBtoUART put boot.py

Bash
Copy
And retrieve it to see that it overwrote the default boot.py:
$ ampy -p /dev/tty.SLAB_USBtoUART get boot.py
def connect():
import network
sta_if = network.WLAN(network.STA_IF)
if not sta_if.isconnected():
print('connecting to network...')
sta_if.active(True)
sta_if.connect('<YOUR WIFI SSID>', '<YOUR WIFI PASS>')
while not sta_if.isconnected():
pass
print('network config:', sta_if.ifconfig())

def no_debug():
import esp
# this can be run from the REPL as well
esp.osdebug(None)

Bash
Copy
Success! This is the gist of uploading files with ampy. You can also upload entire folders, as we’ll
see later.
From here, we can open our REPL again, and run our code. No need to restart the board!
Running a MicroPython Module
In following examples, I will eliminate the command prompt (>>>) from code run in a REPL,
for ease of copying & pasting.
Re-connect to the REPL.
$ screen /dev/tty.SLAB_USBtoUART 115200

Bash
Copy
First, we’ll disconnect from WiFi:
import network
sta_if = network.WLAN(network.STA_IF)
sta_if.disconnect()

Python
Copy
Debug output follows:
I (3299583) wifi: state: run -> init (0)
I (3299583) wifi: n:1 0, o:1 0, ap:255 255, sta:1 0, prof:1
I (3299583) wifi: pm stop, total sleep time: 0/-1688526567
I (3299583) wifi: STA_DISCONNECTED, reason:8

Plain
Copy
Then, we can import the boot module. This will make our connect and no_debug functions
available.
import boot
connect()

Python
Copy
Output:
connecting to network...
I (87841) wifi: n:1 0, o:1 0, ap:255 255, sta:1 0, prof:1
I (88401) wifi: state: init -> auth (b0)
I (88401) wifi: state: auth -> assoc (0)
I (88411) wifi: state: assoc -> run (10)
I (88441) wifi: connected with SON OF ZOLTAR, channel 1
I (88441) network: event 4
I (90081) event: sta ip: 10.0.0.26, mask: 255.255.255.0, gw: 10.0.0.1
I (90081) network: GOT_IP
network config: ('10.0.0.26', '255.255.255.0', '10.0.0.1', '10.0.0.1')
I (91411) wifi: pm start, type:0

Plain
Copy
Super. Let’s silence the noise, and try again:
no_debug()
sta_if.disconnect()
connect()

Python
Copy
Output:
connecting to network...
network config: ('10.0.0.26', '255.255.255.0', '10.0.0.1', '10.0.0.1')

Plain
Copy
LGTM.
The IP addresses above depend upon your local network configuration, and will likely
be different.

Disconnect from the port (if using screen: Ctrl-A Ctrl-\, y) and append these lines to
boot.py:
no_debug()
connect()

Python
Copy
Upload it again via ampy put boot.py, which will overwrite the existing boot.py. Hard
reset (“push the button”) or otherwise power -cycle the board. Reconnect to the REPL and execute
connect() to assert connectivity:
connect()

Python
Copy
Output:
network config: ('10.0.0.26', '255.255.255.0', '10.0.0.1', '10.0.0.1')

Plain
Copy
You’ll notice “connecting to network...” was not printed to the console; if already connected, the
connect() function prints the configuration and returns. If you’ve gotten this far, then your board
is successfully connecting to Wifi at boot. Good job!
We now have two more items to check off our list, unless you forgot what we were trying to do:
1. We need to read the ambient temperature on an interval.
2. We need to publish this information to an MQTT broker.
Next, we’ll knock out that temperature reading.
Temperature Readings in MicroPython
As we write our code, we can use the REPL to experiment.
I’m using the example found here. You’ll need to import three (3) modules, machine, onewire
and ds18x20 (note the x):
import machine, onewire, ds18x20

Python
Copy
I’ve connected my sensor to pin 12 on my ESP32. Your breadboard should look something like this:

Example breadboard wiring for ESP32 dev board and DS18B20

To read temperature, we will create a Matryoshka-doll-like object by passing a Pin instance into a
OneWire constructor (read about 1-Wire) and finally into a DS18X20 constructor:
pin = machine.Pin(12)
wire = onewire.OneWire(pin)
ds = ds18x20.DS18X20(wire)

Python
Copy
Note that if the output of the following command is an empty list ([]), the sensor
couldn't be found. Check your wiring!

Now, we can ask ds to scan for connected devices, and return their addresses:
ds.scan()

Python
Copy
Output:
[bytearray(b'(\xee3\x0c"\x15\x004')]

Plain
Copy
ds.scan() returns a list of device addresses in bytearray format. Yours may look slightly
different. Since we only have one, we can save its address to a variable. To read temperature data,
we tell the 1-Wire bus to reset via ds.convert_temp(), take a short pause of 750ms (in case
you're pasting this):
import time
addr = ds.scan().pop()
ds.convert_temp()
time.sleep_ms(750)
temp = ds.read_temp(addr)
temp

Python
Copy
Output:
19.875

Plain
Copy
This reading is in Celsius. If you’re like me, you don’t speak Celsius, so maybe you want to convert
it to Fahrenheit:
(temp * 1.8) + 32

Python
Copy
Output:
67.775

Plain
Copy
…which is right around what I expected!
Let’s take what we’ve done and create a new file, temperature.py:
import time
from machine import Pin
from onewire import OneWire
from ds18x20 import DS18X20

class TemperatureSensor:
"""
Represents a Temperature sensor
"""
def __init__(self, pin):
"""
Finds address of single DS18B20 on bus specified by `pin`
:param pin: 1-Wire bus pin
:type pin: int
"""
self.ds = DS18X20(OneWire(Pin(pin)))
addrs = self.ds.scan()
if not addrs:
raise Exception('no DS18B20 found at bus on pin %d' % pin)
# save what should be the only address found
self.addr = addrs.pop()

def read_temp(self, fahrenheit=True):


"""
Reads temperature from a single DS18X20
:param fahrenheit: Whether or not to return value in Fahrenheit
:type fahrenheit: bool
:return: Temperature
:rtype: float
"""
self.ds.convert_temp()
time.sleep_ms(750)
temp = self.ds.read_temp(self.addr)
if fahrenheit:
return self.c_to_f(temp)
return temp

@staticmethod
def c_to_f(c):
"""
Converts Celsius to Fahrenheit
:param c: Temperature in Celsius
:type c: float
:return: Temperature in Fahrenheit
:rtype: float
"""
return (c * 1.8) + 32

Python
Copy
Disconnect from the REPL. Upload temperature.py via ampy:
$ ampy -p /dev/tty.SLAB_USBtoUART put temperature.py

Bash
Copy
Then we can open our REPL once again, and try it:
from temperature import TemperatureSensor
t = TemperatureSensor(12)
t.read_temp() # use t.read_temp(False) to return Celsius

Python
Copy
Seems to have warmed up a bit. Output:
68.7875

Plain
Copy
Good work!
Conclusion of Part One (1)
In the first part of this tutorial, we’ve learned how to:
1. Flash an ESP32 dev board with MicroPython
2. Use MicroPython’s REPL to experiment
3. Connect the ESP32 to WiFi
4. Upload and execute MicroPython scripts
5. Read the temperature with a 1-Wire DS18B20 sensor
In the forthcoming second part of this tutorial, we’ll learn about MQTT, how to publish our
temperature data to an MQTT broker, and likewise interface with an MQTT-based cloud “IoT
platform”.

Get on the Good Foot with MicroPython on


the ESP32, Part 2 of 2
by Christopher Hiller
In the first part of this excruciating tutorial, I taught the reader how to begin with MicroPython on
an ESP32-based development board. We:
1. Flashed the board
2. Frolicked in the REPL
3. Configured WiFi
4. Uploaded scripts
5. Build a circuit with a DS18B20 1-Wire temperature sensor
6. Used MicroPython to read the temperature
In this part of the tutorial, we’ll take the data we gather with the sensor and publish it over MQTT.
If you’re unfamiliar with the concept, I’ll try to explain MQTT in a nutshell.

MQTT in a Nutshell
MQTT is a machine-to-machine protocol for publishing and subscribing to messages. Importantly,
MQTT imposes no constraints upon the content nor structure of those messages.
In a typical setup, you have a single MQTT broker and one-or-many MQTT clients. A client may
publish messages, subscribe to messages, or both. A client needn’t be an IoT device, a web app, a
desktop or mobile app, a microservice, or anything in particular, as long as it speaks MQTT.
All clients connect to the broker. The broker is responsible for receiving published messages and
(possibly) delivering them to interested clients.
Each message has a “topic”. As is vital to the publish/subscribe pattern, a message’s publisher
doesn’t necessarily care if anyone is listening. Interested clients will subscribe to this topic.
A MQTT Example
You have an MQTT client—perhaps a device with a temperature sensor—called bob which wants
to publish temperature data. It may publish on a topic such as bob/sensor/temperature, and
the message would be the data, e.g., 68.75.

Another MQTT client, ray, may want to listen for temperature data so we can display it as a time
series graph on a dashboard; ray would tell the broker it wishes to subscribe to the
bob/sensor/temperature topic. Finally, when bob publishes on this topic, the broker
notifies ray, and ray receives the message. ray can then do whatever it needs with its data.

Wildcards
Subscriptions support wildcards. If client bob had another sensor which reports the relative
humidity, it may publish this data under the topic bob/sensor/humidity. Client ray could
use a single-level wildcard such as bob/sensor/+, which would receive messages published on
bob/sensor/humidity and bob/sensor/temperature. Or perhaps a multi-level
wildcard such as bob/#, which would subscribe to any topic beginning with bob/.

A “topic per client” is merely a convention for sake of our example. MQTT enforces no
such constraint.

There’s certainly more to it than just the above—but that’s the nutshell, and I’m calling it good.

Photo by Caleb Martin / Unsplash

Why MQTT?
It’s just as important to understand why you’d want to use a technology over another (or none at
all).
MQTT's designers had resource-constrained devices (such as sensors) in mind; it’s a “thin”
protocol, and easier to implement compared to, say, HTTP. As such, you’ll find that MQTT is a core
technology behind many cloud-based “IoT platforms”, including the offerings of IBM, Amazon,
Microsoft, Adafruit, and many others.
You can directly access many of these services via RESTful APIs, but it will necessarily consume
more of your devices’ resources to do so.
Using HTTP(S) instead of MQTT makes sense if you need to make a remote procedure
call, or if a request/response model is more natural than MQTT's publish/subscribe
model in your problem domain. Even then, protocols such as CoAP will demand fewer
resources.

Now that we understand what MQTT is all (or more accurately, “partly”) about, let’s use it to spread
the word about our ambient temperatures.

Boot Script and Temperature Module


We’ll use the code from the last tutorial to begin with. For reference, I’ll show them below.
You should have two (2) files, the first being our startup script, boot.py:
def connect():
import network
sta_if = network.WLAN(network.STA_IF)
if not sta_if.isconnected():
print('connecting to network...')
sta_if.active(True)
sta_if.connect('<YOUR SSID>', '<YOUR PASSWORD>')
while not sta_if.isconnected():
pass
print('network config:', sta_if.ifconfig())

def no_debug():
import esp
# you can run this from the REPL as well
esp.osdebug(None)

no_debug()
connect()

Python
Copy
And the second is temperature.py, an abstraction around the temperature sensor:
import time
from machine import Pin
from onewire import OneWire
from ds18x20 import DS18X20

class TemperatureSensor:
"""
Represents a Temperature sensor
"""
def __init__(self, pin):
"""
Finds address of single DS18B20 on bus specified by `pin`
:param pin: 1-Wire bus pin
:type pin: int
"""
self.ds = DS18X20(OneWire(Pin(pin)))
addrs = self.ds.scan()
if not addrs:
raise Exception('no DS18B20 found at bus on pin %d' % pin)
# save what should be the only address found
self.addr = addrs.pop()

def read_temp(self, fahrenheit=True):


"""
Reads temperature from a single DS18X20
:param fahrenheit: Whether or not to return value in Fahrenheit
:type fahrenheit: bool
:return: Temperature
:rtype: float
"""

self.ds.convert_temp()
time.sleep_ms(750)
temp = self.ds.read_temp(self.addr)
if fahrenheit:
return self.c_to_f(temp)
return temp

@staticmethod
def c_to_f(c):
"""
Converts Celsius to Fahrenheit
:param c: Temperature in Celsius
:type c: float
:return: Temperature in Fahrenheit
:rtype: float
"""
return (c * 1.8) + 32

Python
Copy
Upload both of these files via ampy:
$ ampy --port /dev/tty.SLAB_USBtoUART put boot.py && \
ampy --port /dev/tty.SLAB_USBtoUART put temperature.py

Bash
Copy
(Replace /dev/tty.SLAB_USBtoUART with your device path or COM port.)

In the first part of this tutorial, I told you to download (or clone) the micropython-lib project. This is
not necessary! Read on.

Install the MQTT Modules via upip


Since your device should be online, we can use upip from the REPL. upip is a stripped-down
package manager for MicroPython. It's built-in to the ESP32 port of MicroPython; you already have
it. It downloads packages from PyPi, just like pip.

Open your REPL, and execute:


import upip
upip.install('micropython-umqtt.robust')

Python
Copy
Sample output:
Installing to: /lib/
Warning: pypi.python.org SSL certificate is not validated
Installing micropython-umqtt.robust 1.0 from
https://pypi.python.org/packages/31/02/7268a19a5054cff8ff4cbbb126f00f098848dbe8f
402caf083295a3a6a11/micropython-umqtt.robust-1.0.tar.gz

Plain
Copy
Take note: if your device isn’t online, upip won’t work from the device’s REPL.

You also need to grab its dependency, micropython-umqtt.simple:


upip.install('micropython-umqtt.robust')

Python
Copy
Sample output:
Installing to: /lib/
Installing micropython-umqtt.simple 1.3.4 from
https://pypi.python.org/packages/bd/cf/697e3418b2f44222b3e848078b1e33ee76aedca9b
6c2430ca1b1aec1ce1d/micropython-umqtt.simple-1.3.4.tar.gz

Plain
Copy
umqtt.simple is a barebones MQTT client. umqtt.robust depends on
umqtt.simple; it’s an MQTT client which will automatically reconnect to the broker
if a disconnection occurs.

To verify that this installed properly, you can execute from your REPL:
from umqtt.robust import MQTTClient

Python
Copy
No errors? You’re good.

Get a MQTT Client App


Before we begin the next section, you might want another application handy—a standalone MQTT
client. You could try:
• MQTT.fx (GUI; Windows/Mac)
• MQTTBox (GUI; Windows/Mac/Linux)
• mosquitto-clients from Mosquitto is available via package manager (CLI;
Linux/Mac)
• Various free clients on app stores (iOS/Android)
• Node-RED can also connect to an MQTT broker (Web; Windows/Mac/Linux)
Using one isn’t strictly necessary, but will aid experimentation.

Experimenting with umqtt in the REPL


If you’ve been reading closely, you’ll understand that we need an MQTT broker (“server”); a
MQTT client with no broker is useless.
It just so happens that public MQTT brokers exist; test.mosquitto.org by the Mosquitto
project is one such broker. As a member of the public, you can use it! Just be aware: any data or
information you publish on a public MQTT broker is also public. Don’t publish anything you
wouldn’t want everyone to know about.
We’ll use this public broker for the purposes of the tutorial, but if you have a different one you wish
to use, you go ahead and do that.
Now, let’s try to use our MQTT lib to publish a message on the broker.

Create a Unique “Client ID”


One caveat to note about MQTT: each MQTT client connected to a broker must have a unique
identifier: a client ID. You’ll need to pick a phrase or generate something. I’ll just generate one on
the command line:
$ python3 -c 'from uuid import uuid4; print(uuid4())'
52dc166c-2de7-43c1-88ff-f80211c7a8f6

Bash
Copy
Copy the resulting value to your clipboard; you’ll need it in a minute.

Connect to the REPL


Open up a serial connection to your ESP32. I’m going to use miniterm here, which Python 3
bundles:
$ python3 -m serial.tools.miniterm --raw /dev/tty.SLAB_USBtoUART 115200

Bash
Copy
The --raw flag avoids problems with special characters such as BS and DEL.

Connect to the Broker


As in the first tutorial, I’ll omit the prompt (>>>) when working with the REPL.

We should now be able to import MQTTClient:


from umqtt.simple import MQTTClient

Python
Copy
The MQTTClient constructor accepts a client ID and a DNS or IP address of a MQTT broker.
We’ll use our pseudorandom client ID from above, and test.mosquitto.org for the server,
then call connect():
client = MQTTClient('52dc166c-2de7-43c1-88ff-f80211c7a8f6',
'test.mosquitto.org')
client.connect()

Python
Copy
The output of this command, if all went well, should be 0; connect() will raise an exception it
the connection failed.
Connect a Second Client
At this point, I’m going to fire up MQTT.fx; I’ll use it to subscribe to the messages which the
ESP32 publishes.
I enter server test.mosquitto.org in the server input field, and leave the port field 1883,
which is the default (insecure) MQTT port. I then click “Connect,” and wait for negotiation. Here’s
a screenshot of my connected client:

MQ
TT.fx connected to test.mosquitto.org.

I’ll come back to MQTT.fx after we learn to publish from the REPL.

Publish an MQTT Message


Assuming the ESP32 is now connected to the broker, you can publish messages. First, I’ll emit a
temperature in Fahrenheit, with the topic boneskull/test/temperature/fahrenheit:
client.publish('boneskull/test/temperature/fahrenheit', 72)

Python
Copy
…but MicroPython complained:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "umqtt/simple.py", line 112, in publish
TypeError: object of type 'int' has no len()

Plain
Copy
What’s the problem here? Let me explain:
1. An MQTT message payload could be literally any data. MQTT has no notion of “data
types”. It doesn’t know what a “number” or “integer” is. Your payload will always consist of
raw bytes.
2. There’s no direct mapping of an integer to “bytes,” as there isn’t just one way to encode this
number as binary data. We don’t know if this is a signed or unsigned integer, how many bits
we should use, etc.
3. The problem could have been obvious (and we could have RTFM), but MicroPython shies
away from overly “friendly” APIs due to resource constraints, so it’s not obvious what’s
happening here.
The easiest solution? Publish a str instead:
client.publish('boneskull/test/temperature/fahrenheit', '72')

Python
Copy
If this worked, there should be no output from the statement.
Hooray? I’m not convinced—are you? This just squirted the temperature into the ether! We should
see where these messages are going. I can do that in my MQTT.fx client by subscribing to the topic.
This is how:

Subscribing to a topic in MQTT.fx

1. Click on the “Subscribe” tab


2. Enter boneskull/test/temperature/fahrenheit in the input field
3. Click “Subscribe” button to the right of input field
After you’ve done this, MQTT.fx will contact the broker, and if successful, you will see the
subscription appear beneath the input field:
An active subscription in MQTT.fx

Next time we (or any client attached to the broker) publishes on this topic, we will see it in the
lower-right area of this window, where it is grey and empty.
Return to your serial terminal, and run the last command again (you can just hit “up-arrow” then
“enter”):
client.publish('boneskull/test/temperature/fahrenheit', '72')

Python
Copy
Switch back to MQTT.fx. It may take a few seconds depending on how busy the broker is, but the
message should now appear to the right, along with its payload:
A received message in MQTT.fx

Excellent work!
Now we can use everything we’ve learned, and periodically publish real temperature data. Let’s
cook up a little module to do that.

A Module to Publish Temperature


I’ve written up a little module which uses MQTTClient and TemperatureSensor (from our
first tutorial) to publish temperature data. Create temperature_client.py:
import time

from umqtt.robust import MQTTClient

from temperature import TemperatureSensor

class TemperatureClient:
"""
Represents an MQTT client which publishes temperature data on an interval
"""

def __init__(self, client_id, server, pin, fahrenheit=True, topic=None,


**kwargs):
"""
Instantiates a TemperatureSensor and MQTTClient; connects to the
MQTT broker.
Arguments `server` and `client_id` are required.

:param client_id: Unique MQTT client ID


:type client_id: str
:param server: MQTT broker domain name / IP
:type server: str
:param pin: 1-Wire bus pin
:type pin: int
:param fahrenheit: Whether or not to publish temperature in Fahrenheit
:type fahrenheit: bool
:param topic: Topic to publish temperature on
:type topic: str
:param kwargs: Arguments for MQTTClient constructor
"""
self.sensor = TemperatureSensor(pin)
self.client = MQTTClient(client_id, server, **kwargs)
if not topic:
self.topic = 'devices/%s/temperature/degrees' % \
self.client.client_id
else:
self.topic = topic
self.fahrenheit = bool(fahrenheit)

self.client.connect()

def publishTemperature(self):
"""
Reads the current temperature and publishes it on the configured topic.
"""
t = self.sensor.read_temp(self.fahrenheit)
self.client.publish(self.topic, str(t))

def start(self, interval=60):


"""
Begins to publish temperature data on an interval (in seconds).
This function will not exit! Consider using deep sleep instead.
:param interval: How often to publish temperature data (60s default)
:type interval: int
"""
while True:
self.publishTemperature()
time.sleep(interval)

Python
Copy
Upload this to your board:
$ ampy --port /dev/tty.SLAB_USBtoUART put temperature_client.py

Bash
Copy
Your standalone MQTT client app should still be online. Let’s send a message in the REPL, then
view the result in the standalone client (please create your own client ID below):
from temperature_client import TemperatureClient
tc = TemperatureClient('boneskull-test-1516667340',
'test.mosquitto.org', 12,
topic='boneskull/test/temperature')
tc.start(10) # publish temperature every 10s

Python
Copy
A word of warning: once you execute the above, the REPL will “hang,” since the start() method
is just busy-waiting.
Even though this is a busy-wait, time.sleep() does not mean that "nothing
happens"; the tick rate in the underlying operating system is 10ms; any sleep time
(necessarily using time.sleep_ms() or time.sleep_us()) equal to or less
than 10ms will preempt other tasks!

Tab back to MQTT.fx:

Real
temperature data in MQTT.fx!

This will loop indefinitely, so when ready, push the “reset” button on your dev board to get back to
the REPL (you don’t need to quit your serial terminal beforehand).
Important to note: the “time and date” you see in the payload detail does not mean
“when the originating client sent the message.” Rather, it means “when the receiving
client received the message.” MQTT messages do not contain a “sent on” timestamp
unless you add one yourself!

(To do this, you'd need to ask an NTP server or an external RTC module, which is
beyond our scope.)

We’re successfully published a number! That is great news, except, that number could refer to
anything. It’d be helpful to include the unit—either Fahrenheit or Celsius—in the payload. I’ll show
you how.

Working with JSON


As I’ve beaten to death, MQTT payloads contain anything. That means if you want to send some
structured data, you are responsible for serialization and deserialization.
JSON is a common data interchange format for which MicroPython contains built-in support
(unlike, say, that vile Arduino API). It’s trivial to “stringify” a dict and publish the result.
To work with JSON—just like in Real Python—we will need to import another module in
temperature_client.py:
import json

Python
Copy
Then, add the data to the payload within the publishTemperature method:
def publishTemperature(self):
"""
Reads the current temperature and publishes a JSON payload on the
configured topic, e.g., `{"unit": "F", "degrees": 72.5}`
"""
t = self.sensor.read_temp(self.fahrenheit)
payload = dict(degrees=t)
if self.fahrenheit:
payload['unit'] = 'F'
else:
payload['unit'] = 'C'
self.client.publish(self.topic, json.dumps(payload))

Python
Copy
Notice that we didn’t need to coerce the temperature (“degrees”) into a str for purposes of
publishing, because JSON is a str itself—the recipient of this payload will decode the JSON into a
numeric value.
Disconnect from the REPL (that’s Ctrl-] if you happen to be using miniterm), and upload
temperate_client.py to the ESP32 again, then reconnect to the REPL. We don’t need to
begin an infinite loop to test it, since we can just call publishTemperature() directly:
from temperature_client import TemperatureClient
tc = TemperatureClient('boneskull-test-1516667340',
'test.mosquitto.org', 12,
topic='boneskull/test/temperature')
tc.publishTemperature()

Python
Copy
The above will send a single message. On the receiving end:
P
retty-printed JSON in MQTT.fx

If you resize your MQTT.fx window to be tall enough, you’ll see the “Payload decoded by”
dropdown in the lower-right. You can see the pretty-printed payload appears as we ‘spected.
MQTT.fx also includes Base64 and hex decoders, but the default is “plain text”.

I think you have the basics down. But maybe you aren’t going to run your own private MQTT
broker. Let’s take this one step further and interface with an IoT platform.

Use an ESP32 with MicroPython on IBM Cloud


Watson IoT Platform is a service in IBM Cloud (formerly Bluemix). I’ve written a MicroPython
module to interface with it, and we’ll use that to save some time.

Watson IoT Platform Quickstart


You can experiment with this platform without needing to sign up for an account.
1. Visit the Quickstart page:

2. Watson IoT Platform's Quickstart Page


3. Tick “I Accept” after carefully reading the entire terms of use.
4. Enter a unique device identifier in the input box. I’m calling mine “boneskull-esp32-test”.
Click “Go”.
Keep this browser window open; you’re now ready to send data, and see the result in real-time.
Let’s get to it.

Upload the micropython-watson-iot module


micropython-watson-iot is the module I referenced earlier. Its README contains installation
instructions using upip, but essentially it’s the same as before, via the REPL:
import upip
upip.install('micropython-watson-iot')

Python
Copy
To verify installation, run:
from watson_iot import Device

Python
Copy
Assuming that didn’t throw an exception, we can use it like so:
d = Device(device_id='boneskull-esp32-test')
d.connect()
d.publishEvent('temperature', {'degrees': 68.5, 'unit': 'F'})

Python
Copy
You should see it reflected in your browser. In fact, if you do something like this…
import time
d.publishEvent('temperature', {'degrees': 68.5, 'unit': 'F'})
time.sleep(5)
d.publishEvent('temperature', {'degrees': 69.5, 'unit': 'F'})
time.sleep(5)
d.publishEvent('temperature', {'degrees': 67.5, 'unit': 'F'})
time.sleep(5)
d.publishEvent('temperature', {'degrees': 66.5, 'unit': 'F'})

Python
Copy
…you should see a nifty line graph:

Real-time graph of our temperature data

You’re welcome to play with this in more depth; Watson IoT Platform has a free tier. To
sign up, you need to:

1. Register with IBM Cloud (no credit card needed)


2. Create a Watson IoT Platform service instance using the “free plan” from the
catalog
3. Click “Launch” to explore the platform.
4. Also, check out the docs.

The micropython-watson-iot library offers a few “quality of life” benefits—as IoT


platforms typically do—when compared to a vanilla MQTT client and/or broker:
1. Messages contain metadata such as “published on” time, handled by the cloud platform
2. You can group devices via logical “device types”
3. Structured data can be automatically encoded/decoded to/from JSON (it does this by default)
4. Create your own custom encoders and decoders (e.g., numeric, Base64)
5. Create custom “command handlers,” which cause the device to react upon reception of a
“command”-style MQTT message. For example, you could send a command to blink an
onboard LED or reboot the device.
I’ve committed a few micropython-watson-iot examples; you can use adapt these
patterns to your own code.

There’s really a lot more going on here than just MQTT—dashboards and gateways and all sorts of
hoodoo that I am not going to go into. But now it’s easy to use with MicroPython on an ESP32,
thanks to ME.
Ahem…

Recap, Obligatory Link Dump, & Goodbyes


In this tutorial, we’ve learned:
1. What MQTT is (and what it’s for)
2. How to talk to an MQTT broker using MicroPython and an ESP32
3. How to publish structured data
4. Install MicroPython libraries from PyPi via upip
5. How to subscribe to simple topics via a standalone MQTT client
6. How to publish data to Watson IoT Platform via its Quickstart site, using micropython-
watson-iot
Check out the README of micropython-watson-iot for more info on usage and discussion
of its limitations.
I’ve posted the complete example files in this Gist for your convenience.
Thanks for reading! Extra thanks for doing, too.

You might also like