Cableless Development with Micropython Server#

Ever tired of having to connect your microcontroller to your laptop with a cable? Wifi-able chips and remote servers come to the rescue.

I encountered this problem recently when I was building a robot car to prepare for the Scioly Robot Tour Event. This is why I hand-wrote a tiny http server based on Microdot to deal with this situation.

This server is capable of displaying the files stored on your microcontroller as well as updating them through an http POST request.

The following is the entire Python script for the server.

server.py#
  1from microdot import Microdot
  2from tarfile import TarFile
  3from pathlib import Path
  4from network import WLAN
  5
  6import tarfile
  7import network
  8import time
  9import os
 10import io
 11
 12server = Microdot()
 13
 14DIR_HTML = """\
 15<html>
 16    <head>
 17        <title>{dir}</title>
 18    </head>
 19    <body>
 20        <h1>{dir}</h1>
 21        <ul>
 22            {files}
 23        </ul>
 24    </body>
 25<html>
 26"""
 27
 28DIR_ELEMENT = """\
 29<li>
 30    <a href="{url}">{text}</a>
 31</li>
 32"""
 33
 34HOST_CONFIG = {
 35    "essid": "<your-ssid>",
 36    "password": "<your-password>"
 37}
 38
 39
 40@server.post("/update")
 41def update_program(request):
 42    tar = TarFile(fileobj=io.BytesIO(request.body))
 43    try:
 44        for file in tar:
 45            if file.type == tarfile.DIRTYPE:
 46                Path(file.name).mkdir(parents=True, exist_ok=True)
 47            else:
 48                contents = tar.extractfile(file)
 49                Path(file.name).write_bytes(contents.read())
 50    except OSError as e:
 51        return {
 52            "status": "error",
 53            "details": str(e),
 54        }
 55
 56    return {"status": "success"}
 57
 58
 59def _format_html(path):
 60    return DIR_HTML.format(
 61        dir=path,
 62        files="\n".join(
 63            DIR_ELEMENT.format(
 64                url=f"/files{(path / file).resolve()}",
 65                text=file,
 66            )
 67            for file in os.listdir(path.resolve())
 68        ),
 69    ), {"Content-Type": "text/html"}
 70
 71
 72@server.get("/files")
 73def display_file_root(request):
 74    return _format_html(Path("/"))
 75
 76
 77@server.get("/files/<path:path>")
 78def display_file(request, path):
 79    path = Path(path)
 80    if not path.exists():
 81        return str(path), 404
 82    if path.is_file():
 83        return path.read_text(), {"Content-Type": "text/plain"}
 84
 85    # a directory
 86    return _format_html(path)
 87
 88
 89@server.get("/shutdown")
 90def shutdown(request):
 91    request.app.shutdown()
 92    return {"status": "success"}
 93
 94
 95def setup():
 96    ap = WLAN(network.AP_IF)
 97    ap.active(True)
 98    ap.config(**HOST_CONFIG)
 99
100    while not ap.active():
101        time.sleep_ms(100)
102
103
104def run():
105    server.run()

To run this server, first install the necessary packages with mip:

import mip
mip.install("tarfile")
mip.install("pathlib")
mip.install("github:miguelgrinberg/microdot/src/microdot.py")

Note

You may need to compile microdot.py with mpy-cross to avoid MemoryErrors.

Then simply import the server and run it!

import server
server.setup()
server.run()  # This will never return unless shutdown is requested

After running it, connect your laptop to the microcontroller via WiFi and go to http://192.168.4.1:5000/files (or whichever IP your server is running on) and you should see your files!

To update the files via the server, archive your code with tar and send an http POST request to http://192.168.4.1:5000/update. With tar and httpx, it looks like this:

tar -cf app.tar ./app

followed by

import httpx
httpx.post(
    f"http://192.168.4.1:5000/update",
    content=b"<tar file in bytes>",
)

Have fun developing cableless!