Main wardriving engine that coordinates WiFi scanning and GPS.
| 1330 | |
| 1331 | # Add GPS track |
| 1332 | with self._lock: |
| 1333 | try: |
| 1334 | with sqlite3.connect(self.db_path) as conn: |
| 1335 | track = conn.execute("SELECT latitude, longitude, altitude FROM gps_track ORDER BY timestamp").fetchall() |
| 1336 | if track: |
| 1337 | coords = ' '.join(f'{r[1]},{r[0]},{r[2] or 0}' for r in track) |
| 1338 | kml_parts.append(f'''<Placemark> |
| 1339 | <name>GPS Track</name> |
| 1340 | <Style><LineStyle><color>ff0088ff</color><width>3</width></LineStyle></Style> |
| 1341 | <LineString><tessellate>1</tessellate><coordinates>{coords}</coordinates></LineString> |
| 1342 | </Placemark>''') |
| 1343 | except Exception: |
| 1344 | pass |
| 1345 | |
| 1346 | kml_parts.extend(['</Document>', '</kml>']) |
| 1347 | return '\n'.join(kml_parts) |
| 1348 | |
| 1349 | # ------------------------------------------------------------------ |
| 1350 | # Survey report (self-contained, printable HTML → PDF) |
| 1351 | # ------------------------------------------------------------------ |
| 1352 | |
| 1353 | def _report_collect(self): |
| 1354 | """Gather everything a survey report needs in a single DB pass. |
| 1355 | |
| 1356 | Returns a plain dict so export_report_html stays pure formatting. |
| 1357 | Every table read is defensive: older session DBs may lack the |
| 1358 | bluetooth/cell/zigbee tables, and a report must never fail because |
| 1359 | an optional radio was never used.""" |
| 1360 | data = { |
| 1361 | 'session_id': self.session_id, |
| 1362 | 'security': {'open': 0, 'owe': 0, 'wep': 0, 'wpa': 0, 'wpa2': 0, 'wpa3': 0, 'other': 0}, |
| 1363 | 'bands': {'2.4GHz': 0, '5GHz': 0, '6GHz': 0, 'other': 0}, |
| 1364 | 'channels': {}, # channel -> count |
| 1365 | 'total_wifi': 0, |
| 1366 | 'top_networks': [], # strongest signal |
| 1367 | 'risk_networks': [], # open / WEP for the concern list |
| 1368 | 'cameras': [], |
| 1369 | 'bt_count': 0, 'cell_count': 0, 'zigbee_count': 0, |
| 1370 | 'coverage': self.get_coverage_stats(), |
| 1371 | 'gps': {'points': 0, 'lat_min': None, 'lat_max': None, |
| 1372 | 'lon_min': None, 'lon_max': None, 'distance_km': 0.0}, |
| 1373 | 'first_seen': None, 'last_seen': None, |
| 1374 | } |
| 1375 | with self._lock: |
| 1376 | try: |
| 1377 | with sqlite3.connect(self.db_path) as conn: |
| 1378 | conn.row_factory = sqlite3.Row |
| 1379 | rows = conn.execute("SELECT * FROM networks").fetchall() |
| 1380 | for row in rows: |
| 1381 | r = dict(row) |
| 1382 | data['total_wifi'] += 1 |
| 1383 | sec = (r.get('security') or '').upper() |
| 1384 | if not sec or sec in ('--',): |
| 1385 | data['security']['open'] += 1 |
| 1386 | elif 'OWE' in sec: |
| 1387 | data['security']['owe'] += 1 |
| 1388 | elif 'WEP' in sec: |
| 1389 | data['security']['wep'] += 1 |
no outgoing calls
no test coverage detected