Interfaces
Group Assignment
I have explored a decent amount of interface and visualization tools from using processing to do simple edge detection , to javascript for my personal website, and computing libraries visualization tools: NumPy, matplotlib, Seaborn, Jax, Torch, Plotly, Triton, Manim. After comparing them, I like to use Torch / Jax for my ML work, and Matplotlib + Manim for visualization.
For this week I kept it simple and used my previous week’s output LEDS with Bluetooth Low Energy using the Pico W. I kept the same code running on the Pico W, and then I wanted to explore Tkinter. I have used it a long time ago, and I had the help of ChatGPT To make a simple interface with 3 buttons. https://chatgpt.com/share/6925c885-500c-8007-8dcc-07492a3903cb
import asyncio
import threading
import tkinter as tk
from bleak import BleakScanner, BleakClient
UART_RX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
client = None
status_label = None
async def ble_connect(loop):
"""Connect to PicoLED asynchronously, update GUI when done."""
global client, status_label
try:
status_label.config(text="Scanning for PicoLED…")
devices = await BleakScanner.discover()
pico = None
for d in devices:
if d.name == "PicoLED":
pico = d
break
if pico is None:
status_label.config(text="PicoLED not found")
return
status_label.config(text=f"Found {pico.name}, connecting…")
client = BleakClient(pico.address)
await client.connect()
status_label.config(text="Connected!")
print("Connected to PicoLED!")
except Exception as e:
status_label.config(text=f"Error: {e}")
async def send_cmd(cmd):
global client
if client is None or not client.is_connected:
print("Not connected!")
status_label.config(text="Not connected")
return
await client.write_gatt_char(UART_RX, cmd.encode())
print("Sent:", cmd)
status_label.config(text=f"Sent '{cmd}'")
def run_asyncio(loop):
asyncio.set_event_loop(loop)
loop.run_forever()
def make_gui(loop):
global status_label
root = tk.Tk()
root.title("PicoLED Controller")
root.geometry("300x240")
tk.Label(root, text="PicoLED Controller", font=("Arial", 18)).pack(pady=10)
status_label = tk.Label(root, text="Connecting…", font=("Arial", 12))
status_label.pack(pady=5)
# Button helper
def send(cmd):
asyncio.run_coroutine_threadsafe(send_cmd(cmd), loop)
tk.Button(root, text="Red", font=("Arial", 14), bg="red", fg="white",
command=lambda: send("red")).pack(fill="x", padx=20, pady=5)
tk.Button(root, text="Green", font=("Arial", 14), bg="green", fg="white",
command=lambda: send("green")).pack(fill="x", padx=20, pady=5)
tk.Button(root, text="Off", font=("Arial", 14), bg="#444", fg="white",
command=lambda: send("off")).pack(fill="x", padx=20, pady=5)
# Start connection AFTER GUI shows
root.after(100, lambda: asyncio.run_coroutine_threadsafe(ble_connect(loop), loop))
root.mainloop()
if __name__ == "__main__":
loop = asyncio.new_event_loop()
# background thread running asyncio
t = threading.Thread(target=run_asyncio, args=(loop,), daemon=True)
t.start()
# main thread runs GUI
make_gui(loop)
Let’s unpack this code. There are 2 key changes, first using threading. Threading allows for multiple processes to run “simultaneously” - I use quotes because if you just have 1 core, it needs to keep switching between threads. This is from my knowledge from my OS class in undergrad. So we will have one thread running the asynchronous BLE logic. Tkinter runs on the main thread, and when the button is pressed
tk.Button(root, text="Off", font=("Arial", 14), bg="#444", fg="white",
command=lambda: send("off")).pack(fill="x", padx=20, pady=5)We can see it sends a command
# Button helper
def send(cmd):
asyncio.run_coroutine_threadsafe(send_cmd(cmd), loop)
---
async def send_cmd(cmd):
global client
if client is None or not client.is_connected:
print("Not connected!")
status_label.config(text="Not connected")
return
await client.write_gatt_char(UART_RX, cmd.encode())
print("Sent:", cmd)
status_label.config(text=f"Sent '{cmd}'")
Here is a video of it working