From 1f6610695b0217794c50044575810ef57f6a1d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20P=C3=A9rez?= Date: Thu, 23 Jul 2026 14:23:50 -0400 Subject: [PATCH 1/2] feat: track is_announcement flag on GROUP VOICE events New trailing CSV/JSON field from new-adn-server marks calls that come from the announcement/TTS synthetic PTT path rather than a real connected peer. Store it as peer_ts["ANNOUNCEMENT"] alongside TRX/SUB/CALL for dashboard use; propagate through the MQTT voice_event -> CSV parts mapper too. --- .../adn_monitor/application/report_mapper.py | 1 + .../src/adn_monitor/application/rts_update.py | 2 ++ monitor/tests/test_report_mapper.py | 4 +++- monitor/tests/test_rts_display_slot.py | 24 +++++++++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/monitor/src/adn_monitor/application/report_mapper.py b/monitor/src/adn_monitor/application/report_mapper.py index 92e6790..840a7be 100644 --- a/monitor/src/adn_monitor/application/report_mapper.py +++ b/monitor/src/adn_monitor/application/report_mapper.py @@ -357,6 +357,7 @@ def voice_event_to_csv_parts(voice: dict[str, Any]) -> list[str] | None: dur = voice.get("duration_s") if dur is not None: parts.append(f"{float(dur):.2f}") + parts.append("1" if voice.get("is_announcement") else "0") return parts diff --git a/monitor/src/adn_monitor/application/rts_update.py b/monitor/src/adn_monitor/application/rts_update.py index e0ca63c..6b65e7e 100644 --- a/monitor/src/adn_monitor/application/rts_update.py +++ b/monitor/src/adn_monitor/application/rts_update.py @@ -288,6 +288,7 @@ def rts_update_impl( source_sub = int(p[6]) time_slot = int(p[7]) destination = int(p[8]) + is_announcement = len(p) > 9 and p[-1] == "1" timeout = time.time() ctable = state.CTABLE sub_short = alias_svc.alias_short(source_sub) @@ -375,6 +376,7 @@ def rts_update_impl( peer_ts["DEST"] = tg_dest peer_ts["TG"] = tg_short peer_ts["TRX"] = crxstatus + peer_ts["ANNOUNCEMENT"] = is_announcement elif action == "END": if ( trx == "TX" diff --git a/monitor/tests/test_report_mapper.py b/monitor/tests/test_report_mapper.py index 7e3dcb0..92617e4 100644 --- a/monitor/tests/test_report_mapper.py +++ b/monitor/tests/test_report_mapper.py @@ -225,6 +225,7 @@ def test_voice_event_to_csv_parts_start(): "3120001", "2", "52090", + "0", ] @@ -243,7 +244,8 @@ def test_voice_event_to_csv_parts_end_with_duration(): "duration_s": 12.5, } parts = voice_event_to_csv_parts(voice) - assert parts[-1] == "12.50" + assert parts[-2] == "12.50" + assert parts[-1] == "0" def test_voice_event_unit_data_header(): diff --git a/monitor/tests/test_rts_display_slot.py b/monitor/tests/test_rts_display_slot.py index 18f5b74..4ca1c88 100644 --- a/monitor/tests/test_rts_display_slot.py +++ b/monitor/tests/test_rts_display_slot.py @@ -104,6 +104,30 @@ def test_transmitter_rx_uses_wire_slot_not_options() -> None: assert peer[2]["TRX"] == "RX" +def test_start_stores_announcement_flag_from_trailing_field() -> None: + state = _state_with_peer(peer_id=730002, ts1_static=[], ts2_static=["73010"]) + rts_update_impl( + "GROUP VOICE,START,RX,SYSTEM,1,730002,730002,2,73010,1".split(","), + state, + _alias(), + lambda: "12:00", + ) + peer = state.CTABLE["MASTERS"]["SYSTEM"]["PEERS"][730002] + assert peer[2]["ANNOUNCEMENT"] is True + + +def test_start_without_trailing_field_defaults_announcement_false() -> None: + state = _state_with_peer(peer_id=730002, ts1_static=[], ts2_static=["73010"]) + rts_update_impl( + "GROUP VOICE,START,RX,SYSTEM,1,730002,730002,2,73010".split(","), + state, + _alias(), + lambda: "12:00", + ) + peer = state.CTABLE["MASTERS"]["SYSTEM"]["PEERS"][730002] + assert peer[2]["ANNOUNCEMENT"] is False + + def test_end_clears_cross_slot_when_static_tg_removed() -> None: """END must clear the slot lit at START even if OPTIONS no longer map the TG there.""" state = _state_with_peer(peer_id=730001, ts1_static=["52090"], ts2_static=[]) From bc662c036337b1a8e438a8d1ab4c1171f99d71d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20P=C3=A9rez?= Date: Thu, 23 Jul 2026 15:14:26 -0400 Subject: [PATCH 2/2] chore: add HP3ICC acknowledgments to README files --- README.md | 4 ++++ README_es.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/README.md b/README.md index 7866a14..92208ef 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,10 @@ This project is **GPL v3**. The **monitor** (Python) and **frontend** (React das **This codebase:** Copyright (C) 2026 Rodrigo Pérez, CE5RPY. Original works and these derivatives are free software under the [GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html). Preserve license and attribution when distributing or modifying. +## Acknowledgments + +Thanks to **[Esteban Mackay, HP3ICC](https://gitlab.com/hp3icc)**, for ideas, help, testing, and suggestions throughout development. + --- ## Further reading diff --git a/README_es.md b/README_es.md index c9080c7..1878f60 100644 --- a/README_es.md +++ b/README_es.md @@ -205,6 +205,10 @@ Este proyecto es **GPL v3**. El **monitor** (Python) y el **frontend** (dashboar **Este código:** Copyright (C) 2026 Rodrigo Pérez, CE5RPY. Las obras originales y estos derivados son software libre bajo la [GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.html). Conserva licencia y atribución al distribuir o modificar. +## Agradecimientos + +Gracias a **[Esteban Mackay, HP3ICC](https://gitlab.com/hp3icc)**, por las ideas, ayudas, pruebas y sugerencias a lo largo del desarrollo. + --- ## Lecturas adicionales