Update contacts handler to match actual device payload format

The device sends contact entries with different field names than
originally expected:
- adv_name (not name) for the advertised node name
- type (numeric: 0=none, 1=chat, 2=repeater, 3=room) instead of node_type

Changes:
- Update handle_contacts to extract adv_name and convert numeric type
- Add NODE_TYPE_MAP for type conversion
- Always update node name if different (not just if empty)
- Add debug logging for node updates
- Update ContactInfo schema with actual device fields
This commit is contained in:
Claude
2025-12-04 14:25:11 +00:00
parent 102e40a395
commit cf633f9f44
2 changed files with 60 additions and 8 deletions
@@ -11,6 +11,14 @@ from meshcore_hub.common.models import Node
logger = logging.getLogger(__name__)
# Map numeric node type to string representation
NODE_TYPE_MAP = {
0: "none",
1: "chat",
2: "repeater",
3: "room",
}
def handle_contacts(
public_key: str,
@@ -25,7 +33,7 @@ def handle_contacts(
Args:
public_key: Receiver node's public key (from MQTT topic)
event_type: Event type name
payload: Contacts payload
payload: Contacts payload (array of contact objects from device)
db: Database manager
"""
contacts = payload.get("contacts", [])
@@ -43,16 +51,24 @@ def handle_contacts(
if not contact_key:
continue
name = contact.get("name")
node_type = contact.get("node_type")
# Device uses 'adv_name' for the advertised name
name = contact.get("adv_name") or contact.get("name")
# Device uses numeric 'type' field, convert to string
raw_type = contact.get("type")
if raw_type is not None:
node_type = NODE_TYPE_MAP.get(raw_type, str(raw_type))
else:
node_type = contact.get("node_type")
# Find or create node
node_query = select(Node).where(Node.public_key == contact_key)
node = session.execute(node_query).scalar_one_or_none()
if node:
# Update existing node
if name and not node.name:
# Update existing node - always update name if we have one
if name and name != node.name:
logger.debug(f"Updating node {contact_key[:12]}... name: {name}")
node.name = name
if node_type and not node.adv_type:
node.adv_type = node_type
@@ -69,6 +85,7 @@ def handle_contacts(
)
session.add(node)
created_count += 1
logger.debug(f"Created node {contact_key[:12]}... name: {name}")
logger.info(
f"Processed contacts sync: {created_count} new, {updated_count} updated"
+38 -3
View File
@@ -157,7 +157,16 @@ class TelemetryResponseEvent(BaseModel):
class ContactInfo(BaseModel):
"""Schema for a single contact in CONTACTS event."""
"""Schema for a single contact in CONTACTS event.
Device payload fields:
- public_key: Node's 64-char hex public key
- adv_name: Node's advertised name (device field)
- type: Numeric node type (0=none, 1=chat, 2=repeater, 3=room)
- flags: Capability flags
- last_advert: Unix timestamp of last advertisement
- adv_lat, adv_lon: GPS coordinates (if available)
"""
public_key: str = Field(
...,
@@ -165,14 +174,40 @@ class ContactInfo(BaseModel):
max_length=64,
description="Node's full public key",
)
adv_name: Optional[str] = Field(
default=None,
max_length=255,
description="Node's advertised name (from device)",
)
type: Optional[int] = Field(
default=None,
description="Numeric node type: 0=none, 1=chat, 2=repeater, 3=room",
)
flags: Optional[int] = Field(
default=None,
description="Capability/status flags bitmask",
)
last_advert: Optional[int] = Field(
default=None,
description="Unix timestamp of last advertisement",
)
adv_lat: Optional[float] = Field(
default=None,
description="GPS latitude (if available)",
)
adv_lon: Optional[float] = Field(
default=None,
description="GPS longitude (if available)",
)
# Legacy field names for backwards compatibility
name: Optional[str] = Field(
default=None,
max_length=255,
description="Node name/alias",
description="Node name/alias (legacy, prefer adv_name)",
)
node_type: Optional[str] = Field(
default=None,
description="Node type: chat, repeater, room, none",
description="Node type string (legacy, prefer type)",
)