• 19
    Jan - 2019

    MicroPython
    6 min | 1047

    #MicroPython: Programming an ESP using Jupyter Notebook

    MicroPython | 6 min | 1047


    access to data
    analytics
    esp32
    jupyter
    jupyter kernel
    sensors
    visual studio code

    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:

    1. Install MicroPython
    2. Install the Jupyter Kernel:
      • on a host PC
      • inside a Docker Container
    3. Connect the Jupyter kernel to an ESP32
    4. Use a ST7735 display using Jupyter
    5. Get data from sensors

    Let's start! but first a video example:

    Hardware & Software

    ESP32 DevKitC-v4 ESP32 Development Board (512kB RAM) x 1
    ESP32 Wrover ESP32 Development Board (4MB RAM) x 1
    ESP32 Wrover ESP32 Development Board (8MB? RAM) x 1
    ESP32 Wrover ESP32 Development Board (8MB RAM) x 1
    uPyLoRa MicroPython Firmware x 1
    Python MicroPython Tutorial x 1
    GitHub Dockerfile GitHub x 1
    DockerHub uPyJupyter Docker Image x 1

    MicroPython

    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

    To exit:

    [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 COMX port (check under Device Manager) with a speed of 115200 baud.

    Jupyter Kernel

    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:

    1. Install it on your host pc directly or using virtualenv.
    2. Running a Docker container with all libraries installed (recommended).
    An ESP32 controlling a ST7735 display
    Fig. 1: An ESP32 controlling a ST7735 display.

    Install the Jupyter kernel on your PC

    1. Create a virtualenv or pipenv, if you want to, but recommended!
    2. Clone the repository using:
      git clone https://github.com/goatchurchprime/jupyter_micropython_kernel.git  
      # (*)
    3. Install the library (in editable mode) using the following command:
      pip install -e jupyter_micropython_kernel

      The -e mode 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_kernel folder and type git update. This command pulls the repository updating the files.

    4. Install the kernel into Jupyter itself using the following:
      python -m jupyter_micropython_kernel.install
    5. To check if everything was correct, type the following:
      jupyter kernelspec list

      The MicroPython remote kernel should be listed.

    6. To run it, you need to type the following:
      jupyter notebook

      This opens a browser pointing to the https://localhost:8888/?token=<token> address.


    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.

    Run the Jupyter kernel inside a Docker Container

    I have written a Dockerfile, built the image and uploaded it to Dockerhub (lemariva/upyjupyter).

    1. 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 --device argument 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 -v link 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.

    2. You need to get a token to enter to the Jupyter notebook.
      1. Type the following to see the token:
        docker logs <container id/hash> 

        where <container id/hash> is the hashed text that you got on step 1.

      2. You can add an environmental variable to the run line 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.

    3. Open a browser and go to: http://localhost:8888 and enter the token or password, when asked, you should get something like Fig. 2.
    Jupyter connected to an ESP32
    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 -v option, 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

    The argument <device> corresponds to the interface in which the ESP is connected. In my case /dev/ttyUSB0.

    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 adafruit-ampy or the Pymakr extension 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.py at the end of the Jupyter cell. Another commands are e.g.: %rebootdevice to do a soft reboot; and %lsmagic to 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.

    Displaying data

    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[2],bgr[1],bgr[0]))
    spi.deinit()

    Getting Data

    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.

    Getting data from ESP32 sensors using Jupyter
    Fig. 3: Jupyter Notebook connected to an ESP32 running MicroPython.

    More Information

    Conclusion

    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.


    Comments

    Empty