Skip to content

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

OBSController(host: str, port: int = 4455, password: str = "")

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

change_scene(scene_name: str) -> bool

Switches the active OBS scene. Returns True on success, False on failure (and sets last_error).

Method: start_stream

start_stream() -> bool

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

stop_stream() -> bool

Stops the active stream. Same benign-phrase handling as start_stream.

Method: get_stream_status

get_stream_status() -> bool | None

Side-effect-free probe used as the OBS comms heartbeat. Issues a GetStreamStatus request and returns:

  • True — OBS is currently streaming
  • False — OBS is not streaming
  • None — could not communicate with OBS (last_error is set)

The dive detector uses this to:

  1. Decide whether to write state:obs_ok = 1 (probe succeeded) or 0 (probe failed). The dashboard's OBS pill reflects this value.
  2. Avoid redundant StartStream/StopStream calls 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

disconnect() -> None

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_PASSWORD environment variable when running the dive detector.