|
1 | | -import subprocess |
2 | | -import logging |
3 | | -from io import StringIO |
4 | | -import pandas |
5 | | -import requests |
6 | | -import shutil |
7 | | -from datetime import datetime |
8 | | -from time import sleep |
9 | | -from pathlib import Path |
10 | | -from typing import Dict, Any, Optional |
11 | | - |
12 | | -NMCLI = shutil.which('nmcli') |
13 | | -if not NMCLI: |
14 | | - raise ImportError('This program relies on NetworkManager via "nmcli"') |
15 | | - |
16 | | -URL = 'https://location.services.mozilla.com/v1/geolocate?key=test' |
17 | | -NMCMD = [NMCLI, '-g', 'SSID,BSSID,FREQ,SIGNAL', 'device', 'wifi'] # Debian stretch, Ubuntu 18.04 |
18 | | -NMLEG = [NMCLI, '-t', '-f', 'SSID,BSSID,FREQ,SIGNAL', 'device', 'wifi'] # ubuntu 16.04 |
19 | | -NMSCAN = [NMCLI, 'device', 'wifi', 'rescan'] |
20 | | -HEADER = 'time lat lon accuracy NumBSSIDs' |
21 | | - |
22 | | -# %% |
23 | | - |
24 | | - |
25 | | -def logwifiloc(T: float, logfile: Path): |
26 | | - |
27 | | - if logfile: |
28 | | - logfile = Path(logfile).expanduser() |
29 | | - with logfile.open('a') as f: |
30 | | - f.write(HEADER+'\n') |
31 | | - |
32 | | - print(f'updating every {T} seconds') |
33 | | - print(HEADER) |
34 | | - |
35 | | - nm_config_check() |
36 | | - sleep(0.5) # nmcli errored for less than about 0.2 sec. |
37 | | - while True: |
38 | | - loc = get_nmcli() |
39 | | - if loc is None: |
40 | | - sleep(T) |
41 | | - continue |
42 | | - |
43 | | - stat = f'{loc["t"].isoformat(timespec="seconds")} {loc["lat"]} {loc["lng"]} {loc["accuracy"]:.1f} {loc["N"]:02d}' |
44 | | - print(stat) |
45 | | - |
46 | | - if logfile: |
47 | | - with logfile.open('a') as f: |
48 | | - f.write(stat+'\n') |
49 | | - |
50 | | - sleep(T) |
51 | | - |
52 | | - |
53 | | -def nm_config_check(): |
54 | | - # %% check that NetworkManager CLI is available and WiFi is active |
55 | | - ret = subprocess.check_output([NMCLI, '-t', 'radio', 'wifi'], universal_newlines=True, timeout=1.).strip().split(':') |
56 | | - |
57 | | - if 'enabled' not in ret and 'disabled' in ret: |
58 | | - raise OSError('must enable WiFi, perhaps via nmcli radio wifi on') |
59 | | - |
60 | | - |
61 | | -def get_nmcli() -> Optional[Dict[str, Any]]: |
62 | | - |
63 | | - ret = subprocess.check_output(NMCMD, universal_newlines=True, timeout=1.) |
64 | | - sleep(0.5) # nmcli errored for less than about 0.2 sec. |
65 | | - try: |
66 | | - subprocess.check_call(NMSCAN, timeout=1.) # takes several seconds to update, so do it now. |
67 | | - except subprocess.CalledProcessError as e: |
68 | | - logging.error(f'consider slowing scan cadence. {e}') |
69 | | - |
70 | | - dat = pandas.read_csv(StringIO(ret), sep=r'(?<!\\):', index_col=False, |
71 | | - header=0, encoding='utf8', engine='python', |
72 | | - dtype=str, usecols=[0, 1, 3], |
73 | | - names=['ssid', 'macAddress', 'signalStrength']) |
74 | | -# %% optout |
75 | | - dat = dat[~dat['ssid'].str.endswith('_nomap')] |
76 | | -# %% cleanup |
77 | | - dat['ssid'] = dat['ssid'].str.replace('nan', '') |
78 | | - dat['macAddress'] = dat['macAddress'].str.replace(r'\\:', ':') |
79 | | -# %% JSON |
80 | | - jdat = dat.to_json(orient='records') |
81 | | - jdat = '{ "wifiAccessPoints":' + jdat + '}' |
82 | | -# print(jdat) |
83 | | -# %% cloud MLS |
84 | | - try: |
85 | | - req = requests.post(URL, data=jdat) |
86 | | - if req.status_code != 200: |
87 | | - logging.error(req.text) |
88 | | - return None |
89 | | - except requests.exceptions.ConnectionError as e: |
90 | | - logging.error(f'no network connection. {e}') |
91 | | - return None |
92 | | -# %% process MLS response |
93 | | - jres = req.json() |
94 | | - loc = jres['location'] |
95 | | - loc['accuracy'] = jres['accuracy'] |
96 | | - loc['N'] = dat.shape[0] # number of BSSIDs used |
97 | | - loc['t'] = datetime.now() |
98 | | - |
99 | | - return loc |
100 | | -# %% |
101 | | - |
102 | | - |
103 | | -def csv2kml(csvfn: Path, kmlfn: Path): |
104 | | - from simplekml import Kml |
105 | | - |
106 | | - """ |
107 | | - write KML track/positions |
108 | | -
|
109 | | - t: vector of times |
110 | | - lonLatAlt: longitude, latitude, altitude or just lon,lat |
111 | | - ofn: KML filename to create |
112 | | - """ |
113 | | - |
114 | | - # lon, lat |
115 | | - dat = pandas.read_csv(csvfn, sep=' ', index_col=0, header=0) |
116 | | - |
117 | | - t = dat.index.tolist() |
118 | | - lla = dat.loc[:, ['lon', 'lat']].values |
119 | | -# %% write KML |
120 | | - """ |
121 | | - http://simplekml.readthedocs.io/en/latest/geometries.html#gxtrack |
122 | | - https://simplekml.readthedocs.io/en/latest/kml.html#id1 |
123 | | - https://simplekml.readthedocs.io/en/latest/geometries.html#simplekml.GxTrack |
124 | | - """ |
125 | | - kml = Kml(name='My Kml') |
126 | | - trk = kml.newgxtrack(name='My Track') |
127 | | - trk.newwhen(t) # list of times. MUST be format 2010-05-28T02:02:09Z |
128 | | - trk.newgxcoord(lla.tolist()) # list of lon,lat,alt, NOT ndarray! |
129 | | - |
130 | | -# just a bunch of points |
131 | | -# for i,p in enumerate(lla): # iterate over rows |
132 | | -# kml.newpoint(name=str(i), coords=[p]) |
133 | | - |
134 | | - print('writing', kmlfn) |
135 | | - kml.save(kmlfn) |
| 1 | +from .base import log_wifi_loc |
0 commit comments