Dive Detector module
File: backend/divedetector.py
The core detection and orchestration module. Each poll cycle it combines sensor data, operator toggles, and an OBS health probe to drive OBS into a definite state and publish observed health to Redis.
Class: DiveDetector
Holds a Redis connection and exposes accessor methods for the sensor, toggle, and state keys belonging to one ship.
Constructor parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
bucket_name |
str |
— | Redis key prefix, e.g. "rcsn" |
redis_host |
str |
"localhost" |
Redis server hostname |
redis_port |
int |
6379 |
Redis server port |
Method: check_in_water
check_in_water(
in_water_key: str = "rov:ctd:inwater",
depth_key: str = "rov:mbari:depth",
depth_threshold: float = 5.0,
) -> bool
Reads the inwater flag and depth from Redis. Returns True when both
hold: inwater == 1 and depth > depth_threshold. Returns False (and
logs a warning) if either key is missing or unparseable.
Method: auto_stream_enabled
Reads {bucket}:config:auto_stream. Returns False if the key is
missing or unparseable — the detector stays safely passive until an
operator explicitly enables it from the dashboard.
Method: force_stream_enabled
Reads {bucket}:config:force_stream. Same default behaviour.
Method: set_streaming_state
Writes {bucket}:state:streaming ("1" / "0") after a successful
control cycle, so the dashboard can render the detector's view of OBS
streaming state.
Method: set_obs_health
Publishes the result of the OBS comms probe for the current poll:
{bucket}:state:obs_ok—"1"/"0"{bucket}:state:obs_last_error— empty string when healthy, otherwise the most recent error (truncated to 240 chars so it stays tooltip-sized on the dashboard).
Watched Redis keys
| Redis key pattern | Direction | Description |
|---|---|---|
{ship}:rov:ctd:inwater |
read | "1" when the CTD sensor detects submersion |
{ship}:rov:mbari:depth |
read | Current ROV depth as a float string, in metres |
{ship}:config:auto_stream |
read | Operator-controlled auto toggle |
{ship}:config:force_stream |
read | Operator-controlled manual override |
{ship}:state:streaming |
write | Last commanded streaming state |
{ship}:state:obs_ok |
write | Whether the OBS comms probe succeeded |
{ship}:state:obs_last_error |
write | Empty when healthy; otherwise the last OBS error |
Main loop
The script's __main__ block runs an infinite polling loop. Each cycle:
dive_status = detector.check_in_water(...)auto = detector.auto_stream_enabled(),force = detector.force_stream_enabled()- Decide
should_streamandmodevia the control table. - Open an
OBSController. - Call
obs.get_stream_status()— confirms comms AND returns OBS's real streaming state. The result drivesset_obs_health(). - Apply scene + start/stop, but only call
StartStream/StopStreamif OBS's actual state differs from the desired state. detector.set_streaming_state(should_stream).obs.disconnect(), sleep.
Control table
force_stream |
auto_stream |
dive_status |
should_stream |
OBS scene |
|---|---|---|---|---|
| ON | any | any | True | MainOnly |
| OFF | ON | yes | True | MainOnly |
| OFF | ON | no | False | FeedOffline |
| OFF | OFF | any | False | FeedOffline |
When both toggles are OFF the detector still actively drives OBS to the stopped state every poll — it does not leave OBS untouched.
CLI reference
usage: divedetector.py [options]
--ship SHIP Redis bucket name / ship identifier (e.g. rcsn)
--redis-host HOST Redis hostname (default: localhost)
--redis-port PORT Redis port (default: 6379)
--depth-threshold DEPTH Minimum depth in metres to count as diving (default: 5.0)
--obs-host HOST OBS WebSocket server hostname
--poll-interval SECS Polling frequency in seconds (default: 10)
--debug 0|1 Enable verbose logging (default: 0)
In addition, the script reads OBS_PORT (default 4455) and
OBS_PASSWORD (default empty) from the environment.