Source: A1 — reTerminal Control Center

Stack Overview

  • Python backend: FastAPI REST API controlling on-board devices
  • TypeScript frontend: React/bun dashboard on reTerminal touchscreen
  • HTTP communication between backend and frontend

uv Package Manager

uv replaces venv + pip for this assignment.

# Initialize project
uv init controller      # creates main.py, pyproject.toml, README.md
 
# Manage dependencies
uv add fastapi['standard']
uv add smbus2
uv remove <package>
 
# Run the app
fastapi run             # production (accessible on local network)
fastapi dev             # development with auto-reload

pyproject.toml tracks all dependencies (like requirements.txt but modern).

AHT20 Sensor via smbus2

Seeed-grove.py is broken/deprecated — use this direct smbus2 implementation.

Critical: reTerminal uses I2C bus 4, not bus 1.

from gpiozero import Device
from time import sleep
from smbus2 import SMBus
 
class AHT20(Device):
    def __init__(self, address=0x38, bus=4):   # bus=4 on reTerminal!
        self.address = address
        self.bus = SMBus(bus)
        # Initialize sensor
        self.bus.write_i2c_block_data(self.address, 0xBE, [0x08, 0x00])
        sleep(0.02)
 
    def read(self):
        # Trigger measurement
        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   # (°C, %RH)
 
# Usage: only ONE AHT20 instance at a time (shared across sensors)
aht20_device = AHT20(bus=4)

Sensor / Actuator Model (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 a 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

Concrete implementations:

class TemperatureSensor(Sensor):
    device: AHT20
    UNIT = "°C"
    def read_sensor(self):
        temp, _ = self.device.read()
        return f"{temp:.1f}{self.UNIT}"
 
class HumiditySensor(Sensor):
    device: AHT20
    UNIT = "%"
    def read_sensor(self):
        _, humi = self.device.read()
        return f"{humi:.1f}{self.UNIT}"
 
class LED(Actuator):
    pass   # implement control_actuator
 
class Fan(Actuator):
    pass   # implement control_actuator

FastAPI Endpoints (app.py)

from fastapi import FastAPI
 
app = FastAPI()
 
# Register sensors and actuators with IDs
sensors = {"temperature": TemperatureSensor(aht20), "humidity": HumiditySensor(aht20)}
actuators = {"led": led_actuator, "fan": fan_actuator}
 
@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)}

Run: fastapi run (accessible via Tailscale IP)

Landscape Mode on reTerminal

# List connected displays
wlr-randr
 
# Rotate screen 270°
wlr-randr --output <display-name> --transform 270

Or: Control Centre → Screens → right-click display → change orientation.

SSH Display Fix

If running GUI/dashboard code over SSH and getting “cannot open display”:

export DISPLAY=:0
# then run your dashboard command

VNC for Dashboard Preview

Enable VNC on reTerminal, then use TigerVNC from lab computer to connect via Tailscale IP.

Project Structure

a1/
├── controller/         # Python backend
│   ├── app.py         # FastAPI app (rename from main.py)
│   ├── model.py       # Sensor / Actuator base classes
│   ├── led.py         # LED class
│   ├── fan.py         # Fan class
│   ├── aht20.py       # AHT20 + TemperatureSensor + HumiditySensor
│   └── pyproject.toml # uv dependencies
└── dashboard/          # Frontend
    ├── package.json
    └── bun.lock

See Also