Skip to content

Commit f531288

Browse files
kieranmanningclaude
andcommitted
docs: update README and CLAUDE.md for SharedCameraSource and CI observability
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 921ffe1 commit f531288

2 files changed

Lines changed: 63 additions & 5 deletions

File tree

CLAUDE.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ ClientPython/
5252
- Signaling WebSocket is **never** closed by WebRTC state changes — only by unrecoverable signaling errors
5353

5454
### Video Capture (`intercomclient/camera_video_stream_track.py`)
55-
- `CameraVideoStreamTrack` extends `aiortc.VideoStreamTrack`
56-
- Uses `cv2.VideoCapture` to capture frames from camera source (default device `0`, configurable via `VIDEO_SOURCE`)
57-
- Converts frames to `av.VideoFrame` (BGR24 format) with proper PTS/time_base timestamps
58-
- `recv()` loops until a frame is captured (no recursion)
59-
- `stop()` releases `VideoCapture` to avoid device handle leaks when the PC is replaced
55+
- `cv2.VideoCapture` can only be opened **once** per process on this hardware. Multi-viewer support is handled by `SharedCameraSource`:
56+
- Opens the capture device once; runs a `_read_loop` background task that reads frames and fan-outs to per-viewer `asyncio.Queue(maxsize=4)`.
57+
- Subscribers call `subscribe(key) → Queue` / `unsubscribe(key)`. If a queue is full, the oldest frame is dropped (never blocks the read loop).
58+
- Released via `stop()` when all viewers disconnect; recreated on next connection.
59+
- `CameraVideoStreamTrack` extends `aiortc.VideoStreamTrack`; subscribes to a `SharedCameraSource` on init, unsubscribes in `stop()`.
60+
- Converts frames to `av.VideoFrame` (BGR24 format) with proper PTS/time_base timestamps.
61+
- `PiClient._get_or_create_camera_source()` — lazily opens the camera; `_release_camera_if_idle()` — stops and nulls it when `peer_connections` is empty.
6062

6163
### Telemetry (`intercomclient/telemetry.py``TelemetryClient`)
6264
- `send(event, message="", level="INFO")` — synchronous; called via `asyncio.to_thread` from async contexts
@@ -135,3 +137,10 @@ devices:
135137
## `.envrc`
136138

137139
**Never modify `.envrc`.** It contains local-only developer environment config and must always reflect the developer's own setup.
140+
141+
## CI/CD Observability
142+
143+
Both workflows send a structured event to Honeycomb's Events API (`github-actions` dataset) at the end of every job (pass or fail). Fields: `workflow`, `job`, `status`, `duration_ms`, `sha`, `ref`, `actor`, `repository`, `run_id`, `run_number`, `commit_message`. Requires the `HONEYCOMB_API_KEY` GitHub Actions secret. Honeycomb failures are non-fatal.
144+
145+
- `build-docker.yml` — one job (`build-and-push`), one event per run
146+
- `build-deb.yml` — two jobs (`test`, `build`), one event per job per run

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Intercom Python Client
2+
3+
Python device client that authenticates via OAuth2 device flow, streams camera video over WebRTC, and sends telemetry to the Intercom API.
4+
5+
## Tech Stack
6+
7+
- **Python 3.14+**, **uv**
8+
- **aiortc** (WebRTC), **OpenCV** (camera capture), **websockets** (signaling), **requests** (HTTP)
9+
10+
## Development
11+
12+
```bash
13+
# From the repo root (recommended)
14+
docker compose up -d clientpython
15+
16+
# Logs
17+
docker compose logs -f clientpython
18+
19+
# Rebuild after code changes
20+
docker compose up -d --build clientpython
21+
```
22+
23+
Requires `/dev/video0` on the host (mapped into the container). Tokens and OAuth config are shared with the `utility` service via Docker volumes.
24+
25+
## Camera Architecture
26+
27+
A single `SharedCameraSource` opens `cv2.VideoCapture` once and fans frames to per-viewer `asyncio.Queue`s. Each `CameraVideoStreamTrack` subscribes with a unique viewer key. This supports multiple simultaneous viewers without attempting to re-open the device. The camera is released when all viewers disconnect, and re-opened on the next connection.
28+
29+
## CI/CD
30+
31+
Push to `master` triggers two workflows:
32+
33+
**`build-docker.yml`** — builds and pushes `docker.kmanning.ie:5000/intercom-python-client:{latest,sha,version}` (ARM runner)
34+
35+
**`build-deb.yml`** — runs pytest, then builds a native ARM64 binary with Nuitka and packages it as a `.deb` for Raspberry Pi installation. Uploads the artifact with 30-day retention.
36+
37+
Both workflows send a structured event to Honeycomb (`github-actions` dataset) at job completion — requires `HONEYCOMB_API_KEY` GitHub secret.
38+
39+
## Installation on Raspberry Pi (from .deb)
40+
41+
```bash
42+
sudo dpkg -i intercom-client_<version>_arm64.deb
43+
44+
# Configure
45+
sudo nano /etc/intercom-client/env
46+
47+
# Enable and start
48+
sudo systemctl enable --now intercom-client
49+
```

0 commit comments

Comments
 (0)