Skip to content

Commit 51ce0d3

Browse files
badbreadclaude
andcommitted
Clarify Stage 3 timing + rename Detection tab to Schedule
- Persistent Deterrence delay label now says "Delay Between Warnings" with hint explaining it's the gap after escalation AND between loops - Stage description explicitly explains the flow: escalation completes → wait delay → check person → generate fresh AI description → repeat - Renamed "Detection" config tab to "Schedule" (clearer purpose) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5b62f67 commit 51ce0d3

File tree

4 files changed

+326
-9
lines changed

4 files changed

+326
-9
lines changed

README.md

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ Frigate NVR MQTT VoxWatch Service MQTT Home Assist
6868

6969
Each stage only fires if the person is still detected (Frigate re-check). AI adapts automatically for nightvision — no color descriptions from IR footage.
7070

71+
**Persistent Deterrence (optional):** When enabled, Stage 3 loops -- each iteration waits a configurable delay, re-checks whether the person is still present, generates a fresh AI description, and escalates the tone. The loop ends when the person leaves or a configurable max iteration count is reached. Configure in the Pipeline tab under Persistent Deterrence.
72+
7173
---
7274

7375
## Response Modes
@@ -122,6 +124,36 @@ All customizable — address, agency, callsign, officer voice, radio intensity,
122124

123125
Fine-grained control: bandpass frequency, compression, noise level, squelch toggle.
124126

127+
---
128+
129+
## Camera Zones
130+
131+
Group cameras by physical area so that one detection triggers one speaker and all cameras in the zone share a single cooldown timer.
132+
133+
Useful when multiple cameras cover the same area (e.g. two angles on the front gate) -- without zones, each camera fires independently and the intruder hears duplicate audio.
134+
135+
```yaml
136+
zones:
137+
front_yard:
138+
cameras: [frontdoor, driveway]
139+
speaker: frontdoor # audio always plays on this camera
140+
cooldown_seconds: 90 # optional per-zone cooldown override
141+
142+
back_yard:
143+
cameras: [backgate, patio]
144+
speaker: backgate
145+
```
146+
147+
| Behavior | Detail |
148+
|----------|--------|
149+
| Shared cooldown | Keyed by zone name ("zone:front_yard"). First camera to fire blocks the rest. |
150+
| Speaker routing | Audio pushed to the zone speaker stream, not the triggering camera stream. |
151+
| Zone cooldown | Overrides global cooldown when set; falls back to global when omitted. |
152+
| Per-camera override | A camera not in any zone still supports audio_output for individual speaker routing. |
153+
154+
Configure zones in the **Camera Zones** tab of the Config editor.
155+
156+
125157
---
126158
127159
## AI Vision Providers
@@ -336,6 +368,10 @@ services:
336368
| **Sunset to Sunrise** | `mode: "sunset_sunrise"` | Solar calculation via `astral` library |
337369
| **Fixed Window** | `mode: "fixed"` | Custom start/end times (handles midnight crossing) |
338370
371+
**Per-camera schedules:** Each camera can override the global active hours with its own schedule. Set a camera's schedule to always, a fixed time window, or sunset-to-sunrise -- independently of every other camera. Cameras with no per-camera schedule fall back to the global setting. Configure per-camera schedules in the **Detection** tab of the Config editor, or in the camera detail panel.
372+
373+
You can also specify a city name (e.g. city: "Seattle") instead of explicit latitude/longitude for sunset/sunrise calculations. VoxWatch uses the astral library's built-in geocoder database for city name resolution.
374+
339375
---
340376
341377
## Legal Considerations
@@ -369,11 +405,11 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for code style and PR guidelines.
369405

370406
## Roadmap
371407

372-
**Recently shipped:** Home Assistant two-way MQTT, TTS announce API, persona customization, Docker optimization (49% size reduction), natural cadence speech, email camera reports
408+
**Recently shipped:** Camera zones (shared cooldown + speaker routing), per-camera schedules, persistent deterrence loop (Stage 3), per-camera audio output override, Home Assistant MQTT, natural cadence speech
373409

374-
**In progress:** Camera zones (group cameras so one detection triggers one speaker)
410+
**In progress:** Dynamic TTS library loading, custom voice models
375411

376-
**Planned:** Dynamic TTS library loading, custom voice models, SMS/Telegram notifications
412+
**Planned:** SMS/Telegram notifications
377413

378414
---
379415

dashboard/frontend/src/components/config/ConfigEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ interface TabDef {
5353
const TABS: TabDef[] = [
5454
{ id: 'response_mode', label: 'Personality', icon: Theater, section: 'response_mode' },
5555
{ id: 'tts', label: 'TTS', icon: Headphones, section: 'response_mode' },
56-
{ id: 'detection', label: 'Detection', icon: Clock, section: 'conditions' },
56+
{ id: 'detection', label: 'Schedule', icon: Clock, section: 'conditions' },
5757
{ id: 'zones', label: 'Camera Zones', icon: MapPin, section: 'conditions' },
5858
{ id: 'pipeline', label: 'Pipeline', icon: Layers, section: 'stages' },
5959
{ id: 'ai', label: 'AI Provider', icon: Brain, section: 'ai' },

dashboard/frontend/src/components/config/StagesConfigForm.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ export function StagesConfigForm({
607607
<StageCard
608608
id="persistent_deterrence"
609609
label="3. Persistent Deterrence"
610-
description="Continues engaging if person stays after escalation. Loops with fresh AI descriptions."
610+
description="If person stays after escalation, keeps warning them with fresh AI descriptions every N seconds."
611611
icon={RefreshCw}
612612
iconColor="text-orange-500"
613613
enabled={persistentDeterrence.enabled}
@@ -617,13 +617,13 @@ export function StagesConfigForm({
617617
>
618618
<div className="space-y-3">
619619
<p className="text-xs text-orange-600 dark:text-orange-400 bg-orange-50 dark:bg-orange-900/20 rounded px-3 py-2">
620-
After escalation, if the person is still present, VoxWatch will keep generating fresh AI descriptions of their actions every loop until they leave or the max is reached.
620+
After the Escalation stage completes, VoxWatch waits the configured delay, then checks if the person is still present. If yes, it generates a fresh AI description of their current actions and pushes another warning. This repeats until the person leaves or the max iteration count is reached.
621621
</p>
622622

623623
<div className="grid grid-cols-2 gap-3">
624624
<Field
625-
label="Loop Delay (seconds)"
626-
hint="Pause between each deterrence iteration."
625+
label="Delay Between Warnings (seconds)"
626+
hint="How long to wait after escalation (and between each loop iteration) before the next warning. The first persistent deterrence warning fires this many seconds after the escalation stage completes."
627627
>
628628
<input
629629
type="number"

0 commit comments

Comments
 (0)