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
1 change: 1 addition & 0 deletions src/meshcore_console/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Message:
created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
is_outgoing: bool = False
path_len: int = 0
path_hops: list[str] = field(default_factory=list)
snr: float | None = None
rssi: int | None = None

Expand Down
2 changes: 2 additions & 0 deletions src/meshcore_console/meshcore/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,7 @@ def _process_message_event(self, data: MeshEventDict, event_type: str = "") -> N
snr = data.get("snr")
rssi = data.get("rssi")
path_len = data.get("path_len") or 0
path_hops = data.get("path_hops", [])
message = Message(
message_id=msg_id,
sender_id=sender_name,
Expand All @@ -588,6 +589,7 @@ def _process_message_event(self, data: MeshEventDict, event_type: str = "") -> N
created_at=datetime.now(UTC),
is_outgoing=False,
path_len=int(path_len) if path_len else 0,
path_hops=list(path_hops) if path_hops else [],
snr=float(snr) if snr is not None else None,
rssi=int(rssi) if rssi is not None else None,
)
Expand Down
2 changes: 2 additions & 0 deletions src/meshcore_console/meshcore/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
tuple(stmt.strip() for stmt in SCHEMA_V1.split(";") if stmt.strip()),
# v1 -> v2: add peer_name column to channels for original-case contact names
("ALTER TABLE channels ADD COLUMN peer_name TEXT",),
# v2 -> v3: add path_hops column to messages for route visualization
("ALTER TABLE messages ADD COLUMN path_hops TEXT",),
]


Expand Down
12 changes: 8 additions & 4 deletions src/meshcore_console/meshcore/state_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def __init__(self, conn: sqlite3.Connection) -> None:
def append(self, message: Message) -> None:
self._conn.execute(
"INSERT OR IGNORE INTO messages "
"(message_id, sender_id, body, channel_id, created_at, is_outgoing, path_len, snr, rssi) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
"(message_id, sender_id, body, channel_id, created_at, is_outgoing, path_len, snr, rssi, path_hops) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(
message.message_id,
message.sender_id,
Expand All @@ -35,6 +35,7 @@ def append(self, message: Message) -> None:
message.path_len,
message.snr,
message.rssi,
json.dumps(message.path_hops) if message.path_hops else None,
),
)
# Prune oldest messages beyond limit
Expand All @@ -53,14 +54,14 @@ def flush_if_dirty(self) -> None:
def get_all(self) -> list[Message]:
rows = self._conn.execute(
"SELECT message_id, sender_id, body, channel_id, created_at, "
"is_outgoing, path_len, snr, rssi FROM messages ORDER BY created_at"
"is_outgoing, path_len, snr, rssi, path_hops FROM messages ORDER BY created_at"
).fetchall()
return [_row_to_message(r) for r in rows]

def get_for_channel(self, channel_id: str, limit: int = 50) -> list[Message]:
rows = self._conn.execute(
"SELECT message_id, sender_id, body, channel_id, created_at, "
"is_outgoing, path_len, snr, rssi FROM messages "
"is_outgoing, path_len, snr, rssi, path_hops FROM messages "
"WHERE channel_id = ? ORDER BY created_at",
(channel_id,),
).fetchall()
Expand Down Expand Up @@ -195,6 +196,8 @@ def _row_to_message(row: tuple) -> Message:
created_at = datetime.fromisoformat(created_at)
else:
created_at = datetime.now(UTC)
path_hops_raw = row[9] if len(row) > 9 else None
path_hops = json.loads(path_hops_raw) if isinstance(path_hops_raw, str) else []
return Message(
message_id=row[0],
sender_id=row[1],
Expand All @@ -203,6 +206,7 @@ def _row_to_message(row: tuple) -> Message:
created_at=created_at,
is_outgoing=bool(row[5]),
path_len=row[6] or 0,
path_hops=path_hops,
snr=row[7],
rssi=row[8],
)
Expand Down
2 changes: 2 additions & 0 deletions src/meshcore_console/mock/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ def _process_event_for_messages(self, event: dict) -> None:
if content_key in existing:
return

path_hops = data.get("path_hops", [])
message = Message(
message_id=str(uuid4()),
sender_id=sender_name,
Expand All @@ -248,6 +249,7 @@ def _process_event_for_messages(self, event: dict) -> None:
created_at=datetime.now(UTC),
is_outgoing=False,
path_len=int(data.get("path_len") or 0),
path_hops=list(path_hops) if path_hops else [],
snr=float(data["snr"]) if data.get("snr") is not None else None,
rssi=int(data["rssi"]) if data.get("rssi") is not None else None,
)
Expand Down
10 changes: 8 additions & 2 deletions src/meshcore_console/mock/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,24 @@ def create_mock_messages() -> list[Message]:
sender_id="Relay A",
body="Route check complete",
channel_id="test",
path_len=2,
path_hops=["B7", "C2"],
),
Message(
message_id=str(uuid4()),
sender_id="Backhaul B",
body="Link stable at SF7",
channel_id="public",
path_len=2,
path_hops=["relay-001", "relay-002"],
),
Message(
message_id=str(uuid4()),
sender_id="Relay A",
body="Forwarding advert burst",
channel_id="ops",
path_len=1,
path_hops=["relay-002"],
),
]

Expand Down Expand Up @@ -195,8 +201,8 @@ def _mock_ts(seed: str, day: datetime = now, spread_min: int = 120) -> str:
"rssi": -95,
"snr": -2.50,
"payload_hex": "f589d410abcd1234567890abcdef",
"path_len": 0,
"path_hops": [],
"path_len": 2,
"path_hops": ["B7", "C2"],
"packet_hash": "1234567890AB",
},
},
Expand Down
31 changes: 26 additions & 5 deletions src/meshcore_console/ui_gtk/views/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from meshcore_console.core.radio import snr_to_quality
from meshcore_console.core.services import MeshcoreService
from meshcore_console.core.time import to_local
from meshcore_console.ui_gtk.widgets import DetailRow, EmptyState, MessageBubble
from meshcore_console.ui_gtk.widgets import DetailRow, EmptyState, MessageBubble, PathVisualization
from meshcore_console.ui_gtk.widgets.node_badge import STYLE_DEFAULT, STYLE_SELF, find_peer_for_hop

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -316,14 +317,34 @@ def _show_message_details(self, message: Message) -> None:
details_header.set_margin_top(8)
self._details_box.append(details_header)

# Hops
# Route / Hops
if message.is_outgoing:
hops_text = "Sent"
self._details_box.append(DetailRow("Hops:", "Sent"))
elif message.path_hops:
route_label = Gtk.Label(label="Route:")
route_label.add_css_class("detail-label")
route_label.set_halign(Gtk.Align.START)
self._details_box.append(route_label)

all_peers = self._service.list_peers()
sender_name = message.sender_id
sender_peer = find_peer_for_hop(all_peers, sender_name)
sender_prefix = (sender_name or "??")[:2].upper()

path = PathVisualization(
hops=message.path_hops,
peers=all_peers,
arrow="\u2190",
start=("Me", "You (this node)", None, STYLE_SELF),
end=(sender_prefix, sender_name, sender_peer, STYLE_DEFAULT),
)
path.set_margin_top(4)
self._details_box.append(path)
elif message.path_len == 0:
hops_text = "Direct"
self._details_box.append(DetailRow("Hops:", "Direct"))
else:
hops_text = f"{message.path_len} hop{'s' if message.path_len != 1 else ''}"
self._details_box.append(DetailRow("Hops:", hops_text))
self._details_box.append(DetailRow("Hops:", hops_text))

# Time
time_label = "Sent:" if message.is_outgoing else "Received:"
Expand Down
6 changes: 5 additions & 1 deletion src/meshcore_console/ui_gtk/widgets/message_bubble.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,11 @@ def __init__(
if message.is_outgoing:
meta_text = local_time
else:
meta_text = f"{message.sender_id} {local_time}"
path_str = ",".join(message.path_hops) if message.path_hops else ""
if path_str:
meta_text = f"{message.sender_id} {local_time} {path_str}"
else:
meta_text = f"{message.sender_id} {local_time}"
meta = Gtk.Label(label=meta_text)
meta.add_css_class("message-meta")
meta.set_xalign(1 if message.is_outgoing else 0)
Expand Down
1 change: 1 addition & 0 deletions tests/unit/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def conn(tmp_path):
"path_len",
"snr",
"rssi",
"path_hops",
],
"packets": ["id", "received_at", "data"],
}
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.