Angel Camera

Implemented 2026-11-02 as a private camera branch of the SOL Radio Emergency Monitor. Source camera: http://10.0.1.123:8080 (IP Webcam, 1280x720 at…

Executive Summary

Implemented 2026-11-02 as a private camera branch of the SOL Radio Emergency Monitor. Source camera: http://10.0.1.123:8080 (IP Webcam, 1280x720 at initial verification). The camera and workstation use 10.0.1.0/24.

Mother's Angel Cam

Implemented 2026-11-02 as a private camera branch of the SOL Radio Emergency
Monitor. Source camera: http://10.0.1.123:8080 (IP Webcam, 1280x720 at initial
verification). The camera and workstation use 10.0.1.0/24.

Open locally: http://10.0.1.89:8888/api/radio/emergency/angel.
The radio emergency web page and desktop control panel link to this view.
Direct workstation access: http://127.0.0.1:8902/emergency/angel.

Operation

sol-radio-angel-camera.service is enabled and runs independently of the SDR.
It uses the existing sol_vision_tracking.MediaPipePersonDetector and
sol_vision_named_objects.NamedObjects libraries on CPU, targets up to two
camera samples per second, and writes a ten-second history sample. Observed
pixel motion and body-region stillness use monotonic time. Camera gaps break
duration continuity. Durations are observations of image change, never vital
signs or proof of consciousness.

The operator confirmed that Mom was the only visible person in the initial
scene. A local appearance template records that context. It is not a trained
biometric identity model and may miss changed clothing, angle or pose. A
visible track can separately be labeled Mom; this attribution expires with
missing or ambiguous track evidence and on worker restart.

Lee's TV was identified by the operator at the right of the initial scene.
The initial person ROI and pixel-motion area exclude that part of the image.
These are placement-specific masks, not automatic TV recognition. Large scene
changes are logged and reset fall history. Fall analysis stays suspended after
a substantial change until the operator selects the new monitoring area.
Video, audio, motion samples and local scene questions continue. Use the
placement controls to drag an area including Mom and visible floor while
excluding screens/reflections. Separately select Mom's visible appearance to
refresh her reference. No risky physical fall demonstration is required.

Potential-event rules and evidence

Potential falls require repeated upright coarse-body observations followed by
a rapid downward transition and sustained body-axis tilt in the same track.
Lying or sitting at startup is not a fall. The rules are heuristic and have
only synthetic temporal validation; they are not a validated medical fall
detector. The temporary view has occlusion and incomplete floor coverage.
The installed model supplies coarse body centers, not a full 33-point pose:
OpenCV person detector.

Camera audio is resampled locally to 16 kHz. Unrestricted Vosk recognition
checks complete word boundaries and word confidence for help, help me, I fell,
I've fallen, I have fallen, emergency and call David. Candidates above 0.45
word confidence are logged; below 0.70 they are explicitly low confidence.
This avoids discarding low-confidence requests for help while preserving
uncertainty. A separate bounded job calls the existing radio Whisper base
transcriber on retained original audio. Neither a phrase nor a second
transcription establishes a real emergency, speaker identity or intent.
Television or other playback can produce candidates.

The ledger saves events before inference. Triggered events retain bounded
recent JPEGs and up to twelve seconds of pre-event audio, with SHA-256 hashes.
As of 2026-11-03, separate rolling audiovisual recording is enabled by user request (see below). An in-memory frame
ring and audio ring are bounded; current.jpg is replaced atomically. Event
snapshots are written before/at detection and do not promise post-event video.
Event logs and event evidence are not automatically deleted; the separately authorized rolling recording window rotates as documented below. Below 2 GiB free on the
evidence volume, media capture yields to metadata preservation.

Possible falls and speech candidates appear in a review banner and can be
marked reviewed locally. They do not enter public RF observation/incident
feeds or trigger BlueBubbles, phone calls, Cast, or external messages.

Local questions

Duration questions use deterministic telemetry. Other questions take a fresh
snapshot and call the installed SmolVLM-500M Q8 model at loopback port 18082.
Results include the frame ID, acquisition time, latency, model name, current
deterministic observations and a hypothesis label. Images and results bypass
the shared chat gateway's public history and trace storage. Requests run in a
bounded asynchronous queue so model latency cannot stall the camera or radio.
An interrupted running job is marked interrupted rather than silently retried.

Files and access boundary

- Code: bin/radio_angel.py, bin/radio_angel_worker.py, bin/radio_angel.html.
- Private configuration: ~/.config/sol-radio/angel.json.
- Private state/evidence: /mnt/sol-data/sol-stack/angel-camera/ (mode 0700).
- Ledger: angel.sqlite3, tables events, samples, jobs, state.
- Service: ~/.config/systemd/user/sol-radio-angel-camera.service.
- Backend integration: bin/radio_site_api.py; Caddy integration in
bin/Caddyfile.pkd_share.

All camera page, frame, media, query, calibration and log endpoints are under
/emergency/angel and pass the same access gate. Caddy permits only direct
10.0.1.0/24 or loopback peers using the explicit local Host values. Forwarded
or Cloudflare-marked requests are rejected; the allow header is overwritten
only on the matching local route and stripped from the general radio route.
The backend is loopback-only and also checks the route marker/Host or direct
loopback without forwarding headers. Mutations require same-origin JSON.
Media and local pages are not stored under the public static web root.

This enforces the SOL serving boundary. The phone's own IPv6 listener and
router firewall are separate; Internet reachability of the original phone
service has not been independently established. Its status reported Ivideon
streaming off during setup. No public camera tunnel was created.

Verification and recovery

Run python -m unittest -v test_radio_angel in random/bin using the radio
venv. Existing emergency and spectrum suites must still pass after API edits.
The final validation includes real LAN HTTP 200, public HTTP 403, spoof-header
403, a rendered desktop/mobile image, a real local-model completion, advancing
camera/audio timestamps and advancing all-22-channel radio telemetry.

The speech fixture is synthesized to a file without speaker playback. It is
not a live room wake-word sensitivity test. Vosk recognized "emergency" with
high confidence, "help me" with lower confidence, and misrecognized "call
David" in that fixture; field accuracy is unvalidated. Physical fall detection
is likewise unvalidated. Preserve those distinctions in status reports.

Restart only the camera branch with
systemctl --user restart sol-radio-angel-camera.service. Stop it with
systemctl --user stop sol-radio-angel-camera.service. Radio reception remains
independent. Restarting the radio API itself reacquires the SDR through its
existing autostart drop-in; verify session owner, heartbeat and frame counters.

Pre-change copies of edited existing files are under the private
evidence directory's backup/. Roll back only the Angel changes if unrelated
edits have subsequently been made; do not blindly overwrite newer files.

Generalized Mom object and directional context

sol-radio-angel-object.service reuses the installed SOL detector environment
and OpenVINO CPU runtime. It runs YOLO26n-pose ONNX plus the small
person-reidentification-retail-0288 256-dimensional appearance encoder, at up
to one sample per second with two CPU threads. Its object mom-angel-local
lives in the private Angel namespace; nothing is enrolled into public feeds.
Two operator-context-confirmed setup crops form a reference gallery. A third
frame is held out. Bed, TV, and empty-room crops form background comparisons.
No neural weights were fine-tuned and no automatic self-enrollment occurs.
A candidate requires person score >=0.60, reference cosine >=0.70, background
margin >=0.15, and three consecutive unambiguous observations. These thresholds
are provisional setup choices, not identity probabilities. No other-person
negative examples, different clothing, or nighttime conditions were validated.

The object adds source-qualified pose and appearance metadata. It does not
replace the original coarse temporal fall detector with unvalidated full-pose
fall rules. Missing/stale camera evidence yields unknown. Its service has its
own CPU and memory limits and may be stopped independently.

The operator mapped bottom-left to the bathroom and bottom-right to the kitchen
hallway. A disappearance may record the last observed edge; this is not proof
of travel or arrival. Direction labels expire after large camera/scene changes
until the layout is reconfirmed. The earlier reported departure toward the
bathroom is saved as an operator annotation, not retrospectively inferred.

Camera-reported circular recording status and one-hour chunk size are exposed
as metadata. Phone recordings were not copied or independently verified.
The existing phone recording configuration was preserved.

SQLite query polling no longer takes an idle write lock. Query claiming and
result persistence retry contention; a completed inference is not repeated
because its result could not immediately be committed. A fault-injection test
covers both cases. The Angel suite now contains 19 passing tests.

Model research and setup evidence are private under the Angel state directory:
setup-embedding-comparison.json, setup-object-verification.json,
setup-pose-comparison.json, setup-yoloe-comparison.json, and
setup-local-models-gpu.json. See setup-review.md for actual comparisons.

Sources:
- https://docs.ultralytics.com/tasks/pose
- https://huggingface.co/onnx-community/yolo26n-pose-ONNX
- https://github.com/openvinotoolkit/open_model_zoo/blob/master/models/intel/person-reidentification-retail-0288/README.md
- https://huggingface.co/google/siglip2-base-patch16-224

SigLIP2 was researched as a richer semantic-image encoder but was not installed:
the measured small re-identification encoder met this setup's bounded appearance
comparison need. Its documented upright-person coverage remains a limitation
for seated and occluded views. Generic embeddings do not establish identity.

The XFCE Radio Emergency tray also provides Open Mother's Angel Cam (LAN only).
The tray was restarted independently of the receiver to load this entry.

Local rolling recordings — 2026-11-03

At the user's request, sol-radio-angel-recorder.service began recording at
11:24:47 America/Chicago. It independently consumes camera MJPEG and WAV audio,
encodes H.264 640x360 at 10 fps plus mono AAC 16 kHz/64 kbps, and writes MKV
segments every 1,800 seconds. Both streams are mandatory; network failures
restart the recorder and may produce short segments or coverage gaps.

sol-radio-angel-recordings.service maintains the user-authorized six-hour
rolling window in angel-camera/recordings/rolling. Only closed, regular,
recorder-named segments whose last write is over six hours old are deleted.
The boundary segment is retained, so coverage can overlap the six-hour cutoff
by up to one 30-minute segment. Open files, symlinks, private event evidence,
and meeting archives are excluded. Deletions are logged to retention.jsonl.

The current meeting is protected separately in
recordings/archive/meeting-2026-11-03. A local copy of the phone's active
10:40 recording was remuxed into a playable 45m15s snapshot containing H.264
and Opus audio. Its SHA-256 and partial-source status are recorded. The original
active-source bytes remain preserved in the .part file. The maintenance worker
fetches a complete source copy after that phone segment closes and verifies
both streams. No phone recording was stopped or removed.

The user supplied a business card as meeting reference: Traci Huff, APRN,
Nurse Practitioner, Segue Health. The photo and literal transcription are
stored privately with provenance; identity/participation is not independently
inferred from video. While meeting.json has archive_active=true, every new local
segment is hard-linked into the archive before rolling cleanup. Set it false
and record end_time when the user confirms meeting end; this affects future
archive enrollment only and does not delete existing files. The segment
containing meeting end remains archived in full. No archive expires automatically.

Status appears on the existing private Angel dashboard. Recorder and archive
maintenance are independent of camera inference and the RF receiver. Pausing
inference does not stop recording. Stop recording explicitly with
systemctl --user stop sol-radio-angel-recorder.service if requested.

Private meeting and timeline exports (2026-11-03)

The Angel page now includes Start meeting, End meeting, Select last meeting,
a unified retained-source timeline, precise local-time in/out inputs, an optional
ensemble transcription checkbox, free-text title/category/notes, and downloads.
Suggested categories include confirmed fall and injury; these remain operator
annotations and do not promote detector candidates into verified events.

radio_angel_exports.py implements LAN-guarded /recordings, /meetings/start,
/meetings/end, /exports, retry and byte-range download routes beneath the Angel
prefix. radio_angel_export_worker.py and sol-radio-angel-exports.service process
a bounded queue independently of recording and radio. Jobs pin source segments
before queueing; live sources are copied, closed sources hard-linked. Exported
intervals select local sources over overlapping phone footage; missing intervals
become explicitly labeled black/silent gaps. Original sources are preserved.

Start meeting pins subsequent recording segments into a separate archive.
End meeting records its cutoff and leaves continuous rolling recording running.
Select last meeting fills the selection; Compile selected interval queues the
video and optional captions. Set any other in/out points for retroactive exports.
Archived sources and exports do not expire with the six-hour rolling window.
Timestamps derived from filenames are approximate, particularly across restarts.

Transcription uses separate normalized analysis audio (80 Hz high-pass and
loudness normalization), local Whisper base.en and tiny.en, and independent Vosk.
The exported video retains its original audio level. Outputs are MP4 with optional
English subtitles, SRT, VTT, TXT, Markdown, timed JSON, disagreement review JSON,
and source provenance. Machine captions, especially medical terms, need checking
against the audio. No cloud transcription or public semantic indexing is used.
Failed/interrupted jobs remain visible and can be retried; source pins survive.

The initial meeting export uses the earliest retained phone footage through
2026-11-03 12:20:09 America/Chicago. Its beginning is not an independently verified
meeting start. Phone/local alignment used matching visual frames, approximately
one-second precision. The original phone recording and contact reference remain
in recordings/archive/meeting-2026-11-03.

Initial completed export: export-27d9ffdfd60f42408282dfa30b888f6f,
5991.4 seconds, H.264/AAC with an English mov_text subtitle track.
545 timed cues; 321 flagged for review; 72 unclear/repetition markers.
Whisper base.en and tiny.en ran on CUDA; Vosk ran independently on CPU.
The early unnormalized recognizer outputs remain preserved for diagnosis.
A full-byte HTTP download of all nine published private outputs matched SHA-256;
Range returned 206, HEAD matched file length, public video access returned 403.
Mobile verification had no JavaScript errors or horizontal overflow. The latest
suite passed 117 tests; a separate 60-second export validated the complete
corrected worker pipeline. Validation evidence is under recordings/validation
and recordings/download-verification.json on the data volume.

Live audio and separate feedback (2026-11-03)

The private Angel page shows timestamped local Vosk final segments and speculative
partial text. Partials never trigger alerts; TV and other speakers are not separated.
New phrase, possible fall, and prolonged-stillness candidates enter the existing
radio incident acknowledgement, private push, desktop indicator, and BlueBubbles
self-message chain. Public radio records carry generic labels only; room details
remain private. Acknowledgement cancels the shared reminder schedule. Repeated
same-kind unresolved observations coalesce. Spoken messages remain separate events.

“Tell David” and “Send David a message” accept a bounded, confidence-gated message
for the fixed private self recipient. Speaker identity and transcription are
unverified. Recognition is experimental: a local synthetic voice was transcribed
as “til David” and correctly did not dispatch. No relaxed homophone trigger was added.

sol-radio-angel-ambient.service consumes new feedback events. Visual cards target
media_player.kitchen_nest (the existing muted orb); chimes and local speech target
media_player.lee_s_bedroom_speaker. It preserves device volume/mute settings,
uses the existing broadcast lease, and keeps orb-loop running for clock recovery.
Audio and display failures are recorded independently. Delivery claims prevent
replay after restart; a crash during dispatch can leave an uncertain claimed event.
Admin mode remains disabled (ambient_voice_enabled=false).

The separate-device test reported matching media playing on both receivers and
confirmed the orb stayed muted. This verifies Cast state, not physical audibility.
The notification-chain test separately verified BlueBubbles delivery, a desktop
alert, queued private push, and acknowledgement from the mobile Angel page.
132 tests passed. Visual fall detection remains gated by placement review after
the camera move; reselect the room/floor region before relying on fall candidates.