bunch of bugs

This commit is contained in:
2026-08-26 18:17:03 +02:00
parent c5b598b522
commit 1e73476830
7 changed files with 77 additions and 9 deletions
+3 -2
View File
@@ -35,8 +35,9 @@ both actions through `on_event: [_notify, _cloud_logger]`. A single action name
such as `on_event: _notify` remains supported. such as `on_event: _notify` remains supported.
Arrival detection emits `arrived` and `departed` for individual device IDs. It Arrival detection emits `arrived` and `departed` for individual device IDs. It
also emits `empty` with ID `house` after the last present device departs. Initial also emits `empty` with ID `house` after the last present device departs, and
startup state never generates these transition events. `first_arrival` with the arriving device ID when an empty house becomes occupied.
Initial startup state never generates these transition events.
Plugin modules, factories, configuration sections, and event actions are all Plugin modules, factories, configuration sections, and event actions are all
declared under `controller`. The controller contains no plugin-specific imports declared under `controller`. The controller contains no plugin-specific imports
+2
View File
@@ -1,2 +1,4 @@
{"timestamp":"2026-08-23T06:09:35.779477+00:00","remote_addr":"127.0.0.1","event":{"sender":"_arrival_detection","event":"departed","id":"ignace","text":""}} {"timestamp":"2026-08-23T06:09:35.779477+00:00","remote_addr":"127.0.0.1","event":{"sender":"_arrival_detection","event":"departed","id":"ignace","text":""}}
{"timestamp":"2026-08-23T06:10:47.665139+00:00","remote_addr":"127.0.0.1","event":{"sender":"_arrival_detection","event":"arrived","id":"ignace","text":""}} {"timestamp":"2026-08-23T06:10:47.665139+00:00","remote_addr":"127.0.0.1","event":{"sender":"_arrival_detection","event":"arrived","id":"ignace","text":""}}
{"timestamp":"2026-08-26T16:16:16.951086+00:00","remote_addr":"127.0.0.1","event":{"sender":"_arrival_detection","event":"departed","id":"ignace","text":"2026-08-26T16:16:16.949395+00:00 _arrival_detection: departed (ignace)","datetime":"2026-08-26T16:16:16.949395+00:00"}}
{"timestamp":"2026-08-26T16:16:17.474743+00:00","remote_addr":"127.0.0.1","event":{"sender":"_arrival_detection","event":"empty","id":"house","text":"2026-08-26T16:16:17.473318+00:00 _arrival_detection: empty (house)","datetime":"2026-08-26T16:16:17.473318+00:00"}}
+13
View File
@@ -137,6 +137,7 @@ class ArrivalMonitor:
] ]
def _process_states(self, states: dict[str, bool]) -> None: def _process_states(self, states: dict[str, bool]) -> None:
arrivals: list[dict] = []
for device in self.devices: for device in self.devices:
present = states[device["mac"]] present = states[device["mac"]]
LOG.debug( LOG.debug(
@@ -154,6 +155,8 @@ class ArrivalMonitor:
} }
_record_event(self.config, event, device["mac"]) _record_event(self.config, event, device["mac"])
self.on_event(event) self.on_event(event)
if transition == "arrived":
arrivals.append(device)
known_states = [detector.state for detector in self.detectors.values()] known_states = [detector.state for detector in self.detectors.values()]
if all(state is not None for state in known_states): if all(state is not None for state in known_states):
@@ -167,6 +170,16 @@ class ArrivalMonitor:
} }
_record_event(self.config, event) _record_event(self.config, event)
self.on_event(event) self.on_event(event)
elif self._house_occupied is False and occupied:
first = arrivals[0]
event = {
"sender": "_arrival_detection",
"event": "first_arrival",
"id": first["id"],
"text": "",
}
_record_event(self.config, event, first["mac"])
self.on_event(event)
self._house_occupied = occupied self._house_occupied = occupied
def _run(self) -> None: def _run(self) -> None:
+3 -2
View File
@@ -2,7 +2,7 @@
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from pprint import pprint
import requests import requests
@@ -62,7 +62,7 @@ def on_event(config: dict, message: dict) -> None:
timestamp = datetime.now(timezone.utc).isoformat() timestamp = datetime.now(timezone.utc).isoformat()
values = {**message, "datetime": timestamp} values = {**message, "datetime": timestamp}
messages = config.get("messages", {}) messages = config.get("messages", {})
template = messages.get( template = config.get(
message["event"], message["event"],
config.get("message", "{datetime} {sender}: {event} ({id})"), config.get("message", "{datetime} {sender}: {event} ({id})"),
) )
@@ -71,6 +71,7 @@ def on_event(config: dict, message: dict) -> None:
"datetime": timestamp, "datetime": timestamp,
"text": message.get("text") or str(template).format(**values), "text": message.get("text") or str(template).format(**values),
} }
pprint(payload)
send_event( send_event(
payload, payload,
event_url=str(config.get("event_url", "")), event_url=str(config.get("event_url", "")),
+13
View File
@@ -1,3 +1,16 @@
2026-08-22T22:27:23+02:00 ARRIVAL name=Ignace mac=ce:68:53:a8:fc:07 2026-08-22T22:27:23+02:00 ARRIVAL name=Ignace mac=ce:68:53:a8:fc:07
2026-08-23T08:09:35+02:00 DEPARTED id=ignace mac=ce:68:53:a8:fc:07 2026-08-23T08:09:35+02:00 DEPARTED id=ignace mac=ce:68:53:a8:fc:07
2026-08-23T08:10:47+02:00 ARRIVED id=ignace mac=ce:68:53:a8:fc:07 2026-08-23T08:10:47+02:00 ARRIVED id=ignace mac=ce:68:53:a8:fc:07
2026-08-23T15:17:07+02:00 DEPARTED id=ignace mac=ce:68:53:a8:fc:07
2026-08-23T15:23:46+02:00 ARRIVED id=ignace mac=ce:68:53:a8:fc:07
2026-08-26T17:46:51+02:00 DEPARTED id=ignace mac=ce:68:53:a8:fc:07
2026-08-26T17:46:51+02:00 EMPTY id=house
2026-08-26T17:50:34+02:00 DEPARTED id=ignace mac=ce:68:53:a8:fc:07
2026-08-26T17:50:34+02:00 EMPTY id=house
2026-08-26T17:57:07+02:00 DEPARTED id=ignace mac=ce:68:53:a8:fc:07
2026-08-26T17:57:08+02:00 EMPTY id=house
2026-08-26T18:01:41+02:00 DEPARTED id=ignace mac=ce:68:53:a8:fc:07
2026-08-26T18:01:41+02:00 EMPTY id=house
2026-08-26T18:05:14+02:00 ARRIVED id=ignace mac=ce:68:53:a8:fc:07
2026-08-26T18:16:16+02:00 DEPARTED id=ignace mac=ce:68:53:a8:fc:07
2026-08-26T18:16:16+02:00 EMPTY id=house
+6 -4
View File
@@ -30,7 +30,7 @@ arrival_detection:
verify_tls: true verify_tls: true
hosts_oid: lanhosts hosts_oid: lanhosts
# Log every router/API call and its result without logging credentials. # Log every router/API call and its result without logging credentials.
debug: false debug: true
devices: devices:
- id: ignace - id: ignace
@@ -53,15 +53,16 @@ notify:
filter: filter:
# Empty accept lists allow all values. Ignore lists always take precedence. # Empty accept lists allow all values. Ignore lists always take precedence.
accept: accept:
events: [empty] events: [empty, first_arrival]
ids: [] ids: []
ignore: ignore:
events: [arrived, departed] events: [departed]
ids: [] ids: []
# Available placeholders: {sender}, {event}, {id}, {text}. # Available placeholders: {sender}, {event}, {id}, {text}.
message: "{sender}: {event} ({id})" message: "{sender}: {event} ({id})"
messages: messages:
arrived: "{id} arrived home" arrived: "{id} arrived home"
first_arrival: "{id} is the first to arrive home"
departed: "{id} left home" departed: "{id} left home"
empty: "The house is empty" empty: "The house is empty"
# May also be supplied through the NTFY_TOPIC_URL environment variable. # May also be supplied through the NTFY_TOPIC_URL environment variable.
@@ -70,7 +71,8 @@ notify:
cloud_logger: cloud_logger:
enabled: true enabled: true
event_url: http://www.suy.nl/home_logger/event # event_url: http://www.suy.nl/home_logger/event
event_url: http://127.0.0.1:29651/home_logger/event
timeout: 10 timeout: 10
filter: filter:
# Empty accept lists allow all values. Ignore lists always take precedence. # Empty accept lists allow all values. Ignore lists always take precedence.
+37 -1
View File
@@ -78,7 +78,43 @@ class ArrivalDetectorTests(unittest.TestCase):
"event": "arrived", "event": "arrived",
"id": "ignace", "id": "ignace",
"text": "", "text": "",
} },
{
"sender": "_arrival_detection",
"event": "first_arrival",
"id": "ignace",
"text": "",
},
],
)
@patch("lib._arrival_detection._build_router")
def test_first_arrival_fires_only_when_empty_house_becomes_occupied(
self, _build_router
):
events = []
monitor = ArrivalMonitor(
{
"devices": [
{"id": "ignace", "mac": "00:00:00:00:00:01"},
{"id": "janine", "mac": "00:00:00:00:00:02"},
],
"polling": {"absent_after": 1, "present_after": 1},
},
events.append,
)
first = "00:00:00:00:00:01"
second = "00:00:00:00:00:02"
monitor._process_states({first: False, second: False})
monitor._process_states({first: True, second: False})
monitor._process_states({first: True, second: True})
self.assertEqual(
[(event["event"], event["id"]) for event in events],
[
("arrived", "ignace"),
("first_arrival", "ignace"),
("arrived", "janine"),
], ],
) )