Skip to content

Commit 38d8eb8

Browse files
committed
Add non-trivial examples: websocket monitor, history stats, template bulk action
1 parent 0ef0de1 commit 38d8eb8

3 files changed

Lines changed: 153 additions & 0 deletions

File tree

examples/history_stats.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Fetch 24h history for numeric sensors and print min/max/avg per entity.
2+
3+
Environment variables:
4+
HOMEASSISTANT_API_ENDPOINT e.g. http://localhost:8123/api
5+
HOMEASSISTANT_API_TOKEN Long-lived access token
6+
"""
7+
8+
import os
9+
from datetime import datetime
10+
from datetime import timedelta
11+
from datetime import timezone
12+
13+
from homeassistant_api import Client
14+
15+
url = os.environ["HOMEASSISTANT_API_ENDPOINT"]
16+
token = os.environ["HOMEASSISTANT_API_TOKEN"]
17+
18+
SENSOR_IDS = [
19+
"sensor.living_room_temperature",
20+
"sensor.outdoor_temperature",
21+
"sensor.energy_consumption",
22+
]
23+
24+
25+
def _to_float(value: str) -> float | None:
26+
try:
27+
return float(value)
28+
except (ValueError, TypeError):
29+
return None
30+
31+
32+
def main() -> None:
33+
now = datetime.now(tz=timezone.utc)
34+
yesterday = now - timedelta(hours=24)
35+
36+
with Client(url, token) as client:
37+
entities = [
38+
entity
39+
for sensor_id in SENSOR_IDS
40+
if (entity := client.get_entity(entity_id=sensor_id)) is not None
41+
]
42+
43+
for history in client.get_entity_histories(
44+
entities=tuple(entities),
45+
start_timestamp=yesterday,
46+
end_timestamp=now,
47+
):
48+
values = [
49+
v for s in history.states if (v := _to_float(s.state)) is not None
50+
]
51+
if not values:
52+
print(f"{history.entity_id}: no numeric data in last 24h") # noqa: T201
53+
continue
54+
print( # noqa: T201
55+
f"{history.entity_id}: "
56+
f"min={min(values):.2f} "
57+
f"max={max(values):.2f} "
58+
f"avg={sum(values) / len(values):.2f} "
59+
f"({len(values)} samples)",
60+
)
61+
62+
63+
if __name__ == "__main__":
64+
main()

examples/template_bulk_action.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Use a Jinja2 template to find all lights that are on, then turn them off.
2+
3+
Demonstrates combining get_rendered_template with trigger_service to perform
4+
bulk operations based on server-side state queries.
5+
6+
Environment variables:
7+
HOMEASSISTANT_API_ENDPOINT e.g. http://localhost:8123/api
8+
HOMEASSISTANT_API_TOKEN Long-lived access token
9+
"""
10+
11+
import os
12+
13+
from homeassistant_api import Client
14+
15+
url = os.environ["HOMEASSISTANT_API_ENDPOINT"]
16+
token = os.environ["HOMEASSISTANT_API_TOKEN"]
17+
18+
FIND_ON_LIGHTS = """\
19+
{{ states.light
20+
| selectattr('state', 'eq', 'on')
21+
| map(attribute='entity_id')
22+
| list
23+
| join(',') }}"""
24+
25+
26+
def main() -> None:
27+
with Client(url, token) as client:
28+
rendered = client.get_rendered_template(FIND_ON_LIGHTS)
29+
entity_ids = [e.strip() for e in rendered.split(",") if e.strip()]
30+
31+
if not entity_ids:
32+
print("No lights are currently on.") # noqa: T201
33+
return
34+
35+
print(f"Turning off {len(entity_ids)} light(s)...") # noqa: T201
36+
for entity_id in entity_ids:
37+
client.trigger_service("light", "turn_off", entity_id=entity_id)
38+
print(f" off: {entity_id}") # noqa: T201
39+
40+
41+
if __name__ == "__main__":
42+
main()
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Stream live state_changed events for entities in a given domain.
2+
3+
Usage:
4+
python websocket_state_monitor.py [domain]
5+
6+
domain defaults to "light". Press Ctrl+C to stop.
7+
8+
Environment variables:
9+
HOMEASSISTANT_WS_ENDPOINT e.g. ws://localhost:8123/api/websocket
10+
HOMEASSISTANT_API_TOKEN Long-lived access token
11+
"""
12+
13+
import os
14+
import sys
15+
16+
from homeassistant_api import WebsocketClient
17+
from homeassistant_api.models.websocket import FiredEvent
18+
19+
url = os.environ["HOMEASSISTANT_WS_ENDPOINT"]
20+
token = os.environ["HOMEASSISTANT_API_TOKEN"]
21+
22+
23+
def monitor_domain(domain: str) -> None:
24+
with WebsocketClient(url, token) as ws:
25+
print(f"Monitoring '{domain}' state changes. Press Ctrl+C to stop.") # noqa: T201
26+
with ws.listen_events("state_changed") as events:
27+
for event in events:
28+
if not isinstance(event, FiredEvent):
29+
continue
30+
entity_id: str = event.data.get("entity_id", "")
31+
if not entity_id.startswith(f"{domain}."):
32+
continue
33+
old_state = (event.data.get("old_state") or {}).get(
34+
"state",
35+
"unavailable",
36+
)
37+
new_state = (event.data.get("new_state") or {}).get(
38+
"state",
39+
"unavailable",
40+
)
41+
timestamp = event.time_fired.strftime("%H:%M:%S")
42+
print(f"[{timestamp}] {entity_id}: {old_state}{new_state}") # noqa: T201
43+
44+
45+
if __name__ == "__main__":
46+
domain = sys.argv[1] if len(sys.argv) > 1 else "light"
47+
monitor_domain(domain)

0 commit comments

Comments
 (0)