FastAPI and HTTP Backend

uv Package Manager

uv is a modern Python package manager that replaces venv + pip.

uv init myproject      # create project with pyproject.toml
uv add fastapi['standard']   # add dependency
uv add smbus2
uv remove <package>    # remove dependency
uvpip + venv
Project inituv initpython -m venv .venv
Installuv add <pkg>pip install <pkg>
Lock filepyproject.tomlrequirements.txt
Modern?Yes (2024+)Standard but older

FastAPI Setup

uv add fastapi['standard']
# app.py
from fastapi import FastAPI
 
app = FastAPI()
 
@app.get("/")
def root():
    return {"status": "ok"}

Run:

fastapi run      # production mode (accessible on network via Tailscale IP)
fastapi dev      # development with auto-reload

Auto-generated API docs available at /docs endpoint.

Sensor / Actuator Pattern (model.py)

from typing import Any
 
class Sensor():
    device: Any
    UNIT: str = ""
 
    def __init__(self, device: Any):
        self.device = device
 
    def read_sensor(self) -> str:
        """Returns current reading as string."""
        pass
 
class Actuator():
    device: Any
    state: str = ""
 
    def __init__(self, device: Any):
        self.device = device
 
    def control_actuator(self, state: str) -> str:
        """Changes state, returns new state as string."""
        pass

REST Endpoints

@app.get("/read/{sensor_id}")
def read_sensor(sensor_id: str):
    return {"value": sensors[sensor_id].read_sensor()}
 
@app.put("/control/{actuator_id}")
def control_actuator(actuator_id: str, state: str):
    return {"state": actuators[actuator_id].control_actuator(state)}

Sensor IDs: "temperature", "humidity" Actuator IDs: "led", "fan"

AHT20 via smbus2

CRITICAL: reTerminal uses I2C bus 4, not bus 1. The Seeed-grove.py library is broken — use this directly.

from smbus2 import SMBus
from time import sleep
 
class AHT20:
    def __init__(self, address=0x38, bus=4):
        self.address = address
        self.bus = SMBus(bus)
        self.bus.write_i2c_block_data(self.address, 0xBE, [0x08, 0x00])
        sleep(0.02)
 
    def read(self):
        self.bus.write_i2c_block_data(self.address, 0xAC, [0x33, 0x00])
        sleep(0.08)
        data = self.bus.read_i2c_block_data(self.address, 0x00, 7)
 
        humidity = ((data[1] << 16) | (data[2] << 8) | data[3]) >> 4
        humidity = humidity * 100 / 1048576.0
 
        temperature = ((data[3] & 0x0F) << 16) | (data[4] << 8) | data[5]
        temperature = temperature * 200 / 1048576.0 - 50
 
        return temperature, humidity
 
# One instance shared by both TemperatureSensor and HumiditySensor
aht20 = AHT20(bus=4)

reTerminal Display

# Rotate screen to landscape (270°)
wlr-randr --output <display-name> --transform 270
 
# Fix "cannot open display" when running GUI over SSH
export DISPLAY=:0

See Also