Fix clock filtering and contact lookup behavior bugs

This commit is contained in:
Jack Kingsman
2026-01-26 22:51:02 -08:00
parent 2b681e1905
commit 00697e3c06
13 changed files with 133 additions and 43 deletions
+16 -8
View File
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
from meshcore import EventType
from app.models import Contact
from app.models import CONTACT_TYPE_REPEATER, Contact
from app.packet_processor import process_raw_packet
from app.repository import ContactRepository, MessageRepository
from app.websocket import broadcast_event
@@ -71,11 +71,20 @@ async def on_contact_message(event: "Event") -> None:
sender_pubkey = payload.get("public_key") or payload.get("pubkey_prefix", "")
received_at = int(time.time())
# Look up full public key from contact database if we only have prefix
if len(sender_pubkey) < 64:
contact = await ContactRepository.get_by_key_prefix(sender_pubkey)
if contact:
sender_pubkey = contact.public_key
# Look up contact from database - use prefix lookup only if needed
# (get_by_key_or_prefix does exact match first, then prefix fallback)
contact = await ContactRepository.get_by_key_or_prefix(sender_pubkey)
if contact:
sender_pubkey = contact.public_key
# Skip messages from repeaters - they only send CLI responses, not chat messages.
# CLI responses are handled by the command endpoint and txt_type filter above.
if contact.type == CONTACT_TYPE_REPEATER:
logger.debug(
"Skipping message from repeater %s (not stored in chat history)",
sender_pubkey[:12],
)
return
# Try to create message - INSERT OR IGNORE handles duplicates atomically
# If the packet processor already stored this message, this returns None
@@ -121,8 +130,7 @@ async def on_contact_message(event: "Event") -> None:
},
)
# Update contact last_contacted
contact = await ContactRepository.get_by_key_prefix(sender_pubkey)
# Update contact last_contacted (contact was already fetched above)
if contact:
await ContactRepository.update_last_contacted(contact.public_key, received_at)
+3
View File
@@ -204,6 +204,9 @@ class TelemetryResponse(BaseModel):
default_factory=list, description="List of neighbors seen by repeater"
)
acl: list[AclEntry] = Field(default_factory=list, description="Access control list")
clock_output: str | None = Field(
default=None, description="Output from 'clock' command (or error message)"
)
class CommandRequest(BaseModel):
+6 -5
View File
@@ -212,13 +212,14 @@ async def create_dm_message_from_decrypted(
Returns the message ID if created, None if duplicate.
"""
# Extract txt_type from flags (lower 4 bits)
# txt_type=1 is CLI response - don't store these in chat history
txt_type = decrypted.flags & 0x0F
if txt_type == 1:
# Check if sender is a repeater - repeaters only send CLI responses, not chat messages.
# CLI responses are handled by the command endpoint, not stored in chat history.
contact = await ContactRepository.get_by_key_or_prefix(their_public_key)
if contact and contact.type == CONTACT_TYPE_REPEATER:
logger.debug(
"Skipping CLI response from %s (txt_type=1)",
"Skipping message from repeater %s (CLI responses not stored): %s",
their_public_key[:12],
(decrypted.message or "")[:50],
)
return None
+42
View File
@@ -361,8 +361,49 @@ async def request_telemetry(public_key: str, request: TelemetryRequest) -> Telem
)
)
# Fetch clock output (up to 2 attempts)
# Must pause polling and stop auto-fetch to prevent race condition where
# the CLI response is consumed before we can call get_msg()
logger.info("Fetching clock from repeater %s", contact.public_key[:12])
clock_output: str | None = None
async with pause_polling():
await mc.stop_auto_message_fetching()
try:
for attempt in range(1, 3):
logger.debug("Clock request attempt %d/2", attempt)
try:
send_result = await mc.commands.send_cmd(contact.public_key, "clock")
if send_result.type == EventType.ERROR:
logger.debug("Clock command send error: %s", send_result.payload)
continue
# Wait for response
wait_result = await mc.wait_for_event(EventType.MESSAGES_WAITING, timeout=5.0)
if wait_result is None:
logger.debug("Clock request timeout, retrying...")
continue
response_event = await mc.commands.get_msg()
if response_event.type == EventType.ERROR:
logger.debug("Clock get_msg error: %s", response_event.payload)
continue
clock_output = response_event.payload.get("text", "")
logger.info("Received clock output: %s", clock_output)
break
except Exception as e:
logger.debug("Clock request exception: %s", e)
continue
finally:
await mc.start_auto_message_fetching()
if clock_output is None:
clock_output = "Unable to fetch `clock` output (repeater did not respond)"
# Convert raw telemetry to response format
# bat is in mV, convert to V (e.g., 3775 -> 3.775)
return TelemetryResponse(
pubkey_prefix=status.get("pubkey_pre", contact.public_key[:12]),
battery_volts=status.get("bat", 0) / 1000.0,
@@ -384,6 +425,7 @@ async def request_telemetry(public_key: str, request: TelemetryRequest) -> Telem
full_events=status.get("full_evts", 0),
neighbors=neighbors,
acl=acl_entries,
clock_output=clock_output,
)