mirror of
https://github.com/kuhyx/wake-alarm.git
synced 2026-11-10 19:27:02 +01:00
On 2026-09-19 the phone was powered off after the bathroom tag and nothing on the PC noticed: every finished session was published and nothing read it. Now wake-alarm distils the phone's morning into an HMAC-signed ~/.local/state/wake_alarm/morning_session.json (wake-alarm-session.timer, 07:00-11:00 every 2 min) that screen-locker and leetcode-guard read to defer their locks -- provisionally until min(start + 90 min, 09:30) while the session is live, until 11:00 once it completed inside that cutoff, and not at all for a session finished later (the phone switched on at noon completes the legs then; that must un-lock nothing). The phone publishes the session at start as well as at finish (current_session, cleared with the finish stamp), so the PC can tell "started, not finished" from "never started". A required 1-5 energy tap joins the weigh-in and ships as latest_energy with a PC reader mirroring _weight (the shared half is _field_reader). Also fixes the vmbox harness, red since the 2026-09-11 home-tidy: the guest tree lives at ~/wake-alarm, and the INSTALL-OK grep matched vm's echo of the command line. Takes Flutter 3.47.5. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tp54ewdXRLaimUuT8LgYyn
74 lines
2.5 KiB
Python
74 lines
2.5 KiB
Python
"""Pick one field's newest value out of every device's synced log.
|
|
|
|
``_weight`` and ``_energy`` each read a single ``{date, value}`` pair the
|
|
phone publishes on ``alarm.json``; everything but the parse is the same, and
|
|
pylint rightly flagged the copy. This is the shared half: JSON in per device,
|
|
one winner out, ranked by the field's HLC with ``device_id`` breaking exact
|
|
ties so two installs never alternate between reads.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from wake_alarm._alarm_payload import extract_hlc
|
|
from wake_alarm._sync_transport import fetch_firebase_logs
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
def field_value(log: dict[str, Any], field: str) -> object:
|
|
"""The value half of one ``(value, HLC)`` pair on the alarm record, or None."""
|
|
record = log.get("alarm")
|
|
fields = record.get("fields") if isinstance(record, dict) else None
|
|
pair = fields.get(field) if isinstance(fields, dict) else None
|
|
return pair[0] if isinstance(pair, list) and pair else None
|
|
|
|
|
|
def newest_field[T](
|
|
published: list[tuple[str, str]],
|
|
field: str,
|
|
parse: Callable[[object, str], T | None],
|
|
) -> T | None:
|
|
"""The most recently stamped ``field`` across every device's log.
|
|
|
|
Args:
|
|
published: ``(device_id, raw_json)`` per device, in any order.
|
|
field: The crdt_sync field name.
|
|
parse: Turns the raw value plus its device id into the result, or
|
|
None when the shape is not usable -- an unusable device never
|
|
outranks a usable one.
|
|
"""
|
|
ranked: list[tuple[str, str, T]] = []
|
|
for device_id, raw in published:
|
|
try:
|
|
log = json.loads(raw)
|
|
except json.JSONDecodeError:
|
|
continue
|
|
if not isinstance(log, dict):
|
|
continue
|
|
parsed = parse(field_value(log, field), device_id)
|
|
if parsed is not None:
|
|
ranked.append((extract_hlc(log, field), device_id, parsed))
|
|
if not ranked:
|
|
return None
|
|
return max(ranked, key=lambda entry: (entry[0], entry[1]))[2]
|
|
|
|
|
|
def latest_field[T](
|
|
field: str, parse: Callable[[object, str], T | None], *, what: str
|
|
) -> T | None:
|
|
"""``newest_field`` over a live Firebase read; None when unreadable."""
|
|
published = fetch_firebase_logs()
|
|
if published is None:
|
|
return None
|
|
value = newest_field(published, field, parse)
|
|
if value is None:
|
|
_logger.info("No %s published yet", what)
|
|
return value
|