Get detailed info about a WiFi interface (bands, manufacturer, USB/built-in).
(self, iface)
| 2104 | if power.get('supported') and power.get('status') != 'ok': |
| 2105 | logger.warning( |
| 2106 | f"Wardriving: POWER — {power.get('message')} " |
| 2107 | f"(throttle register {power.get('raw')}, " |
| 2108 | f"{len(self.interfaces)} radio(s) about to start)") |
| 2109 | except Exception as e: |
| 2110 | logger.debug(f"power status check failed: {e}") |
| 2111 | |
| 2112 | # Bring each interface up (rfkill unblock + ip link up). USB adapters |
| 2113 | # like the NETGEAR mt76x2u enumerate as admin-DOWN; iw scan on a DOWN |
| 2114 | # interface returns "Network is down" and zero results forever. |
| 2115 | for iface in self.interfaces: |
| 2116 | self._prepare_interface(iface) |
| 2117 | |
| 2118 | # Compute per-adapter band assignments (only matters when band_mode='split'). |
| 2119 | # Must run AFTER _prepare_interface because freq detection reads iw phy info, |
| 2120 | # which can fail on a wedged/down interface. |
| 2121 | self.band_mode = (self.shared_data.config.get('wardriving_band_mode', 'redundant') |
| 2122 | or 'redundant').lower() |
| 2123 | if self.band_mode not in ('redundant', 'split'): |
| 2124 | self.band_mode = 'redundant' |
| 2125 | self._iface_freq_cache.clear() # force re-detection with new assignments |
| 2126 | self._compute_band_assignments() |
| 2127 | |
| 2128 | # Pre-detect ALL ESP32 ports BEFORE GPS auto-detection so we can |
| 2129 | # exclude them from GPS probing (a tty collision makes both readers |
| 2130 | # get half the bytes each — GPS reports 0 sats, companion mis-classifies). |
| 2131 | import glob as _glob |
| 2132 | _raw_candidates = sorted(_glob.glob('/dev/ttyACM*') + _glob.glob('/dev/ttyUSB*')) |
| 2133 | esp_exclude = {p for p in _raw_candidates if self._port_is_espressif(p)} |
| 2134 | |
| 2135 | # If gpsd is installed, make sure it's running and pinned to the current |
| 2136 | # USB GPS (any puck, hot-swap aware). gps_manager auto-detection then |
| 2137 | # prefers the gpsd socket. Best-effort — falls back to direct serial. |
| 2138 | self._ensure_gpsd(esp_exclude) |
| 2139 | |
| 2140 | # Start GPS (exclude known ESP32 ports from probing) |
| 2141 | from gps_manager import GPSManager |
| 2142 | # Treat "auto" or empty string as None to trigger auto-detection |
| 2143 | if gps_port and gps_port.lower() == 'auto': |
| 2144 | gps_port = None |
| 2145 | self._gps = GPSManager( |
| 2146 | port=gps_port, exclude_ports=esp_exclude, |
| 2147 | state_file=os.path.join(self.data_dir, 'last_gps.json')) |
| 2148 | gps_ok = self._gps.start() |
| 2149 | if not gps_ok: |
| 2150 | logger.warning(f"GPS not available: {self._gps.error}. Wardriving without GPS.") |
| 2151 | |
| 2152 | # Create session |
| 2153 | self.session = WardrivingSession(self.data_dir) |
| 2154 | self._running = True |
| 2155 | self._starting = False |
| 2156 | self.error = None |
| 2157 | self.scans_completed = 0 |
| 2158 | self.bt_count = 0 |
| 2159 | self.cell_count = 0 |
| 2160 | self.zigbee_count = 0 |
| 2161 | |
| 2162 | # Save device name and session info |
| 2163 | if self.device_name: |
no test coverage detected