A Python driver module for Brilliant Labs Monocle
I bundled up the things I learned about driving a Brilliant Labs Monocle into a new Python package. While doing the fine-tuning, I learned a bit more about driving the MicroPython UART interface.
Worth noting: I only discovered it after I completed this project, but another good example of using Python to drive Monocle actually comes from the Monocle’s creators.
Fine-tuning the connection
The UART service has a maximum size of data it can consume in one “chunk;” overflowing this limit can result in dropping bytes. As of right now, the count is 128 bytes (- 3 bytes for overhead). For some reason I haven’t sussed out yet, the BLE characteristic reports the size is only 20 bytes, so hard-coding it to 128 sped it up. The Bleak module will do chunking for you if you set the mtu_size, but I implemented my own (mostly because I didn’t know Bleak would handle it for me, but now that it’s implemented I like the logging so I’m keeping it.
De-automating the REPL’s terminal
By default, the MicroPython REPL in the Monocle firmware has some user-friendliness features that are extremely convenient for hand-coding MicroPython, but complicate passing MicroPython through as literal strings. To that end, the module wraps the commands it’s sending in
- Control-C, to stop any running programs on the Monocle
- Control-A, to set the REPL to “raw” mode where it accepts input but does not interpret it.
- After the program is sent, Control-D to accept and interpret the sent code.
With these changes in place, the library far more reliably sends programs. A short demo below shows how to send a program that updates the battery status once a second for five seconds.
import asyncio
from brilliant_monocle_driver import Monocle
def callback(channel, text_in):
"""
Callback to handle incoming text from the Monocle.
"""
print(text_in)
# Simple MicroPython command that prints battery level for five seconds
# and then blanks the screen
COMMAND = """
import display
import device
import time
def show_battery(count):
batLvl = str(device.battery_level())
display.fill(0x000066)
display.text("bat: {} {}".format(batLvl, count), 5, 5, 0xffffff)
display.show()
count = 0
while (count < 5):
show_battery(count)
time.sleep(1)
count += 1
display.fill(0x000000)
display.show()
print("Done")
"""
async def execute():
mono = Monocle(callback)
async with mono:
await mono.send(COMMAND)
asyncio.run(execute())
What’s next?
Now that the module is working, I can focus on doing things on the Monocle itself. Next, I’m going to try and plumb data from the Monocle back to react to the control inputs.