OBS Control module
File: backend/obs_control.py
A thin, defensive wrapper around obs-websocket-py. The dive detector
uses this module to switch scenes, start/stop the stream, and probe OBS
to check that the WebSocket connection is alive. Every public method
returns a bool reflecting success and stashes failure details on the
controller so the detector can surface them to the dashboard.
Class: OBSController
Attempts to connect on construction. If connect fails it does not
raise — self.connected stays False and self.last_connect_error
captures the reason. The first _call will attempt one transparent
reconnect before giving up, so a transient blip at construction doesn't
poison the rest of the poll cycle.
Constructor parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
host |
str |
— | Hostname or IP of the machine running OBS |
port |
int |
4455 |
OBS WebSocket port |
password |
str |
"" |
OBS WebSocket password (empty if auth is disabled) |
Attributes
| Attribute | Type | Description |
|---|---|---|
connected |
bool |
True after a successful WebSocket connect; set to False if a call raises. |
last_error |
str \| None |
Most recent action/probe error message. Cleared after a successful probe. |
last_connect_error |
str \| None |
Reason the most recent connect attempt failed (preserved across calls). |
Method: change_scene
Switches the active OBS scene. Returns True on success, False on
failure (and sets last_error).
Method: start_stream
Starts the configured stream output. Returns True on success. OBS
responses containing benign phrases like "already active" /
"already streaming" are treated as success.
Method: stop_stream
Stops the active stream. Same benign-phrase handling as start_stream.
Method: get_stream_status
Side-effect-free probe used as the OBS comms heartbeat. Issues a
GetStreamStatus request and returns:
True— OBS is currently streamingFalse— OBS is not streamingNone— could not communicate with OBS (last_erroris set)
The dive detector uses this to:
- Decide whether to write
state:obs_ok = 1(probe succeeded) or0(probe failed). The dashboard's OBS pill reflects this value. - Avoid redundant
StartStream/StopStreamcalls when OBS is already in the desired state — OBS v5 returns an error response in that case, which used to surface as a false-positive failure.
On success the method also clears last_error so stale messages don't
linger after recovery.
Method: disconnect
Cleanly closes the OBS WebSocket. Safe to call even if connect failed.
Expected OBS scenes
The dive detector expects these two scenes to exist in each OBS instance:
| Scene name | When used |
|---|---|
MainOnly |
OBS should be streaming the dive feed |
FeedOffline |
OBS should be stopped — fallback "offline" graphic |
Example
from obs_control import OBSController
obs = OBSController(host="rc-teleops.rc.mbari.org", port=4455, password="")
active = obs.get_stream_status()
if active is None:
print(f"OBS unreachable: {obs.last_error}")
elif not active:
obs.change_scene("MainOnly")
obs.start_stream()
obs.disconnect()
Notes
- OBS WebSocket must be enabled in OBS under Tools → WebSocket Server Settings.
- The default WebSocket port is
4455(OBS WebSocket v5 protocol). - If OBS has authentication enabled, pass the password via the
OBS_PASSWORDenvironment variable when running the dive detector.