#MicroPython: Programming an ESP using Jupyter Notebook
MicroPython | 6 min | 19650
I was looking at my last articles about MicroPython and my new articles about Jupyter and Docker, and I thought if it is possible to make a mix between Jupyter and the ESP boards. I use usually Visual Studio Code to program the ESP but for analytics I use Jupyter. I looked if it was possible to connect the Jupyter kernel to the ESP using the serial port and: Yes!, it is possible. This can be a great tool to teach kids to access data from connected sensors and analyze it using a browser with Jupyter.
This is what I will try to accomplish in this tutorial:
- Install MicroPython
- Install the Jupyter Kernel:
- on a host PC
- inside a Docker Container
- Connect the Jupyter kernel to an ESP32
- Use a ST7735 display using Jupyter
- Get data from sensors
Let's start! but first a video example:
Hardware & Software
Go to the MicroPython site and download the appropriated FW for your device and then follow the instructions described in this post.
To test if everything is working (on Linux/Mac OSX):
# MAC OSX screen /dev/tty.SLAB_USBtoUART 115200 >>> print (‘hello’) >>> hello # LINUX # if not installed: sudo apt-get install screen screen /dev/ttyUSB0 >>> print (‘hello’) >>> hello
[Ctrl+C] to break a program [Ctrl+A] [K] [Y] to quit and return to the terminal.
on Windows, you can use Putty and connect to the corresponding
COMXport (check under Device Manager) with a speed of
To interact with the ESP32/8266 running MicroPython over its serial REPL, you need to install a specific Jupyter Kernel. The Jupyter documentation has a section for the "Community-maintained kernels". In this list, there is a kernel for the ESP32/ESP8266 microcontroller, which is the GitHub project: goatchurchprime/jupyter_micropython_kernel. There are two solutions for installing this Jupiter kernel:
- Install it on your host pc directly or using virtualenv.
- Running a Docker container with all libraries installed (recommended).
Fig. 1: An ESP32 controlling a ST7735 display.
Install the Jupyter kernel on your PC
Note (*): A newer fork of this repository is available on andrewleech/jupyter_micropython_remote. I tried to use it, but I got some problems with the `pydevd` library, and the `mpy_kernel` tried to connect to a port on my host PC that is not opened (no service is running on that port). I commented the line, it worked (no error appears), but I wasn't able to run code on the ESP32.
- Create a virtualenv or pipenv, if you want to, but recommended!
- Clone the repository using:
git clone https://github.com/goatchurchprime/jupyter_micropython_kernel.git # (*)
- Install the library (in editable mode) using the following command:
pip install -e jupyter_micropython_kernel
-emode makes easier to update the library. pip does not copy the files from the local source directory but places a special file, called an egg-link, in your distribution path. Then, to update your library, you just need to go inside the
jupyter_micropython_kernelfolder and type
git update. This command pulls the repository updating the files.
- Install the kernel into Jupyter itself using the following:
python -m jupyter_micropython_kernel.install
- To check if everything was correct, type the following:
jupyter kernelspec list
MicroPython remotekernel should be listed.
- To run it, you need to type the following:
This opens a browser pointing to the
Run the Jupyter kernel inside a Docker Container
I have written a Dockerfile, built the image and uploaded it to Dockerhub (lemariva/upyjupyter).
If you have Docker running on your system, type the following:
docker run -d -p 8888:8888 --privileged --device=/dev/ttyUSB0 -v /home/lemariva/notebooks:/notebooks lemariva/upyjupyter
After running the command line, you get a hashed text, which is the container ID. You need to copy it, for the step 2.1. The
--deviceargument should be pointing to the serial interface (e.g. on Linux
/dev/ttyUSB0), where the ESP32 is connected. Before you start the container, the board should be connected, otherwise the container does not found the device and does not link it. You can disconnect the cable while the container is running, it still works when you connect it back. The argument
-vlink the folder outside the container (
/home/lemariva/notebooks- you need to point this to a local path on your host PC) to the inside folder
/notebooks. This makes your files/notebooks persistent. Otherwise, they are deleted if you re-run or update the container.
You can find more information about Docker installation on this post.
- You need to get a
tokento enter to the Jupyter notebook.
- Type the following to see the token:
docker logs <container id/hash>
<container id/hash>is the hashed text that you got on step 1.
- You can add an environmental variable to the
runline to include a Jupyter password. The run line should look like this:
docker run -d -p 8888:8888 --privileged --device=/dev/ttyUSB0 -e PASSWORD="<your-password>" lemariva/upyjupyter
Then, you need
<your-password>to enter to the notebooks.
- Type the following to see the token:
- Open a browser and go to:
http://localhost:8888and enter the token or password, when asked, you should get something like Fig. 2.
Fig. 2: Jupyter Notebook connected to an ESP32 running MicroPython.
Connect the Jupyter kernel to an ESP32
Inside the Docker image, I included a example notebook to start the connection (if you use the
-voption, you are not going to see it, sorry! but it is here.). You need to type the following:
%serialconnect <device> --baud=115200 --user='micro' --password='python' --wait=0
<device>corresponds to the interface in which the ESP is connected. In my case
If everything is working, you get something like this:
Connecting to --port=/dev/ttyUSB0 --baud=115200 Ready.
You cannot load files to the ESP using the Jupyter, you need to use
Pymakrextension of VSCode or Atom (read this tutorial if you need some help). But, you can write the content of a cell to a file, using:
%sendtofile yourfilename.pyat the end of the Jupyter cell. Another commands are e.g.:
%rebootdeviceto do a soft reboot; and
%lsmagicto list all other functions available.**Note:** Remember that you should only use one connection to the serial port. E.g. if you upload the files using VSCode or Atom, close the connection after the uploading process is finished in order to use the port with Jupyter (you need to reset the connection re-running the `%serialconnect` command). If you try to connect with two programs to the ESP at the same time, you are going to get an error.
Runing some Code on Jupyter
I uploaded the uPySensors repository to the ESP32, and I played a little bit with the ST7735 and some other sensors.
The following code plots the Jupyter logo on the ST7735. The image (BMP 24 bits) is loaded on the board (
jupyter.bmp). The result is shown on Fig. 1.
from uPySensors.ST7735 import TFT,TFTColor from machine import SPI,Pin spi = SPI(1, baudrate=20000000, polarity=0, phase=0, sck=Pin(14), mosi=Pin(13), miso=Pin(12)) tft=TFT(spi,16,17,18) tft.initr() tft.rgb(True) tft.fill(TFT.WHITE) f=open('jupyter.bmp', 'rb') if f.read(2) == b'BM': #header dummy = f.read(8) #file size(4), creator bytes(4) offset = int.from_bytes(f.read(4), 'little') hdrsize = int.from_bytes(f.read(4), 'little') width = int.from_bytes(f.read(4), 'little') height = int.from_bytes(f.read(4), 'little') if int.from_bytes(f.read(2), 'little') == 1: #planes must be 1 depth = int.from_bytes(f.read(2), 'little') if depth == 24 and int.from_bytes(f.read(4), 'little') == 0:#compress method == uncompressed print("Image size:", width, "x", height) rowsize = (width * 3 + 3) & ~3 if height < 0: height = -height flip = False else: flip = True w, h = width, height if w > 128: w = 128 if h > 160: h = 160 tft._setwindowloc((0,0),(w - 1,h - 1)) for row in range(h): if flip: pos = offset + (height - 1 - row) * rowsize else: pos = offset + row * rowsize if f.tell() != pos: dummy = f.seek(pos) for col in range(w): bgr = f.read(3) tft._pushcolor(TFTColor(bgr,bgr,bgr)) spi.deinit()
In this case, I took the DHT11 and measured the temperature and humidity over a period of 10 seconds:
import dht import machine import utime d = dht.DHT11(machine.Pin(4)) # temperatur =  humidity =  for i in range(0,10): d.measure() temperatur.append(d.temperature()) humidity.append(d.humidity()) utime.sleep_ms(1000) # print(temperatur) print(humidity) >>> [23, 22, 22, 22, 23, 23, 24, 24, 24, 24] >>> [60, 37, 37, 38, 38, 38, 39, 39, 40, 42]
I was blowing the sensor, that's why the values were changing. See Fig. 3 for the Jupyter Notebook.
Fig. 3: Jupyter Notebook connected to an ESP32 running MicroPython.
- MicroPython and its libraries
- MicroPython extra packages - check the "Installing Dependencies on MicroPython" tutorial for help.
- Jupyter kernels
- Jupyter MicroPython kernel
I hope this project can help you to integrate three technologies together in an easy way: ESP32, Docker and Jupyter. I had really fun programming the ESP32 with Jupyter. It is easier and you can always see the last results of the cell that you've run, and if the kernel or the ESP resets, you just need to re-run the cells and you have everything back (ok, with new sensor data ;)).
I still have a ToDo: try to make the new repository work. With the new version, it is possible to use the local Jupyter kernel (using
%local) with its libraries together with the remote connection to the ESP. I do not know now, if the workspace variables remain global (it is quite impossible). If they remain, it is possible then to plot the data with e.g matplotlib or similar.