mirror of
https://github.com/l5yth/potato-mesh.git
synced 2026-07-06 01:42:11 +02:00
* enh: surface meshcore role types (#680) Map MeshCore ADV_TYPE_* integers to user.role strings so COMPANION, REPEATER, ROOM_SERVER, and SENSOR roles are surfaced to the dashboard. Role is omitted when ADV_TYPE_NONE (0) or unknown. Co-authored-by: Ben Allfree <ben@benallfree.com> * data: run black --------- Co-authored-by: Ben Allfree <ben@benallfree.com>
This commit is contained in:
@@ -37,7 +37,8 @@ Node entry fields are “Meshtastic-ish” (camelCase) and may include:
|
||||
- `snr` (float)
|
||||
- `hopsAway` (int)
|
||||
- `isFavorite` (bool)
|
||||
- `user` (mapping; e.g. `shortName`, `longName`, `macaddr`, `hwModel`, `role`, `publicKey`, `isUnmessagable`)
|
||||
- `user` (mapping; e.g. `shortName`, `longName`, `macaddr`, `hwModel`, `publicKey`, `isUnmessagable`)
|
||||
- `role` (optional string) — omit when unknown; known values include Meshtastic role names (e.g. `CLIENT`, `ROUTER`) and MeshCore role names (`COMPANION`, `REPEATER`, `ROOM_SERVER`, `SENSOR`)
|
||||
- `deviceMetrics` (mapping; e.g. `batteryLevel`, `voltage`, `channelUtilization`, `airUtilTx`, `uptimeSeconds`)
|
||||
- `position` (mapping; `latitude`, `longitude`, `altitude`, `time`, `locationSource`, `precisionBits`, optional nested `raw`)
|
||||
- Optional radio metadata: `lora_freq`, `modem_preset`
|
||||
|
||||
@@ -73,6 +73,14 @@ _CONNECT_TIMEOUT_SECS: float = 30.0
|
||||
_DEFAULT_BAUDRATE: int = 115200
|
||||
"""Default baud rate for MeshCore serial connections."""
|
||||
|
||||
# MeshCore ``ADV_TYPE_*`` (``AdvertDataHelpers.h``) → ``user.role`` for POST /api/nodes.
|
||||
_MESHCORE_ADV_TYPE_ROLE: dict[int, str] = {
|
||||
1: "COMPANION", # ADV_TYPE_CHAT
|
||||
2: "REPEATER", # ADV_TYPE_REPEATER
|
||||
3: "ROOM_SERVER", # ADV_TYPE_ROOM_SERVER
|
||||
4: "SENSOR", # ADV_TYPE_SENSOR
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -131,14 +139,36 @@ def _meshcore_short_name(public_key_hex: str | None) -> str:
|
||||
public_key_hex: Full public key as a hex string from the MeshCore API.
|
||||
|
||||
Returns:
|
||||
Four lowercase hex characters, or an empty string when the key is
|
||||
missing or shorter than four hex digits.
|
||||
Four lowercase hex characters (e.g. ``"aabb"``), or an empty string
|
||||
when the key is missing or shorter than four hex characters.
|
||||
"""
|
||||
if not public_key_hex or len(public_key_hex) < 4:
|
||||
return ""
|
||||
return public_key_hex[:4].lower()
|
||||
|
||||
|
||||
def _meshcore_adv_type_to_role(adv_type: object) -> str | None:
|
||||
"""Map MeshCore ``ADV_TYPE_*`` (contact ``type`` / self ``adv_type``) to ingest role.
|
||||
|
||||
Values match MeshCore firmware ``AdvertDataHelpers.h`` (``ADV_TYPE_CHAT``,
|
||||
``ADV_TYPE_REPEATER``, …). Role strings match the MeshCore palette keys
|
||||
used by the web dashboard (``COMPANION``, ``REPEATER``, …).
|
||||
|
||||
Parameters:
|
||||
adv_type: Raw type byte from meshcore_py (typically ``int`` 0–4).
|
||||
Non-integer values (e.g. ``float``, ``None``) are rejected and
|
||||
return ``None``. Future firmware type codes not yet in the mapping
|
||||
also return ``None`` until the table is updated.
|
||||
|
||||
Returns:
|
||||
Uppercase role string, or ``None`` when the value is unknown or should
|
||||
not override the web default (``ADV_TYPE_NONE`` / unrecognised).
|
||||
"""
|
||||
if not isinstance(adv_type, int):
|
||||
return None
|
||||
return _MESHCORE_ADV_TYPE_ROLE.get(adv_type)
|
||||
|
||||
|
||||
def _pubkey_prefix_to_node_id(contacts: dict, pubkey_prefix: str) -> str | None:
|
||||
"""Look up a canonical node ID by six-byte public-key prefix.
|
||||
|
||||
@@ -162,20 +192,22 @@ def _contact_to_node_dict(contact: dict) -> dict:
|
||||
|
||||
Parameters:
|
||||
contact: Contact dict from the MeshCore library. Expected keys
|
||||
include ``public_key``, ``adv_name``, ``last_advert``,
|
||||
``adv_lat``, and ``adv_lon``.
|
||||
include ``public_key``, ``type`` (``ADV_TYPE_*``), ``adv_name``,
|
||||
``last_advert``, ``adv_lat``, and ``adv_lon``.
|
||||
|
||||
Returns:
|
||||
Node dict compatible with the ``POST /api/nodes`` payload format.
|
||||
"""
|
||||
pub_key = contact.get("public_key", "")
|
||||
name = (contact.get("adv_name") or "").strip()
|
||||
role = _meshcore_adv_type_to_role(contact.get("type"))
|
||||
node: dict = {
|
||||
"lastHeard": contact.get("last_advert"),
|
||||
"user": {
|
||||
"longName": name,
|
||||
"shortName": _meshcore_short_name(pub_key),
|
||||
"publicKey": pub_key,
|
||||
**({"role": role} if role is not None else {}),
|
||||
},
|
||||
}
|
||||
lat = contact.get("adv_lat")
|
||||
@@ -190,19 +222,22 @@ def _self_info_to_node_dict(self_info: dict) -> dict:
|
||||
|
||||
Parameters:
|
||||
self_info: Payload dict from the ``SELF_INFO`` event. Expected keys
|
||||
include ``name``, ``public_key``, ``adv_lat``, and ``adv_lon``.
|
||||
include ``name``, ``public_key``, ``adv_type`` (``ADV_TYPE_*``),
|
||||
``adv_lat``, and ``adv_lon``.
|
||||
|
||||
Returns:
|
||||
Node dict compatible with the ``POST /api/nodes`` payload format.
|
||||
"""
|
||||
name = (self_info.get("name") or "").strip()
|
||||
pub_key = self_info.get("public_key", "")
|
||||
role = _meshcore_adv_type_to_role(self_info.get("adv_type"))
|
||||
node: dict = {
|
||||
"lastHeard": int(time.time()),
|
||||
"user": {
|
||||
"longName": name,
|
||||
"shortName": _meshcore_short_name(pub_key),
|
||||
"publicKey": pub_key,
|
||||
**({"role": role} if role is not None else {}),
|
||||
},
|
||||
}
|
||||
lat = self_info.get("adv_lat")
|
||||
|
||||
Reference in New Issue
Block a user