diff --git a/app/device_manager.py b/app/device_manager.py index d3a36e9..2bbd8aa 100644 --- a/app/device_manager.py +++ b/app/device_manager.py @@ -1741,6 +1741,37 @@ class DeviceManager: logger.error(f"req_mma failed: {e}") return {'success': False, 'error': str(e)} + def repeater_req_neighbours(self, name_or_key: str) -> Dict: + """Request neighbours from a repeater.""" + if not self.is_connected: + return {'success': False, 'error': 'Device not connected'} + contact = self.resolve_contact(name_or_key) + if not contact: + return {'success': False, 'error': f"Contact not found: {name_or_key}"} + try: + contact_timeout = contact.get('timeout', 0) or 0 + result = self.execute( + self.mc.commands.fetch_all_neighbours(contact, timeout=contact_timeout), + timeout=120 + ) + if result is not None: + return {'success': True, 'data': result} + return {'success': False, 'error': 'No neighbours response (timeout)'} + except Exception as e: + logger.error(f"req_neighbours failed: {e}") + return {'success': False, 'error': str(e)} + + def resolve_contact_name(self, pubkey_prefix: str) -> str: + """Resolve a contact name from pubkey prefix using device memory and DB cache.""" + if self.mc: + contact = self.mc.get_contact_by_key_prefix(pubkey_prefix) + if contact: + return contact.get('adv_name', '') or contact.get('name', '') + db_contact = self.db.get_contact_by_prefix(pubkey_prefix) + if db_contact: + return db_contact.get('name', '') + return '' + # ── Contact Management (extended) ──────────────────────────── def contact_info(self, name_or_key: str) -> Dict: diff --git a/app/main.py b/app/main.py index 0f4b897..a977325 100644 --- a/app/main.py +++ b/app/main.py @@ -477,10 +477,42 @@ def _execute_console_command(args: list) -> str: name = ' '.join(args[1:]) result = device_manager.repeater_req_clock(name) if result.get('success'): + import datetime as _dt data = result['data'] - lines = [f"Clock of {name}:"] - for k, v in data.items(): - lines.append(f" {k}: {v}") + hex_data = data.get('data', '') + timestamp = int.from_bytes(bytes.fromhex(hex_data[0:8]), byteorder="little", signed=False) + dt_str = _dt.datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") + return f"Clock of {name}: {dt_str} ({timestamp})" + return f"Error: {result.get('error')}" + + elif cmd == 'req_neighbours' and len(args) >= 2: + name = ' '.join(args[1:]) + result = device_manager.repeater_req_neighbours(name) + if result.get('success'): + data = result['data'] + total = data.get('neighbours_count', 0) + got = data.get('results_count', 0) + lines = [f"Got {got} neighbours out of {total} from {name}:"] + for n in data.get('neighbours', []): + pubkey = n.get('pubkey', '') + ct_name = device_manager.resolve_contact_name(pubkey) + if ct_name: + label = f"[{pubkey[0:8]}] {ct_name}" + else: + label = f"[{pubkey}]" + + t_s = n.get('secs_ago', 0) + if t_s >= 86400: + time_ago = f"{int(t_s / 86400)}d ago ({t_s}s)" + elif t_s >= 3600: + time_ago = f"{int(t_s / 3600)}h ago ({t_s}s)" + elif t_s >= 60: + time_ago = f"{int(t_s / 60)}m ago ({t_s}s)" + else: + time_ago = f"{t_s}s" + + snr = n.get('snr', 0) + lines.append(f" {label:30s} {time_ago}, {snr}dB SNR") return "\n".join(lines) return f"Error: {result.get('error')}" @@ -868,12 +900,13 @@ def _execute_console_command(args: list) -> str: " login — Log into a repeater\n" " logout — Log out of a repeater\n" " cmd — Send command to a repeater\n" - " req_status — Request repeater status\n" - " req_regions — Request repeater regions\n" - " req_owner — Request repeater owner\n" - " req_acl — Request access control list\n" - " req_clock — Request repeater clock\n" - " req_mma — Request min/max/avg sensor data\n\n" + " req_status — Request repeater status\n" + " req_neighbours — Request repeater neighbours\n" + " req_regions — Request repeater regions\n" + " req_owner — Request repeater owner\n" + " req_acl — Request access control list\n" + " req_clock — Request repeater clock\n" + " req_mma — Request min/max/avg sensor data\n\n" " Management\n" " get — Get device parameter\n" " set — Set device parameter\n"