mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-19 09:51:30 +10:00
104 lines
3.6 KiB
Python
104 lines
3.6 KiB
Python
"""Read-only device queries via meshtastic.SerialInterface."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from .connection import connect
|
|
|
|
|
|
def _primary_channel_name(iface) -> str | None:
|
|
try:
|
|
channels = iface.localNode.channels or []
|
|
except AttributeError:
|
|
return None
|
|
for ch in channels:
|
|
role = getattr(ch, "role", None)
|
|
# Role enum: 0 DISABLED, 1 PRIMARY, 2 SECONDARY
|
|
if role == 1:
|
|
name = getattr(getattr(ch, "settings", None), "name", None)
|
|
return name or "(default)"
|
|
return None
|
|
|
|
|
|
def device_info(port: str | None = None, timeout_s: float = 8.0) -> dict[str, Any]:
|
|
"""Return summary info for the connected device."""
|
|
with connect(port=port, timeout_s=timeout_s) as iface:
|
|
my = iface.myInfo
|
|
meta = iface.metadata
|
|
local = iface.localNode
|
|
|
|
# Owner (long/short name) is on the local node's user record
|
|
long_name: str | None = None
|
|
short_name: str | None = None
|
|
hw_model: str | int | None = None
|
|
if iface.nodesByNum and my is not None:
|
|
local_rec = iface.nodesByNum.get(my.my_node_num, {})
|
|
user = local_rec.get("user") or {}
|
|
long_name = user.get("longName")
|
|
short_name = user.get("shortName")
|
|
hw_model = user.get("hwModel")
|
|
|
|
region = None
|
|
if local is not None and local.localConfig is not None:
|
|
try:
|
|
lora = local.localConfig.lora
|
|
# region is an enum; get its string name
|
|
region = (
|
|
lora.DESCRIPTOR.fields_by_name["region"]
|
|
.enum_type.values_by_number[lora.region]
|
|
.name
|
|
)
|
|
except Exception:
|
|
region = None
|
|
|
|
return {
|
|
"port": iface.devPath if hasattr(iface, "devPath") else port,
|
|
"my_node_num": getattr(my, "my_node_num", None),
|
|
"long_name": long_name,
|
|
"short_name": short_name,
|
|
"firmware_version": getattr(meta, "firmware_version", None),
|
|
"hw_model": hw_model,
|
|
"region": region,
|
|
"num_nodes": len(iface.nodesByNum) if iface.nodesByNum else 0,
|
|
"primary_channel": _primary_channel_name(iface),
|
|
}
|
|
|
|
|
|
def _node_record(node_dict: dict[str, Any]) -> dict[str, Any]:
|
|
user = node_dict.get("user") or {}
|
|
position = node_dict.get("position") or None
|
|
device_metrics = node_dict.get("deviceMetrics") or {}
|
|
return {
|
|
"node_num": node_dict.get("num"),
|
|
"user": {
|
|
"long_name": user.get("longName"),
|
|
"short_name": user.get("shortName"),
|
|
"hw_model": user.get("hwModel"),
|
|
"role": user.get("role"),
|
|
},
|
|
"position": (
|
|
{
|
|
"latitude": position.get("latitude"),
|
|
"longitude": position.get("longitude"),
|
|
"altitude": position.get("altitude"),
|
|
"time": position.get("time"),
|
|
}
|
|
if position
|
|
else None
|
|
),
|
|
"snr": node_dict.get("snr"),
|
|
"rssi": node_dict.get("rssi"),
|
|
"last_heard": node_dict.get("lastHeard"),
|
|
"battery_level": device_metrics.get("batteryLevel"),
|
|
"is_favorite": bool(node_dict.get("isFavorite", False)),
|
|
}
|
|
|
|
|
|
def list_nodes(port: str | None = None, timeout_s: float = 8.0) -> list[dict[str, Any]]:
|
|
"""Return the device's node database."""
|
|
with connect(port=port, timeout_s=timeout_s) as iface:
|
|
if not iface.nodesByNum:
|
|
return []
|
|
return [_node_record(n) for n in iface.nodesByNum.values()]
|