Skip to content

Commit 73e7283

Browse files
committed
Add audio/video streaming and web tester for telemetry
Introduces bidirectional audio and video streaming using GStreamer pipelines, with new modules for audio (src/audio.py) and video (src/video.py). Adds a new main entrypoint (main.py) to orchestrate telemetry, audio, and video processes. Refactors telemetry.py to src/data.py and enhances UDP socket priority and stats reporting. Adds a web-tester Flask/SocketIO backend and car frontend for browser-based audio/video testing, including Docker/test scripts and documentation updates for hardware setup and environment variables.
1 parent 4275f69 commit 73e7283

14 files changed

Lines changed: 961 additions & 46 deletions

File tree

test_local.sh

Lines changed: 0 additions & 37 deletions
This file was deleted.

universal-telemetry-software/Dockerfile

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,46 @@ FROM python:3.11-slim
22

33
WORKDIR /app
44

5-
# Install system dependencies for CAN and networking
5+
# Install system dependencies for CAN, networking, and GStreamer (Audio/Video)
66
RUN apt-get update && apt-get install -y \
77
can-utils \
88
iproute2 \
9+
python3-gi \
10+
python3-gst-1.0 \
11+
gir1.2-gstreamer-1.0 \
12+
gstreamer1.0-tools \
13+
gstreamer1.0-plugins-base \
14+
gstreamer1.0-plugins-good \
15+
gstreamer1.0-plugins-bad \
16+
gstreamer1.0-plugins-ugly \
17+
gstreamer1.0-libav \
18+
gstreamer1.0-alsa \
19+
libgirepository1.0-dev \
20+
libgirepository-2.0-dev \
21+
libcairo2-dev \
22+
pkg-config \
23+
gcc \
24+
python3-dev \
25+
libglib2.0-dev \
26+
libgl1 \
27+
gobject-introspection \
928
&& rm -rf /var/lib/apt/lists/*
1029

1130
COPY requirements.txt .
1231
RUN pip install --no-cache-dir -r requirements.txt
1332

14-
COPY telemetry.py .
33+
# Copy source code
34+
COPY src/ ./src/
35+
COPY web-tester/ ./web-tester/
36+
COPY main.py .
1537

1638
# Use environment variables for configuration
1739
ENV ROLE=auto
1840
ENV REMOTE_IP=192.168.1.100
1941
ENV UDP_PORT=5005
2042
ENV TCP_PORT=5006
2143
ENV REDIS_URL=redis://localhost:6379/0
44+
ENV ENABLE_AUDIO=true
45+
ENV ENABLE_VIDEO=true
2246

23-
CMD ["python", "telemetry.py"]
24-
47+
CMD ["python", "main.py"]

universal-telemetry-software/README.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,44 @@ This is a unified firmware/software for both the Car (Source) and Base Station (
1212
- **Sequence Tracking** on the base station.
1313
- **TCP Retransmission**: Every 10s, the base station requests missing packets from the car.
1414
- **Frontend Integration**: Publishes JSON messages to Redis for the `redis_ws_bridge`.
15+
- **Bidirectional Audio**: Low-latency voice comms using OPUS/UDP.
1516

16-
## RPi Setup (CAN)
17-
Ensure your CAN hat (e.g., MCP2515) is configured in `/boot/config.txt`:
17+
## Hardware Setup (RPi 5)
18+
19+
### 1. CAN Bus
20+
Ensure your CAN hat (e.g., MCP2515) is configured in `/boot/config.txt`. Note that RPi 5 uses a different SPI controller sometimes, but standard overlays usually work:
1821
```text
1922
dtparam=spi=on
2023
dtoverlay=mcp2515-can0,oscillator=16000000,interrupt=25
2124
```
22-
Then initialize the interface:
25+
Initialize:
2326
```bash
2427
sudo ip link set can0 up type can bitrate 500000
2528
```
2629

30+
### 2. Audio (No 3.5mm Jack)
31+
The RPi 5 has no analog audio. You must use a digital interface.
32+
33+
#### Option A: I2S Audio HAT (Recommended for FSAE)
34+
Use a HAT like the **WM8960** or **HiFiBerry**. These use the GPIO header, are mechanically secure, and have low latency.
35+
1. Install drivers (see HAT vendor docs).
36+
2. Check device index: `aplay -l` and `arecord -l`.
37+
3. Configure Env Vars:
38+
```bash
39+
export AUDIO_SOURCE="alsasrc device=hw:0,0"
40+
export AUDIO_SINK="alsasink device=hw:0,0"
41+
```
42+
43+
#### Option B: USB Audio Adapter
44+
Easiest to test, but **vibration risk** in car. Use hot glue or strain relief!
45+
1. Plug in USB Sound Card.
46+
2. Check device index: `aplay -l` (usually card 1).
47+
3. Configure Env Vars:
48+
```bash
49+
export AUDIO_SOURCE="alsasrc device=hw:1,0"
50+
export AUDIO_SINK="alsasink device=hw:1,0"
51+
```
52+
2753
## Deployment
2854
1. Set the `REMOTE_IP` in `docker-compose.yml` to the IP of the other side.
2955
2. Run:
@@ -37,3 +63,6 @@ docker-compose up -d
3763
- `UDP_PORT`: Default `5005`.
3864
- `TCP_PORT`: Default `5006`.
3965
- `REDIS_URL`: Default `redis://localhost:6379/0`.
66+
- `ENABLE_AUDIO`: `true` (default).
67+
- `AUDIO_SOURCE`: GStreamer source element (default `autoaudiosrc`).
68+
- `AUDIO_SINK`: GStreamer sink element (default `autoaudiosink`).
8.99 MB
Binary file not shown.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import os
2+
import time
3+
import multiprocessing
4+
import logging
5+
from src.data import TelemetryNode
6+
from src.video import run_video
7+
from src.audio import run_audio
8+
import asyncio
9+
10+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
11+
logger = logging.getLogger("Main")
12+
13+
def start_telemetry():
14+
# Set High Priority (Critical for CAN)
15+
try:
16+
os.nice(-10)
17+
logger.info("Telemetry process priority set to -10 (High)")
18+
except PermissionError:
19+
logger.warning("Could not set Telemetry priority (needs root/CAP_SYS_NICE)")
20+
21+
# Telemetry is asyncio based
22+
node = TelemetryNode()
23+
asyncio.run(node.start())
24+
25+
def start_video(role, remote_ip):
26+
# Set Lower Priority (Video can drop frames if needed)
27+
try:
28+
os.nice(5)
29+
except PermissionError:
30+
pass
31+
run_video(role, remote_ip)
32+
33+
def start_audio(role, remote_ip):
34+
# Set Medium Priority (Audio needs low latency)
35+
try:
36+
os.nice(-5)
37+
logger.info("Audio process priority set to -5 (Medium)")
38+
except PermissionError:
39+
logger.warning("Could not set Audio priority")
40+
run_audio(role, remote_ip)
41+
42+
if __name__ == "__main__":
43+
logger.info("Universal Telemetry Software Starting...")
44+
45+
# Configuration
46+
role = os.getenv("ROLE", "auto")
47+
remote_ip = os.getenv("REMOTE_IP", "127.0.0.1")
48+
enable_video = os.getenv("ENABLE_VIDEO", "true").lower() == "true"
49+
enable_audio = os.getenv("ENABLE_AUDIO", "true").lower() == "true"
50+
51+
# Note: Telemetry needs to run first or alone to detect role if "auto"
52+
# But for simplicity, if "auto", we might need logic in main to detect first?
53+
# TelemetryNode detects it internally. Ideally, we detect once here.
54+
55+
if role == "auto":
56+
# Quick detection logic (duplicated for now from telemetry.py for init)
57+
try:
58+
import can
59+
with can.interface.Bus(channel='can0', bustype='socketcan') as bus:
60+
role = "car"
61+
except:
62+
role = "base"
63+
logger.info(f"Auto-detected Role: {role}")
64+
65+
processes = []
66+
67+
# 1. Telemetry (Critical)
68+
p_telemetry = multiprocessing.Process(target=start_telemetry, name="Telemetry")
69+
p_telemetry.start()
70+
processes.append(p_telemetry)
71+
72+
# 2. Video (Optional)
73+
if enable_video:
74+
p_video = multiprocessing.Process(target=start_video, args=(role, remote_ip), name="Video")
75+
p_video.start()
76+
processes.append(p_video)
77+
78+
# 3. Audio (Optional)
79+
if enable_audio:
80+
p_audio = multiprocessing.Process(target=start_audio, args=(role, remote_ip), name="Audio")
81+
p_audio.start()
82+
processes.append(p_audio)
83+
84+
try:
85+
while True:
86+
time.sleep(1)
87+
# Monitor children
88+
for p in processes:
89+
if not p.is_alive():
90+
logger.error(f"Process {p.name} died!")
91+
# Optional: Restart logic
92+
except KeyboardInterrupt:
93+
logger.info("Shutting down...")
94+
for p in processes:
95+
p.terminate()
96+
p.join()
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
python-can
22
redis
3-
websockets
3+
websockets
4+
PyGObject
5+
Flask
6+
Flask-SocketIO
7+
eventlet
8+
opencv-python-headless

0 commit comments

Comments
 (0)