Skip to content

Commit b3da757

Browse files
authored
Merge pull request #5 from javydekoning/develop
feat: minor improvements and readme update
2 parents b4d6d36 + 77a65bf commit b3da757

File tree

7 files changed

+87
-21
lines changed

7 files changed

+87
-21
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,6 @@ dmypy.json
130130

131131
test.py
132132

133-
.github/linters/pyproject.toml
133+
.github/linters/pyproject.toml
134+
135+
uv.lock

README.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ uv pip install -e .
1616

1717
## Usage
1818

19+
Easy mode:
20+
21+
```sh
22+
uv run main.py --ip x.x.x.x --auth-key "ABCD1234WXYZ"
23+
```
24+
25+
Roll your own;
26+
1927
```python
2028
import asyncio
2129
from xcomfort import Bridge
@@ -46,7 +54,6 @@ async def main():
4654
await runTask
4755

4856
asyncio.run(main())
49-
5057
```
5158

5259
## Development
@@ -56,24 +63,17 @@ asyncio.run(main())
5663
You can run the tests using uvx without any local dependency management:
5764

5865
```bash
59-
# Run tests with uvx (no local installation needed)
60-
uvx --with aiohttp --with rx --with pycryptodome --with pytest-asyncio pytest tests/ -v
61-
62-
# Or use the convenience script
6366
./run_tests.sh
64-
65-
# Or install dev dependencies and run tests locally
66-
uv pip install -e ".[dev]"
67-
pytest
6867
```
6968

69+
To run Github workflows locally
70+
71+
Install [act](https://nektosact.com/installation/index.html) and run flows locally using `act`.
72+
7073
### Dependencies
7174

7275
The project includes the following dependencies:
76+
7377
- `aiohttp` - For async HTTP client functionality
7478
- `rx` - For reactive programming
7579
- `pycryptodome` - For cryptographic operations
76-
77-
## To run Github workflows locally
78-
79-
Install [act](https://nektosact.com/installation/index.html) and run flows locally using `act`.

docs/example_payloads/roomPayload.json

Whitespace-only changes.

main.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""Example script demonstrating xComfort Bridge usage."""
2+
3+
import argparse
4+
import asyncio
5+
import logging
6+
7+
from xcomfort import Bridge
8+
9+
_LOGGER = logging.getLogger(__name__)
10+
11+
12+
def observe_device(device):
13+
"""Subscribe to device state changes and log them."""
14+
device.state.subscribe(
15+
lambda state: _LOGGER.info(
16+
"Device state [%s] '%s': %s", device.device_id, device.name, state
17+
)
18+
)
19+
20+
21+
async def main(ip: str, auth_key: str):
22+
"""Run the main example demonstrating bridge connection and device observation."""
23+
bridge = Bridge(ip, auth_key)
24+
25+
runTask = asyncio.create_task(bridge.run())
26+
27+
devices = await bridge.get_devices()
28+
29+
for device in devices.values():
30+
observe_device(device)
31+
32+
# Wait 50 seconds. Try flipping the light switch manually while you wait
33+
await asyncio.sleep(50)
34+
35+
# Turn off all the lights.
36+
# for device in devices.values():
37+
# await device.switch(False)
38+
#
39+
# await asyncio.sleep(5)
40+
41+
await bridge.close()
42+
await runTask
43+
44+
if __name__ == "__main__":
45+
# Configure debug logging
46+
logging.basicConfig(
47+
level=logging.DEBUG,
48+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
49+
)
50+
51+
parser = argparse.ArgumentParser(description="Test xComfort Bridge connection")
52+
parser.add_argument("--ip", required=True, help="IP address of the xComfort Bridge")
53+
parser.add_argument("--auth-key", required=True, help="Authentication key for the xComfort Bridge")
54+
55+
args = parser.parse_args()
56+
57+
asyncio.run(main(args.ip, args.auth_key))

run_tests.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
#!/bin/bash
2-
# Script to run tests with uvx without local dependency management
2+
# Script to run tests and linting with uvx without local dependency management
3+
4+
set -e # Exit on first error
5+
6+
curl -L -o ./.github/linters/pyproject.toml https://raw.githubusercontent.com/home-assistant/core/refs/heads/dev/pyproject.toml
7+
8+
uvx ruff check --config .github/linters/.ruff.toml main.py
9+
uvx ruff check --config .github/linters/.ruff.toml xcomfort/
310
uvx --with aiohttp --with rx --with pycryptodome --with pytest-asyncio pytest tests/ -v

xcomfort/devices.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ def __init__(self, bridge, device_id, name, comp_id):
199199

200200
def handle_state(self, payload):
201201
"""Handle RcTouch state updates."""
202+
_LOGGER.debug("RcTouch %s: Received payload: %s", self.name, payload)
202203
temperature = None
203204
humidity = None
204205
if "info" in payload:
@@ -350,12 +351,8 @@ def __init__(self, bridge, device_id, name, comp_id, payload):
350351
self.is_on = bool(payload["curstate"])
351352

352353
# Subscribe to component state updates if this is a multisensor
353-
comp = bridge._comps.get(comp_id) # noqa: SLF001
354-
if comp is not None and comp.comp_type in (
355-
ComponentTypes.PUSH_BUTTON_MULTI_SENSOR_1_CHANNEL,
356-
ComponentTypes.PUSH_BUTTON_MULTI_SENSOR_2_CHANNEL,
357-
ComponentTypes.PUSH_BUTTON_MULTI_SENSOR_4_CHANNEL,
358-
):
354+
if self.has_sensors:
355+
comp = self.bridge._comps.get(self.comp_id) # noqa: SLF001
359356
comp.state.subscribe(lambda _: self._on_component_update())
360357
# Find and subscribe to companion sensor device
361358
self._find_and_subscribe_sensor_device()

xcomfort/room.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def __init__(self, bridge, room_id, name: str):
7878

7979
def handle_state(self, payload):
8080
"""Handle room state updates."""
81+
_LOGGER.debug("Room %s: Received payload: %s", self.name, payload)
8182
old_state = self.state.value
8283

8384
if old_state is not None:
@@ -89,6 +90,7 @@ def handle_state(self, payload):
8990
humidity = payload.get("humidity", None)
9091
power = payload.get("power", 0.0)
9192

93+
mode = None
9294
if "currentMode" in payload: # When handling from _SET_ALL_DATA
9395
mode = RctMode(payload.get("currentMode", None))
9496
if "mode" in payload: # When handling from _SET_STATE_INFO
@@ -101,6 +103,7 @@ def handle_state(self, payload):
101103
self.modesetpoints[RctMode(mode_data["mode"])] = float(mode_data["value"])
102104
_LOGGER.debug("Room %s: Loaded mode setpoints: %s", self.name, self.modesetpoints)
103105

106+
currentstate = None
104107
if "state" in payload:
105108
currentstate = RctState(payload.get("state", None))
106109

0 commit comments

Comments
 (0)