Render a live wardriving map (GPS breadcrumb + network dots). Toggled by KEY3 while wardriving. Auto-scales the current session's GPS track and located networks to the screen; the current fix is marked with a ringed dot.
(self, image, draw)
| 1580 | auto = netdiag_auto_iface() |
| 1581 | rows = [(("*" if sel is None else "") + "Auto", |
| 1582 | f"> {auto}" if auto else 'none usable')] |
| 1583 | for c in netdiag_iface_choices(): |
| 1584 | nm = c['name'] |
| 1585 | if len(nm) > 10: # enx<mac> names — keep head + MAC tail |
| 1586 | nm = nm[:5] + '~' + nm[-4:] |
| 1587 | label = ("*" if sel == c['name'] else "") + nm |
| 1588 | if c['usb'] and len(label) <= 9: |
| 1589 | label += " USB" |
| 1590 | status = c['ipv4'] or ('no IP' if c['up'] else 'down') |
| 1591 | rows.append((label, status)) |
| 1592 | self._draw_stat_rows(draw, y, rows) |
| 1593 | |
| 1594 | elif page == 8: # BT — on-demand Bluetooth/BLE neighbours (press scans) |
| 1595 | self._draw_neighbour_card(draw, y, 'bt') |
| 1596 | |
| 1597 | else: # page 9: ZIGBEE — on-demand 802.15.4 sniff (press scans) |
| 1598 | self._draw_neighbour_card(draw, y, 'zigbee') |
| 1599 | |
| 1600 | def _draw_neighbour_card(self, draw, y, which): |
| 1601 | """The BT / ZIGBEE cards: the last on-demand 2.4 GHz neighbour scan. |
| 1602 | |
| 1603 | Both are one-shot rather than background-polled — BT discovery and an |
| 1604 | 802.15.4 sniff each cost radio time, and Zigbee needs a HuginnESP that |
| 1605 | wardriving may be holding — so the card shows the previous result (or a |
| 1606 | prompt) and the joystick press runs a new scan. `which` is 'bt' or |
| 1607 | 'zigbee'; the payloads differ only in the per-device fields. |
| 1608 | """ |
| 1609 | sy = getattr(self, 'render_sy', self.scale_factor_y) |
| 1610 | bl = self.button_listener |
| 1611 | res = getattr(bl, 'netdiag_bt' if which == 'bt' else 'netdiag_zb', None) if bl else None |
| 1612 | |
| 1613 | if not res: |
| 1614 | label = 'BT' if which == 'bt' else 'Zigbee' |
| 1615 | self._draw_stat_rows(draw, y, [(label, 'no scan'), |
| 1616 | ('Press', 'scans'), |
| 1617 | ('Takes', '~8s')]) |
| 1618 | return |
| 1619 | if res.get('error'): |
| 1620 | self._draw_stat_rows(draw, y, [('Failed', _short_scan_error(res['error'])), |
| 1621 | ('Press', 'retries')]) |
| 1622 | return |
| 1623 | |
| 1624 | itf = res.get('interference') or {} |
| 1625 | rows = [('Devices', res.get('device_count', 0))] |
| 1626 | if which == 'bt': |
| 1627 | rows.append(('LE/Clsc', f"{itf.get('le_count', 0)}/{itf.get('classic_count', 0)}")) |
| 1628 | else: |
| 1629 | rows.append(('Channels', itf.get('channel_count', 0))) |
| 1630 | # Age matters on a one-shot card — a stale scan shouldn't read as live. |
| 1631 | ts = res.get('timestamp') |
| 1632 | if isinstance(ts, (int, float)) and ts > 0: |
| 1633 | age = max(0, int(time.time() - ts)) |
| 1634 | rows.append(('Age', f"{age}s" if age < 120 else f"{age // 60}m")) |
| 1635 | yy = self._draw_stat_rows(draw, y, rows) |
| 1636 | |
| 1637 | # Strongest few neighbours as name/addr + signal bars — the same "who is |
| 1638 | # loud near me" read the SIGNAL card gives for Wi-Fi. |
| 1639 | devices = (res.get('devices') or [])[:3] |
no test coverage detected