From a9cd1643013737cec7dbcfb4df18bb541d8762fb Mon Sep 17 00:00:00 2001 From: archipelago Date: Wed, 22 Jul 2026 15:51:18 -0400 Subject: [PATCH] fix(pine): announce the first-ever mesh message + halve announce lag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The seeded announce automation blocked state transitions out of None/ unavailable — but None is exactly the sensor's state before the first received message (and after the in-memory store restarts empty), so a fresh node's first DM was silently swallowed (framework-pt 2026-07-22). Only 'unknown' (the HA-restart marker) still blocks; rest sensors don't restore state, so restart announce-storms remain impossible. Seeder upgrades an already-seeded automations.yaml by replacing exactly the v1 clause, leaving user edits untouched. Sensor scan_interval 30->15s: the sensor only carries the latest message id, so two messages inside one poll window coalesce into one announce — halving the window halves both the lag and the coalescing odds. Co-Authored-By: Claude Fable 5 --- .../src/api/rpc/package/pine_ha.rs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/core/archipelago/src/api/rpc/package/pine_ha.rs b/core/archipelago/src/api/rpc/package/pine_ha.rs index dc1efdad..2e045ec3 100644 --- a/core/archipelago/src/api/rpc/package/pine_ha.rs +++ b/core/archipelago/src/api/rpc/package/pine_ha.rs @@ -123,7 +123,11 @@ rest: - resource: http://host.containers.internal/api/pine/status headers: Authorization: "Bearer {token}" - scan_interval: 30 + # 15s: the mesh-message sensor only carries the LATEST message id, so two + # messages inside one poll window coalesce and only the newest announces + # (observed on framework-pt 2026-07-22). Halving the window halves both + # the announce lag and the coalescing odds; the endpoint is local + cheap. + scan_interval: 15 sensor: - name: "Archy Block Height" unique_id: archy_block_height @@ -337,7 +341,14 @@ const MESH_ANNOUNCE_AUTOMATION_ID: &str = "archy_mesh_announce"; /// Announce new mesh messages on every Assist satellite. Only seeded into an /// empty/missing automations.yaml — merging into user-authored YAML risks /// mangling it, and anyone with existing automations can paste this one from -/// the docs. Trigger conditions skip HA-restart transitions (unknown -> id). +/// the docs. Trigger conditions skip HA-restart transitions (unknown -> id) +/// but deliberately allow `None`/`unavailable` -> id: `None` is the sensor's +/// state before the first-ever received message (and again whenever the mesh +/// message store restarts empty), and `unavailable` follows any endpoint +/// blip — blocking those swallowed the first DM a fresh node ever received +/// (framework-pt, 2026-07-22). HA restarts still can't re-announce an old +/// message: rest sensors don't restore state, so the post-restart transition +/// is always from `unknown`. const MESH_ANNOUNCE_AUTOMATION: &str = r#"- id: archy_mesh_announce alias: Announce mesh messages on Pine description: Speak new mesh messages on the Pine speaker (seeded by Archipelago) @@ -348,7 +359,7 @@ const MESH_ANNOUNCE_AUTOMATION: &str = r#"- id: archy_mesh_announce - condition: template value_template: >- {{ trigger.from_state is not none - and trigger.from_state.state not in ['unknown', 'unavailable', 'None'] + and trigger.from_state.state != 'unknown' and trigger.to_state.state not in ['unknown', 'unavailable', 'None'] and trigger.from_state.state != trigger.to_state.state }} actions: @@ -371,6 +382,22 @@ async fn seed_mesh_announce_automation() -> bool { let path = std::path::Path::new(HA_CONFIG_DIR).join("automations.yaml"); let existing = tokio::fs::read_to_string(&path).await.unwrap_or_default(); if existing.contains(MESH_ANNOUNCE_AUTOMATION_ID) { + // v1 of the seeded condition also blocked `None`/`unavailable` -> id + // transitions, swallowing the first-ever received message. Upgrade by + // replacing exactly that clause; anything else in the file (including + // user-added automations) is left untouched. + const V1_CLAUSE: &str = + "trigger.from_state.state not in ['unknown', 'unavailable', 'None']"; + const V2_CLAUSE: &str = "trigger.from_state.state != 'unknown'"; + if existing.contains(V1_CLAUSE) { + let upgraded = existing.replace(V1_CLAUSE, V2_CLAUSE); + if let Err(e) = tokio::fs::write(&path, upgraded).await { + warn!("pine/HA seed: upgrading automations.yaml failed: {e}"); + return false; + } + info!("pine/HA seed: mesh announce automation upgraded (first message announces)"); + return true; + } return false; } let trimmed = existing.trim();