From 8cebec67124168aceba752713bef52e542fce082 Mon Sep 17 00:00:00 2001 From: bepis Date: Sat, 27 Jun 2026 17:35:30 +1000 Subject: [PATCH] Creation of main.py files --- BaseStation/main.py | 192 ++++++++++++++++++++++++++++++++++++++++++++ main.py | 192 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 384 insertions(+) create mode 100644 BaseStation/main.py create mode 100644 main.py diff --git a/BaseStation/main.py b/BaseStation/main.py new file mode 100644 index 0000000..12809f5 --- /dev/null +++ b/BaseStation/main.py @@ -0,0 +1,192 @@ +# base_station_main.py -- Trivia buzzer JUDGE (Raspberry Pi Pico W) +# +# Modes: "quizbowl" (live on reset) or "jeopardy" (stand-by until activated). +# Control button GP15 -> GND: +# quizbowl : press = reset / re-arm. +# jeopardy : press on STAND BY = ACTIVATE; press when ARMED/LOCKED = reset. +# +# Color LEDs: the base lights the winning team's color (blue or green), +# matching the buzzer NAMEs. Onboard LED: solid = someone buzzed. + +import network, socket, select, time +from machine import Pin + +SSID = "TriviaBuzzers" +PASSWORD = "buzzin123" +PORT = 5000 + +MODE = "jeopardy" # "jeopardy" or "quizbowl" +LOCK_TIMEOUT_MS = 10000 +BATT_LOW_V = 3.60 + +BUTTON_PIN = 15 # control button GP15 -> GND +BLUE_LED_PIN = 16 # base's blue LED -> resistor -> GND +GREEN_LED_PIN = 17 # base's green LED -> resistor -> GND + +led = Pin("LED", Pin.OUT) # onboard: solid = buzzed +button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP) + +# winner NAME -> indicator LED on the base +TEAM_LEDS = { + "blue": Pin(BLUE_LED_PIN, Pin.OUT), + "green": Pin(GREEN_LED_PIN, Pin.OUT), +} + +def set_team_led(name): + for k, p in TEAM_LEDS.items(): + p.value(1 if k == name else 0) + +def team_leds_off(): + for p in TEAM_LEDS.values(): + p.value(0) + +# --- Wi-Fi access point --- +ap = network.WLAN(network.AP_IF) +ap.config(essid=SSID, password=PASSWORD) +ap.active(True) +while not ap.active(): + time.sleep(0.1) +print("AP ready:", ap.ifconfig()) + +# --- TCP server --- +srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +srv.bind(("0.0.0.0", PORT)) +srv.listen(8) +srv.setblocking(False) + +poller = select.poll() +poller.register(srv, select.POLLIN) + +clients = {} +phase = "standby" +winner = None +lock_time = 0 + +def send(sock, msg): + try: + sock.send(msg) + except OSError: + pass + +def state_msg(): + if phase == "locked": + return b"LOCKED " + winner.encode() + b"\n" + if phase == "standby": + return b"STANDBY\n" + return b"ARMED\n" + +def broadcast(msg, exclude=None): + for c in list(clients.values()): + if c["sock"] is not exclude: + send(c["sock"], msg) + +def battery_summary(): + if not clients: + return + parts = [] + for c in clients.values(): + v = c.get("batt") + if v is None: + parts.append("%s ?" % c["name"]) + else: + parts.append("%s %.2f%s" % (c["name"], v, "!" if v < BATT_LOW_V else "")) + print("Batteries:", " ".join(parts)) + +def go_armed(): + global phase, winner + phase, winner = "armed", None + led.off() + team_leds_off() + broadcast(b"ARMED\n") + print("--- ARMED (buzz now) ---") + +def go_standby(): + global phase, winner + phase, winner = "standby", None + led.off() + team_leds_off() + broadcast(b"STANDBY\n") + print("--- STAND BY ---") + +def start_round(): + battery_summary() + if MODE == "jeopardy": + go_standby() + else: + go_armed() + +def handle_line(fd, line): + global phase, winner, lock_time + c = clients.get(fd) + if not c: + return + if line.startswith(b"HELLO"): + parts = line.split() + if len(parts) > 1: + c["name"] = parts[1].decode() + send(c["sock"], state_msg()) + elif line.startswith(b"BATT"): + parts = line.split() + if len(parts) > 1: + try: + v = float(parts[1]) + except ValueError: + return + was = c.get("batt") + c["batt"] = v + if v < BATT_LOW_V and (was is None or was >= BATT_LOW_V): + print("[BATTERY LOW] %s = %.2f V" % (c["name"], v)) + elif line.startswith(b"PRESS"): + if phase == "armed": + phase, winner = "locked", c["name"] + lock_time = time.ticks_ms() + led.on() + set_team_led(winner) + print("WINNER:", winner) + send(c["sock"], b"WIN\n") + broadcast(b"LOCKED " + winner.encode() + b"\n", exclude=c["sock"]) + else: + send(c["sock"], state_msg()) + +start_round() +last_btn = 0 + +while True: + for sock, ev in poller.poll(20): + if sock is srv: + conn, addr = srv.accept() + conn.setblocking(False) + poller.register(conn, select.POLLIN) + clients[conn.fileno()] = {"sock": conn, "name": str(addr[0]), + "buf": b"", "batt": None} + send(conn, state_msg()) + else: + fd = sock.fileno() + try: + data = sock.recv(64) + except OSError: + continue + if not data: + poller.unregister(sock) + clients.pop(fd, None) + sock.close() + continue + c = clients.get(fd) + if not c: + continue + c["buf"] += data + while b"\n" in c["buf"]: + line, c["buf"] = c["buf"].split(b"\n", 1) + handle_line(fd, line.strip()) + + if button.value() == 0 and time.ticks_diff(time.ticks_ms(), last_btn) > 300: + last_btn = time.ticks_ms() + if MODE == "jeopardy" and phase == "standby": + go_armed() + else: + start_round() + + if phase == "locked" and LOCK_TIMEOUT_MS and time.ticks_diff(time.ticks_ms(), lock_time) > LOCK_TIMEOUT_MS: + print("auto-reset after timeout") + start_round() \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..12809f5 --- /dev/null +++ b/main.py @@ -0,0 +1,192 @@ +# base_station_main.py -- Trivia buzzer JUDGE (Raspberry Pi Pico W) +# +# Modes: "quizbowl" (live on reset) or "jeopardy" (stand-by until activated). +# Control button GP15 -> GND: +# quizbowl : press = reset / re-arm. +# jeopardy : press on STAND BY = ACTIVATE; press when ARMED/LOCKED = reset. +# +# Color LEDs: the base lights the winning team's color (blue or green), +# matching the buzzer NAMEs. Onboard LED: solid = someone buzzed. + +import network, socket, select, time +from machine import Pin + +SSID = "TriviaBuzzers" +PASSWORD = "buzzin123" +PORT = 5000 + +MODE = "jeopardy" # "jeopardy" or "quizbowl" +LOCK_TIMEOUT_MS = 10000 +BATT_LOW_V = 3.60 + +BUTTON_PIN = 15 # control button GP15 -> GND +BLUE_LED_PIN = 16 # base's blue LED -> resistor -> GND +GREEN_LED_PIN = 17 # base's green LED -> resistor -> GND + +led = Pin("LED", Pin.OUT) # onboard: solid = buzzed +button = Pin(BUTTON_PIN, Pin.IN, Pin.PULL_UP) + +# winner NAME -> indicator LED on the base +TEAM_LEDS = { + "blue": Pin(BLUE_LED_PIN, Pin.OUT), + "green": Pin(GREEN_LED_PIN, Pin.OUT), +} + +def set_team_led(name): + for k, p in TEAM_LEDS.items(): + p.value(1 if k == name else 0) + +def team_leds_off(): + for p in TEAM_LEDS.values(): + p.value(0) + +# --- Wi-Fi access point --- +ap = network.WLAN(network.AP_IF) +ap.config(essid=SSID, password=PASSWORD) +ap.active(True) +while not ap.active(): + time.sleep(0.1) +print("AP ready:", ap.ifconfig()) + +# --- TCP server --- +srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +srv.bind(("0.0.0.0", PORT)) +srv.listen(8) +srv.setblocking(False) + +poller = select.poll() +poller.register(srv, select.POLLIN) + +clients = {} +phase = "standby" +winner = None +lock_time = 0 + +def send(sock, msg): + try: + sock.send(msg) + except OSError: + pass + +def state_msg(): + if phase == "locked": + return b"LOCKED " + winner.encode() + b"\n" + if phase == "standby": + return b"STANDBY\n" + return b"ARMED\n" + +def broadcast(msg, exclude=None): + for c in list(clients.values()): + if c["sock"] is not exclude: + send(c["sock"], msg) + +def battery_summary(): + if not clients: + return + parts = [] + for c in clients.values(): + v = c.get("batt") + if v is None: + parts.append("%s ?" % c["name"]) + else: + parts.append("%s %.2f%s" % (c["name"], v, "!" if v < BATT_LOW_V else "")) + print("Batteries:", " ".join(parts)) + +def go_armed(): + global phase, winner + phase, winner = "armed", None + led.off() + team_leds_off() + broadcast(b"ARMED\n") + print("--- ARMED (buzz now) ---") + +def go_standby(): + global phase, winner + phase, winner = "standby", None + led.off() + team_leds_off() + broadcast(b"STANDBY\n") + print("--- STAND BY ---") + +def start_round(): + battery_summary() + if MODE == "jeopardy": + go_standby() + else: + go_armed() + +def handle_line(fd, line): + global phase, winner, lock_time + c = clients.get(fd) + if not c: + return + if line.startswith(b"HELLO"): + parts = line.split() + if len(parts) > 1: + c["name"] = parts[1].decode() + send(c["sock"], state_msg()) + elif line.startswith(b"BATT"): + parts = line.split() + if len(parts) > 1: + try: + v = float(parts[1]) + except ValueError: + return + was = c.get("batt") + c["batt"] = v + if v < BATT_LOW_V and (was is None or was >= BATT_LOW_V): + print("[BATTERY LOW] %s = %.2f V" % (c["name"], v)) + elif line.startswith(b"PRESS"): + if phase == "armed": + phase, winner = "locked", c["name"] + lock_time = time.ticks_ms() + led.on() + set_team_led(winner) + print("WINNER:", winner) + send(c["sock"], b"WIN\n") + broadcast(b"LOCKED " + winner.encode() + b"\n", exclude=c["sock"]) + else: + send(c["sock"], state_msg()) + +start_round() +last_btn = 0 + +while True: + for sock, ev in poller.poll(20): + if sock is srv: + conn, addr = srv.accept() + conn.setblocking(False) + poller.register(conn, select.POLLIN) + clients[conn.fileno()] = {"sock": conn, "name": str(addr[0]), + "buf": b"", "batt": None} + send(conn, state_msg()) + else: + fd = sock.fileno() + try: + data = sock.recv(64) + except OSError: + continue + if not data: + poller.unregister(sock) + clients.pop(fd, None) + sock.close() + continue + c = clients.get(fd) + if not c: + continue + c["buf"] += data + while b"\n" in c["buf"]: + line, c["buf"] = c["buf"].split(b"\n", 1) + handle_line(fd, line.strip()) + + if button.value() == 0 and time.ticks_diff(time.ticks_ms(), last_btn) > 300: + last_btn = time.ticks_ms() + if MODE == "jeopardy" and phase == "standby": + go_armed() + else: + start_round() + + if phase == "locked" and LOCK_TIMEOUT_MS and time.ticks_diff(time.ticks_ms(), lock_time) > LOCK_TIMEOUT_MS: + print("auto-reset after timeout") + start_round() \ No newline at end of file