Skip to main content

Harbor Python

Async Python client for connecting locally to Harbor Sleep Cameras.

harbor-python speaks directly to Harbor devices on your local network over MQTT, using the camera certificate material issued for your setup. It provides typed event parsing, device state tracking, command publishing, and helpers for configuring local WHIP streaming targets.

Installation

pip install harbor-python

Python 3.11 or newer is required.

Quick Start

import asyncio

from harbor import Harbor, HarborCamera, HarborCameraConfig, HeartbeatUpdate


async def main() -> None:
    config = HarborCameraConfig(
        serial="CAMERA_SERIAL",
        ip_address="192.168.1.50",
        cert_path="/path/to/cert.pem",
        key_path="/path/to/key.pem",
    )

    harbor = Harbor()
    camera = HarborCamera(config)

    camera.subscribe_updates(
        lambda state: print(f"{state.serial} values: {state.values}")
    )
    camera.subscribe(
        HeartbeatUpdate,
        lambda event: print(f"temperature: {event.payload.temperature}"),
    )

    harbor.add_device(camera)
    harbor.add_camera_connection(config)

    try:
        await harbor.start()
        await asyncio.Event().wait()
    finally:
        await harbor.stop()


asyncio.run(main())

On Windows, aiomqtt works best with the selector event loop policy:

import asyncio
import sys

if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

Certificate Configuration

HarborCameraConfig accepts certificate material in either form:

HarborCameraConfig(
    serial="CAMERA_SERIAL",
    ip_address="192.168.1.50",
    cert_pem="<certificate PEM contents>",
    key_pem="<private key PEM contents>",
)

or:

HarborCameraConfig(
    serial="CAMERA_SERIAL",
    ip_address="192.168.1.50",
    cert_path="/path/to/cert.pem",
    key_path="/path/to/key.pem",
    cert_dir="/path/to/ca-directory",
)

When both PEM strings and file paths are provided, the in-memory PEM values are used.

Commands

Camera commands can be published directly:

await harbor.publish_camera_command("CAMERA_SERIAL", "some-command", {"value": True})

For request/response commands, use request_camera_command or the settings helper:

settings = await harbor.get_camera_settings("CAMERA_SERIAL")
print(settings.settings)

Camera controls

await harbor.set_camera_on("CAMERA_SERIAL", False)      # privacy: pause the stream
await harbor.set_night_mode("CAMERA_SERIAL", "auto")    # "auto" | "on" | "off"
await harbor.set_video_flip("CAMERA_SERIAL", True)      # rotate the image 180°
await harbor.set_clock_display("CAMERA_SERIAL", False)  # clock overlay on the video
await harbor.set_temperature_scale("CAMERA_SERIAL", "C")  # "F" | "C"
await harbor.update_camera_settings(
    "CAMERA_SERIAL", {"preference_video_ir_brightness": 40}
)

Each control writes one preference and refreshes device state, so camera.state.values reflects the change once the call returns:

camera.state.values[...] Type Setting
camera_on bool preference_stream_paused (inverted)
night_mode_preference "auto" | "on" | "off" preference_video_night_mode
night_mode bool runtime IR state (read-only, see below)
video_flip bool preference_video_flip
clock_display bool preference_video_has_clock_display
temperature_scale "F" | "C" preference_temperature_scale

set_video_flip and set_clock_display take real booleans — 1/0 and "true" raise ValueError rather than being sent as a number or string, which the firmware would reject.

The enum setters validate against the firmware's own option list, exported as NIGHT_MODE_MODES and TEMPERATURE_SCALES. Matching is exact: "f" raises ValueError, because the device compares the string verbatim. State values preserve case for the same reason — what you read back is always something you can write.

Night mode

Night mode is a three-way preference, not a boolean — "auto", "on" or "off" (default "auto"). Passing a bool raises ValueError rather than guessing at a mode. A camera exposes two separate night-mode values:

camera.state.values[...] Type Meaning
night_mode_preference "auto" | "on" | "off" The setting. This is what set_night_mode writes and what reads back.
night_mode bool Whether IR is engaged right now. Read-only and device-driven — under "auto" it flips on its own as light levels change.

Consumers building a UI entity should bind it to night_mode_preference, since night_mode moves independently of any command.

Command errors

A rejected command raises HarborCommandError, which carries the parsed status and the firmware's per-field errors list:

from harbor import HarborCommandError, HarborUnsupportedCommandError

try:
    await harbor.set_night_mode("CAMERA_SERIAL", "on")
except HarborUnsupportedCommandError:
    ...  # firmware has no such command; permanent, so stop offering the feature
except HarborCommandError as err:
    print(err.status, err.errors)  # e.g. "REQUEST_MALFORMED", [{"error_code": "INVALID_VALUE", ...}]

HarborUnsupportedCommandError is a subclass of HarborCommandError, raised only on a RESOURCE_NOT_FOUND status. That means the firmware has no handler for the command at all, so retrying can never succeed.

Firmware compatibility

Commands are verified against real hardware, most recently a camera running os_version 2.8.0 / app_version 2.8.0-rc1+c1b0a32: ping, get-settings, update-settings, pause-stream, unpause-stream, set-night-mode-ir-brightness, update-operating-mode, set-scheduled-reboot and list-viewers all respond OK. See mqtt_home_assistant.md for the full audit and payload shapes.

WHIP Endpoint

Harbor cameras allow custom WHIP endpoints. This tells the camera where to stream and works with tools that support WHIP, including go2rtc and Frigate.

Setting up WHIP Ingestion (go2rtc)

You can self-host go2rtc in many ways; see the go2rtc installation guide. If you use Home Assistant, the easiest option is the go2rtc add-on.

Once go2rtc is running, add a stream keyed by your camera serial number:

api:
  listen: ":1984" # Change this if you use a non-default port.

streams:
  "CAMERA_SERIAL":

Setting up WHIP Ingestion (Frigate)

Frigate runs an instance of go2rtc under the hood. Add the following to your Frigate config:

go2rtc:
  api:
    listen: ":1984"
  streams:
    "CAMERA_SERIAL":

Setting the Endpoint

  1. Open your Harbor app
  2. Go to Live
  3. Open Camera Settings
  4. Scroll down and click on Advanced Settings
  5. Enter the WHIP endpoint, for example: http://192.168.1.10:1984/api/webrtc?dst=CAMERA_SERIAL

Replace CAMERA_SERIAL with your camera serial number and 192.168.1.10 with the IP address of your go2rtc or Frigate server.

Development

uv sync
uv run pytest

License

Licensed under the Apache License 2.0.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

harbor_python-1.5.0.tar.gz (27.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

harbor_python-1.5.0-py3-none-any.whl (32.8 kB view details)

Uploaded Python 3

File details

Details for the file harbor_python-1.5.0.tar.gz.

File metadata

  • Download URL: harbor_python-1.5.0.tar.gz
  • Upload date:
  • Size: 27.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for harbor_python-1.5.0.tar.gz
Algorithm Hash digest
SHA256 e4c68a0e4408fc480ffa1768c808712d08d30140e17b851794434f1ef7547c73
MD5 15c6e7b44867018d81e33ceeb5ab62a7
BLAKE2b-256 48f44d31f252fc3bbfe4992c9e6797a5f88c6bc16b9e074205d71111d62f2836

See more details on using hashes here.

Provenance

The following attestation bundles were made for harbor_python-1.5.0.tar.gz:

Publisher: ci.yml on Harbor-Systems/harbor-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file harbor_python-1.5.0-py3-none-any.whl.

File metadata

  • Download URL: harbor_python-1.5.0-py3-none-any.whl
  • Upload date:
  • Size: 32.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for harbor_python-1.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 01524afa8338d1b14d9c5e916d27f8d123e3ff17ab080db68f49cffe53ba56dd
MD5 6caa141dc73571b5b68973a70d600bf8
BLAKE2b-256 bba1ee77d814efe812c71ea29352c5630dff5a005ced7e5f23c067b0fe23e257

See more details on using hashes here.

Provenance

The following attestation bundles were made for harbor_python-1.5.0-py3-none-any.whl:

Publisher: ci.yml on Harbor-Systems/harbor-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

1.5.0 This release

2 files

1.4.0

2 files

1.3.0

2 files

1.2.1

2 files

1.2.0

2 files

1.1.0

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page