Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions README_es.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions monitor/src/adn_monitor/application/report_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
2 changes: 2 additions & 0 deletions monitor/src/adn_monitor/application/rts_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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"
Expand Down
4 changes: 3 additions & 1 deletion monitor/tests/test_report_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def test_voice_event_to_csv_parts_start():
"3120001",
"2",
"52090",
"0",
]


Expand All @@ -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():
Expand Down
24 changes: 24 additions & 0 deletions monitor/tests/test_rts_display_slot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=[])
Expand Down
Loading