Parse iw scan dump output into network dicts.
(self, output, interface)
| 2921 | 'error': None if detected else 'No GPS device detected', |
| 2922 | } |
| 2923 | |
| 2924 | # 'starting' is the bring-up window (start() called, scan loop not live |
| 2925 | # yet). Capped so a start() that raised before setting _running can't |
| 2926 | # leave the panel stuck on "Starting..." forever. |
| 2927 | starting = bool(self._starting and not self._running |
| 2928 | and (time.time() - self._start_ts) < 45) |
| 2929 | result = { |
| 2930 | 'running': self._running, |
| 2931 | 'starting': starting, |
| 2932 | 'session_id': self.session.session_id if self.session else None, |
| 2933 | 'interfaces': self.interfaces, |
| 2934 | 'scans_completed': self.scans_completed, |
| 2935 | 'networks_this_scan': self.networks_per_scan, |
| 2936 | 'total_networks': self.total_networks, |
| 2937 | 'last_scan_time': self.last_scan_time, |
| 2938 | 'error': self.error, |
| 2939 | 'gps': gps_status, |
| 2940 | 'bluetooth_count': self.bt_count, |
| 2941 | 'cell_count': self.cell_count, |
| 2942 | 'zigbee_count': self.zigbee_count, |
| 2943 | 'device_name': self.device_name, |
| 2944 | # Backward-compat single-companion fields (aggregated) |
| 2945 | 'serial_connected': self.serial_connected, |
| 2946 | 'serial_port': self._serial_port or '', |
| 2947 | 'serial_networks': self.serial_networks, |
| 2948 | 'companion_name': self._companion_name, |
| 2949 | 'mesh_node_count': self._mesh_node_count, |
| 2950 | 'coordinator': None, |
| 2951 | 'coordinator_board': self._coordinator_board, |
| 2952 | 'coordinator_fw': self._coordinator_fw, |
| 2953 | 'coordinator_nodes': self._active_coordinator_nodes(), |
| 2954 | 'esp_mode': self._current_esp_mode, |
| 2955 | 'esp_ble_count': self._esp_ble_count, |
| 2956 | 'esp_zigbee_count': self._esp_zigbee_count, |
| 2957 | 'esp_alerts': self._esp_alerts[-5:], |
| 2958 | 'band_mode': self.band_mode, |
| 2959 | # Multi-companion list: full per-device status for the UI. Each gets |
| 2960 | # its configured role ('wardrive' | 'zigbee') so the UI can show and |
| 2961 | # toggle which Huginn is the dedicated Zigbee/Thread node. |
| 2962 | 'companions': [ |
| 2963 | {**c.as_dict(), 'role': self._companion_role(c)} |
| 2964 | for c in self._companions.values() |
| 2965 | ], |
| 2966 | } |
| 2967 | # Interface details (cached 30s — sysfs/udev calls are slow) |
| 2968 | now_if = time.time() |
| 2969 | if self._running and self.interfaces: |
| 2970 | if not hasattr(self, '_iface_cache') or not self._iface_cache or \ |
| 2971 | now_if - getattr(self, '_iface_cache_time', 0) > 30: |
| 2972 | self._iface_cache = self.get_interface_details() |
| 2973 | self._iface_cache_time = now_if |
| 2974 | else: |
| 2975 | # Update only the per-interface network counts (cheap DB query) |
| 2976 | if self.session: |
| 2977 | try: |
| 2978 | with sqlite3.connect(self.session.db_path) as conn: |
| 2979 | for d in self._iface_cache: |
| 2980 | row = conn.execute( |
no test coverage detected