Export session to WiGLE CSV format string including WiFi, BT, and cell. Rows whose position was estimated via backfill_gps_from_track (gps_backfilled = 1) are excluded — they are interpolated coordinates, not real observations, and must not be submitted to WiGLE.
(self, device_name='Ragnar')
| 975 | with self._lock: |
| 976 | try: |
| 977 | with sqlite3.connect(self.db_path) as conn: |
| 978 | conn.row_factory = sqlite3.Row |
| 979 | rows = conn.execute( |
| 980 | "SELECT * FROM cell_towers ORDER BY signal_dbm DESC" |
| 981 | ).fetchall() |
| 982 | return [dict(r) for r in rows] |
| 983 | except Exception: |
| 984 | return [] |
| 985 | |
| 986 | def get_zigbee_devices(self): |
| 987 | """Get all discovered Zigbee / 802.15.4 devices.""" |
| 988 | with self._lock: |
| 989 | try: |
| 990 | with sqlite3.connect(self.db_path) as conn: |
| 991 | conn.row_factory = sqlite3.Row |
| 992 | rows = conn.execute( |
| 993 | "SELECT * FROM zigbee_devices ORDER BY rssi DESC" |
| 994 | ).fetchall() |
| 995 | return [dict(r) for r in rows] |
| 996 | except Exception: |
| 997 | return [] |
| 998 | |
| 999 | def get_gps_track(self): |
| 1000 | """Return GPS track as list of [lat, lon] points.""" |
| 1001 | with self._lock: |
| 1002 | try: |
| 1003 | with sqlite3.connect(self.db_path) as conn: |
| 1004 | rows = conn.execute( |
| 1005 | "SELECT latitude, longitude FROM gps_track ORDER BY timestamp" |
| 1006 | ).fetchall() |
| 1007 | return [[r[0], r[1]] for r in rows] |
| 1008 | except Exception: |
| 1009 | return [] |
| 1010 | |
| 1011 | def backfill_gps_from_track(self, max_gap_seconds=300): |
| 1012 | """Fill missing positions on networks / BT / cell rows by interpolating |
| 1013 | each row's first_seen timestamp against the gps_track table. |
| 1014 | |
| 1015 | Handles the typical wardriving GPS gap: device discovered during the |
| 1016 | first ~minutes before fix lock, or during a brief reception dropout. |
| 1017 | Uses linear interpolation when bracketing trackpoints exist within |
| 1018 | max_gap_seconds on both sides; falls back to the nearest single |
| 1019 | trackpoint when only one side is close enough. |
| 1020 | |
| 1021 | Returns dict {wifi, bluetooth, cells} of row counts backfilled. |
| 1022 | Invalidates the stats cache so subsequent reads reflect new positions. |
| 1023 | """ |
| 1024 | import bisect |
| 1025 | result = {'wifi': 0, 'bluetooth': 0, 'cells': 0, 'trackpoints': 0} |
| 1026 | |
| 1027 | def _parse_iso(s): |
| 1028 | if not s: |
| 1029 | return None |
| 1030 | try: |
| 1031 | return datetime.fromisoformat(s.replace('Z', '+00:00')).timestamp() |
| 1032 | except Exception: |
| 1033 | return None |
| 1034 |
no test coverage detected