Async Python client for Ben Software SecuritySpy (v5/v6)
Project description
aiosecspy
Async Python client for the SecuritySpy web API (v5 and v6).
Supports ++systemInfo, the long-lived ++eventStream, arm/disarm, schedules, PTZ, snapshots, and recording download.
Used by the hass-securityspy Home Assistant integration.
Install
pip install aiosecspy
Requires Python 3.12+ and aiohttp.
Quick start
import asyncio
from aiosecspy import SecSpyClient
async def main() -> None:
async with SecSpyClient("192.168.1.10", 8000, "user", "pass") as client:
info = await client.refresh()
print(info.name, info.version, len(client.cameras))
for camera in client.cameras.values():
print(camera.number, camera.name, camera.armed_motion)
jpeg = await client.get_image(3, width=1280)
print(len(jpeg), "bytes of JPEG")
asyncio.run(main())
async with creates and owns an aiohttp.ClientSession. Pass session= if you
already have one (Home Assistant does); the client will then leave it open when
it closes.
Events
++eventStream is a long-lived HTTP response of CR-delimited lines. EventStream
reads it in a background task, reconnects with exponential backoff and jitter,
and re-reads ++systemInfo when the server reports a configuration change.
from aiosecspy import Event, EventType, SecSpyClient
def on_event(event: Event) -> None: # sync or async callbacks both work
if event.event_type is EventType.MOTION:
print(event.when, "motion on camera", event.camera_number)
async def watch() -> None:
async with SecSpyClient("192.168.1.10", 8000, "user", "pass") as client:
unsubscribe = client.events.add_listener(on_event)
client.events.start()
try:
await asyncio.sleep(3600)
finally:
unsubscribe()
await client.events.stop()
Event.when is timezone-aware, using the server's UTC offset from ++systemInfo.
Alongside the wire event types, the stream emits a few of its own so you can drive UI state without polling:
| Event type | Meaning |
|---|---|
CONNECTED |
The stream attached successfully. |
DISCONNECTED |
The stream dropped; a reconnect is scheduled. |
AUTHFAIL |
Credentials were rejected. The watcher stops instead of retrying, so this is the signal to start a reauth flow. |
REFRESH / REFRESHFAIL |
Result of the automatic ++systemInfo reload after CONFIGCHANGE. |
A listener that raises is logged and skipped; it never takes down the stream.
API surface
| Area | Methods |
|---|---|
| Lifecycle | open(), close(), refresh(), camera(number), cameras, info |
| Arming | toggle_motion(), toggle_actions(), toggle_continuous(), trigger_motion() |
| Schedules | set_schedule(), set_schedule_override(), set_schedule_preset() |
| PTZ | ptz_left/right/up/down/zoom/home/stop(), ptz_preset(1-8), ptz_command() |
| Video | get_image(), image_url(), mjpeg_url(), hls_url(), rtsp_url() |
| Recordings | list_motion_files(), download_file(), download_latest_motion_recording() |
refresh() replaces the Camera objects but carries over the runtime state the
event stream maintains (motion, classification scores, trigger reasons), so a
refresh does not blank out live state.
Errors
Everything derives from SecSpyError:
| Exception | Raised when |
|---|---|
AuthenticationError |
HTTP 401/403 — bad username or password. |
UnsupportedError |
The server does not implement the endpoint (HTTP 404 on a command). |
RequestError |
Transport failure, timeout, or an HTTP/body error. |
UntrustedHostError |
A server-supplied link pointed off the configured server. |
ResponseTooLargeError |
The response exceeded the size ceiling. |
InvalidResponseError |
The body could not be parsed or carried a DTD. |
The last three are subclasses of RequestError, so catching RequestError is
enough for a simple "cannot talk to the server" path.
Version differences
++ssControlContinuous is missing from many builds. toggle_continuous() falls
back to the schedule API when the server answers 404, matching the
"Armed 24/7" / "Disarmed 24/7" schedule by name (v5 calls the latter
"Unarmed 24/7"). Real failures are not swallowed.
Trigger reason bit 512 means Animal on v5 and HomeKit on v6; the parser uses the server's major version to decide.
Security
Read SECURITY.md before logging anything this library produces.
The short version: the auth query parameter is equivalent to the password, so
image_url(), mjpeg_url(), hls_url() and rtsp_url() all return secrets.
Development
pip install -e ".[dev]"
pytest -q --cov
ruff check . && ruff format --check .
mypy aiosecspy
Releasing to PyPI
Publishing uses Trusted Publishing (no API token).
- Bump
__version__inaiosecspy/__init__.py; the packaging metadata reads it from there. - Commit, merge to
main, and create a GitHub Release / tag (for examplev0.1.0). publish.ymlchecks the tag against the package version, builds the sdist/wheel, and uploads to PyPI with attestations via thepypiGitHub Environment.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aiosecspy-0.1.0.tar.gz.
File metadata
- Download URL: aiosecspy-0.1.0.tar.gz
- Upload date:
- Size: 33.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9006b87af5d2c4d6950f786009a7558c3f57dac5ad18948732a39d4f7b60859
|
|
| MD5 |
98f9edc8b46b78cff38bb5390e285149
|
|
| BLAKE2b-256 |
3565e249825cedae1e986b2032846e6604d884e45c28136cc778db5a116c0088
|
Provenance
The following attestation bundles were made for aiosecspy-0.1.0.tar.gz:
Publisher:
publish.yml on TwitchCaptain/aiosecspy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aiosecspy-0.1.0.tar.gz -
Subject digest:
e9006b87af5d2c4d6950f786009a7558c3f57dac5ad18948732a39d4f7b60859 - Sigstore transparency entry: 2335589831
- Sigstore integration time:
-
Permalink:
TwitchCaptain/aiosecspy@51c0be3b3db24ec061dd61a0b16e35d33ff12fd6 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/TwitchCaptain
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@51c0be3b3db24ec061dd61a0b16e35d33ff12fd6 -
Trigger Event:
release
-
Statement type:
File details
Details for the file aiosecspy-0.1.0-py3-none-any.whl.
File metadata
- Download URL: aiosecspy-0.1.0-py3-none-any.whl
- Upload date:
- Size: 24.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e2ff2cecf8446a8bbe6c78e4e8165bac15a86dca6f6deaa07d4b02fa8293d982
|
|
| MD5 |
d90851c4a313178722d60b85b888ebec
|
|
| BLAKE2b-256 |
da4a028fd749129ea8762c1827d6f69577fe3ff3281c0759adc77d53e26756d2
|
Provenance
The following attestation bundles were made for aiosecspy-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on TwitchCaptain/aiosecspy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aiosecspy-0.1.0-py3-none-any.whl -
Subject digest:
e2ff2cecf8446a8bbe6c78e4e8165bac15a86dca6f6deaa07d4b02fa8293d982 - Sigstore transparency entry: 2335589836
- Sigstore integration time:
-
Permalink:
TwitchCaptain/aiosecspy@51c0be3b3db24ec061dd61a0b16e35d33ff12fd6 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/TwitchCaptain
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@51c0be3b3db24ec061dd61a0b16e35d33ff12fd6 -
Trigger Event:
release
-
Statement type: