Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: CI

on:
push:
branches:
- backport-hacs-client-foundation
- package/wheemer-python-xsense
- package/wheemer-python-xsense-integration-parity
pull_request:

jobs:
test:
name: Python ${{ matrix.python-version }}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
python-version:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"

steps:
- uses: actions/checkout@v7

- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install test tooling
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install pytest build twine pip-audit
python -m pip install -e .

- name: Test
run: python -m pytest -q

- name: Compile
run: python -m compileall -q asynctest.py xsense tests

- name: Metadata check
run: python setup.py check --metadata --strict

- name: Build
run: python -m build --sdist --wheel

- name: Check distributions
run: python -m twine check dist/*

- name: Check installed dependencies
run: python -m pip check

- name: Audit installed dependencies
run: python -m pip_audit
80 changes: 52 additions & 28 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
Python-xsense
=============

Python-xsense is a small library to interact with the API of XSense Home
Security. It allows to retrieve the status of various devices and the
basestation.
Python-xsense is an async client library for X-Sense cloud accounts,
AWS IoT shadows, MQTT updates, ADDX/VicoHome camera APIs, camera history,
live-view helpers, and supported device actions/settings.

Example sync usage
------------------
Supported Python versions are 3.10 through 3.14. Home Assistant currently runs
on a newer Python, but the library keeps the lower floor for scripts and other
async Python consumers when the test matrix proves compatibility.

::
This package contains reusable Python client logic only. Home Assistant
entity descriptions, icons, translations, blueprints, platform setup, and
registry cleanup belong in the Home Assistant integration.

>>> from xsense import XSense
>>> from xsense.utils import dump_environment
>>> api = XSense()
>>> api.init()
>>> api.login(username, password)
>>> api.load_all()
>>> for _, h in api.houses.items():
>>> for _, s in h.stations.items():
>>> api.get_state(s)
>>> dump_environment(api)

Example async usage
-------------------
Client usage
------------

::

Expand All @@ -31,18 +23,50 @@ Example async usage
>>> from xsense.utils import dump_environment
>>>
>>> async def run(username: str, password: str):
>>> api = AsyncXSense()
>>> await api.init()
>>> await api.login(username, password)
>>> for _, h in api.houses.items():
>>> for _, s in h.stations.items():
>>> await api.get_state(s)
>>> dump_environment(api)
>>> async with AsyncXSense() as api:
>>> await api.init()
>>> await api.login(username, password)
>>> await api.load_all()
>>> for house in api.houses.values():
>>> for station in house.stations.values():
>>> await api.get_state(station)
>>> dump_environment(api)
>>>
>>> asyncio.run(run(username, password))

Reusable helpers
----------------

The main async client lives in ``xsense.AsyncXSense``. It exposes the current
X-Sense app API request shape, Cognito session refresh, AWS IoT shadow
reads/writes, ADDX/VicoHome camera discovery, camera thumbnail and live-view
helpers, and supported device actions/settings.

Pure parsing helpers live in separate modules:

* ``xsense.event_parser`` parses MQTT, camera history, AI detection, presence,
and self-test event payloads.
* ``xsense.mqtt_helper`` handles X-Sense MQTT connection setup, topics,
subscription helpers, payload parsing, and publish helpers.
* ``xsense.webrtc_signal`` parses ADDX WebRTC tickets and builds/parses the
signal-server SDP and ICE payloads.

Migration surface
-----------------

The library is intended to carry the reusable surface currently embedded under
the Home Assistant integration's ``custom_components/xsense/api`` package:

* authentication, token refresh, app request signing, and AWS credential loading
* X-Sense ``/app`` calls and AWS IoT shadow reads/writes
* house, station, device, camera, and entity mapping/normalization
* MQTT connection helpers, shadow/presence topics, and payload parsing
* ADDX/VicoHome camera discovery, metadata/config merging, thumbnails, history,
AI history, WebRTC tickets, and live-view helpers
* reusable device actions and settings writes for supported device data

Development
-----------

This library is in an early development stage and created primarily to
make an integration for home assistant.
This library is in an early development stage. It is maintained as a
shared upstream client for integrations and other async Python consumers.
31 changes: 16 additions & 15 deletions asynctest.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import asyncio

from xsense.async_xsense import AsyncXSense
from xsense import AsyncXSense
from xsense.utils import dump_environment, get_credentials


async def run(username: str, password: str):
api = AsyncXSense()
await api.init()
await api.login(username, password)
await api.load_all()
async with AsyncXSense() as api:
await api.init()
await api.login(username, password)
await api.load_all()

for _, h in api.houses.items():
await api.get_house_state(h)
for _, s in h.stations.items():
await api.get_station_state(s)
await api.get_state(s)
for h in api.houses.values():
await api.get_house_state(h)
for s in h.stations.values():
await api.get_station_state(s)
await api.get_state(s)

if s.has_alarm:
await api.get_alarm_state(s)
if s.has_alarm:
await api.get_alarm_state(s)

dump_environment(api)
dump_environment(api)


username, password = get_credentials()
asyncio.run(run(username, password))
if __name__ == "__main__":
username, password = get_credentials()
asyncio.run(run(username, password))
10 changes: 4 additions & 6 deletions docs/protocol.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ See below

Clienttype: 1 for IOS, 2 for Android.

App-version currently is "v1.17.2_20240115".
App-version currently is "v1.36.0_20260130".

AppCode looks like an encoded version of the App-version and is currenlty 1172 (v **1** . **17** . **2** _20240115)
AppCode looks like an encoded version of the App-version and is currently 1360 (v **1** . **36** . **0** _20260130)

The bizCode specifies which command is requested.

Expand All @@ -57,10 +57,8 @@ Some requests can be unauthenticated, other must be authenticated. Authention ca

MAC-hash
--------
All requests must include a MAC-hash. This is a md5-hash calculated over the values of all custom field included in the
request combined with the secret key.
The implementation for the hash-calculation is probably incorrect. Keys in a python-dict are unsorted which result
in hashes that are unpredictable.
All requests must include a MAC-hash. This is a md5-hash calculated over the values of all custom fields included in the
request combined with the secret key. Container values are serialized in the compact JSON form used by the Android app.

Commands
========
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools>=40.8.0", "wheel"]
build-backend = "setuptools.build_meta"
31 changes: 22 additions & 9 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from setuptools import setup, find_packages

VERSION = '0.0.17'
DESCRIPTION = 'XSense Python Module'
VERSION = '0.1.0'
DESCRIPTION = 'Async X-Sense cloud, MQTT, and camera client'

with open('README.rst', 'r') as fd:
LONG_DESCRIPTION = fd.read()
Expand All @@ -11,26 +11,39 @@
version=VERSION,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
long_description_content_type='text/x-rst',
url='https://github.com/theosnel/python-xsense',
project_urls={
'Source': 'https://github.com/theosnel/python-xsense',
'Pull requests': 'https://github.com/theosnel/python-xsense/pulls',
},
license='MIT',
author='Theo Snelleman',
author_email='<python@theo.snelleman.net>',
author_email='python@theo.snelleman.net',
packages=find_packages(),
python_requires='>=3.10',
install_requires=[
'requests',
'boto3',
'botocore',
'pycognito',
'paho-mqtt>=2.1.0',
'aiohttp',
'paho-mqtt>=2.1.0,<3',
],
extras_require={'async': ['aiohttp']},
extras_require={
'test': ['pytest'],
'dev': ['build', 'pip-audit', 'pytest', 'twine'],
},

keywords=['python', 'xsense'],
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Education',
'License :: OSI Approved :: MIT License',
'Intended Audience :: Developers',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.14',
'Operating System :: OS Independent',
]
)
)
35 changes: 0 additions & 35 deletions test.py

This file was deleted.

Loading