| Device | OS | Python | Notes |
|---|---|---|---|
| Laptop/Server | Linux / macOS / Windows | 3.11+ / Node 20+ | Server + React web UI |
| Raspberry Pi 5 | Raspberry Pi OS (64-bit) | 3.11+ | Display client |
| Smartwatch | ESP32 Arduino toolchain | — | Firmware flashing |
cd padel_system/server
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtcd ../web
npm install
npm run build
cd ../servercp ../.env.example .env
# Edit .env:
# DATABASE_URL=sqlite:///./padel.db
# DEVICE_TOKEN=your-secret-token
# MEDIA_DIR=./uploads
# SERVER_PUBLIC_URL=http://YOUR_SERVER_LAN_IP:8000
# SERVER_HOST=0.0.0.0
# SERVER_PORT=8000
# SECRET_KEY=change-this-long-random-value
# DEFAULT_ADMIN_EMAIL=admin@padel.local
# DEFAULT_ADMIN_PASSWORD=change-this-password
# DEFAULT_CLIENT_EMAIL=client@padel.local
# DEFAULT_CLIENT_PASSWORD=change-this-too
# DEFAULT_CLIENT_COURTS=["court_01"]# Development (auto-reload)
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
# Production (no reload, workers=1 for SQLite)
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 1- Open http://localhost:8000 → React login
- Admin portal: http://localhost:8000/admin
- Client portal: http://localhost:8000/client
- Legacy dashboard: http://localhost:8000/legacy/admin
- Open http://localhost:8000/health → JSON health check
The server already supports any hostname through SERVER_PUBLIC_URL. Pick the
option that fits the venue:
Works on any LAN where Avahi/Bonjour is allowed (most home and small-club networks).
sudo apt install -y avahi-daemon
sudo hostnamectl set-hostname padel
sudo systemctl enable --now avahi-daemonSet in server/.env:
SERVER_PUBLIC_URL=http://padel.local:8000
Restart the server. Now everyone on the same Wi-Fi opens http://padel.local:8000
and Pi displays use ws://padel.local:8000. macOS, Windows 10+ and most Android
phones resolve *.local natively. iPhones, iPads and Linux desktops with Avahi
do too.
Better for permanent installations or networks where mDNS is blocked.
- Reserve the server's MAC address to a fixed IP on the router.
- Add a local DNS entry (e.g.
padel,padel-server, orscoreboard). - Use that hostname in
SERVER_PUBLIC_URL, the PiSERVER_*_URL, and the watch firmwareSERVER_HOST.
If you want plain http://padel.local, run a tiny reverse proxy on port 80.
This does not change the FastAPI server — keep it on 0.0.0.0:8000.
Caddy (single-line config):
sudo apt install -y caddy
sudo tee /etc/caddy/Caddyfile <<'EOF'
:80 {
reverse_proxy 127.0.0.1:8000
}
EOF
sudo systemctl reload caddyNginx (/etc/nginx/sites-available/padel):
server {
listen 80;
server_name padel.local _;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
}
}After enabling, also set SERVER_PUBLIC_URL=http://padel.local (no port) so
download URLs sent to displays match.
For ESP32 watches: prefer Option B (router DNS or reserved IP). The arduinoWebSockets library does not perform mDNS resolution reliably across all Wi-Fi chipsets.
cd server
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python -m pytest tests/ -v- Open
http://{SERVER_IP}:8000. - Sign in with
DEFAULT_ADMIN_EMAIL/DEFAULT_ADMIN_PASSWORD. - Create a court from the admin dashboard.
- Register/assign watch and display devices with the device token.
- Start a match from the court card.
- Upload ads from the admin dashboard; online displays receive
media_sync.
- Connect laptop/server, Pi displays, and watches to the new Wi-Fi.
- Find the new server IP, or configure the router DNS name again.
- Update
SERVER_PUBLIC_URLinserver/.env. - Update Pi
SERVER_WS_URLandSERVER_HTTP_URL. - On watches, use the NET panel for Wi-Fi credentials. If
SERVER_HOSTchanged and you do not use stable DNS, reflash firmware with the new host. - Restart server and displays, then verify
/healthand one test score.
For deployments, consider shipping a small dedicated router with a fixed SSID and DHCP reservation. That keeps the system address stable at every venue.
sudo apt update
sudo apt install -y python3-pip python3-venv libgl1 libglib2.0-0 \
gstreamer1.0-plugins-good gstreamer1.0-plugins-bad \
python3-pyside6cd padel_system/pi_display
python3 -m venv .venv --system-site-packages
source .venv/bin/activate
pip install -r requirements.txt# Create /opt/padel_display/media directory
sudo mkdir -p /opt/padel_display/media
sudo chown $USER:$USER /opt/padel_display/media
# Set environment variables (or create .env in pi_display/)
export DISPLAY_ID=display_01
export DISPLAY_COURT_ID=court_01
# Match whatever hostname you chose for the server. Fall back to a LAN IP
# only if mDNS or router DNS is unavailable.
export SERVER_WS_URL=ws://padel.local:8000
export SERVER_HTTP_URL=http://padel.local:8000
export DEVICE_TOKEN=your-secret-token
export MEDIA_CACHE_DIR=/opt/padel_display/mediacd padel_system/pi_display
python3 -m app.main# /etc/systemd/system/padel-display.service
[Unit]
Description=Padel Display
After=network-online.target graphical.target
[Service]
User=pi
WorkingDirectory=/home/pi/padel_system/pi_display
Environment=DISPLAY=:0
Environment=DISPLAY_ID=display_01
Environment=DISPLAY_COURT_ID=court_01
Environment=SERVER_WS_URL=ws://padel.local:8000
Environment=DEVICE_TOKEN=your-secret-token
ExecStart=/home/pi/padel_system/pi_display/.venv/bin/python3 -m app.main
Restart=always
RestartSec=5
[Install]
WantedBy=graphical.targetsudo systemctl enable padel-display
sudo systemctl start padel-display- Install Arduino IDE 2.x
- Add ESP32 board package:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - Select board: ESP32S3 Dev Module (or specific Waveshare board if available)
arduinoWebSocketsby Markus Sattler (WebSocketsClient)lvgl(version matching lv_conf.h)Arduino_GFX_LibraryArduino_DriveBus_LibraryXPowersLibSensorPCF85063(or include from Waveshare SDK)
Copy pin_config.h and Waveshare board support files into the sketch folder
(same as the original SmartWatch.ino project).
Edit config.h before flashing:
#define WATCH_ID "watch_01" // unique per device
#define COURT_ID "court_01" // matches admin assignment
#define DEVICE_TOKEN "your-secret-token"
#define SERVER_HOST "10.144.186.211"
#define SERVER_PORT 8000
#define DEFAULT_WIFI_SSID "YourSSID"
#define DEFAULT_WIFI_PASSWORD "YourPassword"- Connect watch via USB
- Open
smartwatch.inoin Arduino IDE - Select correct port
- Upload
- Watch will try to connect to DEFAULT_WIFI_SSID
- Tap NET button on screen to change WiFi credentials
- After connecting, it auto-connects to the WebSocket server
- Score with BOOT button or touch buttons
| Gesture | Action |
|---|---|
| Single click | point_a |
| Double click | point_b |
| Triple click | undo |
| Long press (0.7s) | timer_toggle |
Change mapping in config.h:
#define MAP_BOOT_SINGLE "point_a"
#define MAP_BOOT_DOUBLE "point_b"
#define MAP_BOOT_TRIPLE "undo"
#define MAP_BOOT_LONG "timer_toggle"-
GET /healthreturns{"status":"ok"} - Admin dashboard loads at
http://{SERVER_IP}:8000 - Can create court, register devices, create match
- Watch connects (Serial prints
[WS] Server accepted hello — READY) - BOOT single-click → server logs
action=point_a applied - Admin dashboard shows watch online
- Display connects and shows court name / team names
- Score updates within ~150 ms of watch button press
- Upload image to media → display shows it within next cycle
cd server
# First create court and match via admin UI, then:
python tests/fake_watch_client.py \
--host localhost --port 8000 \
--watch-id watch_01 --court-id court_01 \
--token padel-secret-token-change-me
# Press 'a' + Enter to score point_a
# Open display app or admin dashboard to see score update live