Skip to content

Commit e2beba1

Browse files
committed
modularize
1 parent 4ff399c commit e2beba1

File tree

14 files changed

+284
-176
lines changed

14 files changed

+284
-176
lines changed

.flake8

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
[flake8]
22
max-line-length = 132
33
exclude = .git,__pycache__,.eggs/,doc/,docs/,build/,dist/,archive/
4+
per-file-ignores =
5+
__init__.py:F401

CODE_OF_CONDUCT.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Contributor Covenant Code of Conduct
2+
3+
## Our Pledge
4+
5+
In the interest of fostering an open and welcoming environment, we as
6+
contributors and maintainers pledge to making participation in our project and
7+
our community a harassment-free experience for everyone, regardless of age, body
8+
size, disability, ethnicity, sex characteristics, gender identity and expression,
9+
level of experience, education, socio-economic status, nationality, personal
10+
appearance, race, religion, or sexual identity and orientation.
11+
12+
## Our Standards
13+
14+
Examples of behavior that contributes to creating a positive environment
15+
include:
16+
17+
* Using welcoming and inclusive language
18+
* Being respectful of differing viewpoints and experiences
19+
* Gracefully accepting constructive criticism
20+
* Focusing on what is best for the community
21+
* Showing empathy towards other community members
22+
23+
Examples of unacceptable behavior by participants include:
24+
25+
* The use of sexualized language or imagery and unwelcome sexual attention or
26+
advances
27+
* Trolling, insulting/derogatory comments, and personal or political attacks
28+
* Public or private harassment
29+
* Publishing others' private information, such as a physical or electronic
30+
address, without explicit permission
31+
* Other conduct which could reasonably be considered inappropriate in a
32+
professional setting
33+
34+
## Our Responsibilities
35+
36+
Project maintainers are responsible for clarifying the standards of acceptable
37+
behavior and are expected to take appropriate and fair corrective action in
38+
response to any instances of unacceptable behavior.
39+
40+
Project maintainers have the right and responsibility to remove, edit, or
41+
reject comments, commits, code, wiki edits, issues, and other contributions
42+
that are not aligned to this Code of Conduct, or to ban temporarily or
43+
permanently any contributor for other behaviors that they deem inappropriate,
44+
threatening, offensive, or harmful.
45+
46+
## Scope
47+
48+
This Code of Conduct applies both within project spaces and in public spaces
49+
when an individual is representing the project or its community. Examples of
50+
representing a project or community include using an official project e-mail
51+
address, posting via an official social media account, or acting as an appointed
52+
representative at an online or offline event. Representation of a project may be
53+
further defined and clarified by project maintainers.
54+
55+
## Enforcement
56+
57+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
58+
reported by contacting the project team at info@scivision.dev. All
59+
complaints will be reviewed and investigated and will result in a response that
60+
is deemed necessary and appropriate to the circumstances. The project team is
61+
obligated to maintain confidentiality with regard to the reporter of an incident.
62+
Further details of specific enforcement policies may be posted separately.
63+
64+
Project maintainers who do not follow or enforce the Code of Conduct in good
65+
faith may face temporary or permanent repercussions as determined by other
66+
members of the project's leadership.
67+
68+
## Attribution
69+
70+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71+
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72+
73+
[homepage]: https://www.contributor-covenant.org
74+
75+
For answers to common questions about this code of conduct, see
76+
https://www.contributor-covenant.org/faq

MozLoc.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
88
Uses ``nmcli`` from Linux only. Could be extended to other tools and OS.
99
"""
10-
from mozloc import logwifiloc
10+
from mozloc import log_wifi_loc
1111
from argparse import ArgumentParser
1212

1313

@@ -16,13 +16,14 @@ def main():
1616
output: lat lon [deg] accuracy [m]
1717
"""
1818
p = ArgumentParser()
19-
p.add_argument('logfile', help='logfile to append location to', nargs='?')
20-
p.add_argument('-T', '--cadence', help='how often to ping [sec]. Some laptops cannot go faster than 30 sec.',
21-
default=60, type=float)
19+
p.add_argument("logfile", help="logfile to append location to", nargs="?")
20+
p.add_argument(
21+
"-T", "--cadence", help="how often to ping [sec]. Some laptops cannot go faster than 30 sec.", default=60, type=float
22+
)
2223
p = p.parse_args()
2324

24-
logwifiloc(p.cadence, p.logfile)
25+
log_wifi_loc(p.cadence, p.logfile)
2526

2627

27-
if __name__ == '__main__':
28+
if __name__ == "__main__":
2829
main()

csv2kml.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
#!/usr/bin/env python
22
"""convert logged positions to KML"""
3-
from mozloc import csv2kml
3+
from mozloc.utils import csv2kml
44
from argparse import ArgumentParser
55

66

77
def main():
88
p = ArgumentParser()
9-
p.add_argument('logfn', help='csv logfile to read')
10-
p.add_argument('kmlfn', help='kml filename to write')
9+
p.add_argument("logfn", help="csv logfile to read")
10+
p.add_argument("kmlfn", help="kml filename to write")
1111
p = p.parse_args()
1212

1313
csv2kml(p.logfn, p.kmlfn)
1414

1515

16-
if __name__ == '__main__':
16+
if __name__ == "__main__":
1717
main()

mozloc/__init__.py

Lines changed: 1 addition & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1 @@
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

mozloc/base.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from time import sleep
2+
from pathlib import Path
3+
4+
from .netman import nm_config_check, get_nmcli
5+
6+
HEADER = "time lat lon accuracy NumBSSIDs"
7+
8+
9+
def log_wifi_loc(T: float, logfile: Path):
10+
11+
if logfile:
12+
logfile = Path(logfile).expanduser()
13+
with logfile.open("a") as f:
14+
f.write(HEADER + "\n")
15+
16+
print(f"updating every {T} seconds")
17+
print(HEADER)
18+
19+
nm_config_check()
20+
sleep(0.5) # nmcli errored for less than about 0.2 sec.
21+
while True:
22+
loc = get_nmcli()
23+
if loc is None:
24+
sleep(T)
25+
continue
26+
27+
stat = f'{loc["t"].isoformat(timespec="seconds")} {loc["lat"]} {loc["lng"]} {loc["accuracy"]:.1f} {loc["N"]:02d}'
28+
print(stat)
29+
30+
if logfile:
31+
with logfile.open("a") as f:
32+
f.write(stat + "\n")
33+
34+
sleep(T)

mozloc/netman.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
""" Network Manager CLI (nmcli) functions """
2+
import subprocess
3+
import typing
4+
import logging
5+
import shutil
6+
import pandas
7+
from io import StringIO
8+
from time import sleep
9+
import requests
10+
from datetime import datetime
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+
21+
22+
def nm_config_check():
23+
# %% check that NetworkManager CLI is available and WiFi is active
24+
ret = subprocess.check_output([NMCLI, "-t", "radio", "wifi"], universal_newlines=True, timeout=1.0).strip().split(":")
25+
26+
if "enabled" not in ret and "disabled" in ret:
27+
raise ConnectionError("must enable WiFi, perhaps via nmcli radio wifi on")
28+
29+
30+
def get_nmcli() -> typing.Dict[str, typing.Any]:
31+
32+
ret = subprocess.run(NMCMD, timeout=1.0)
33+
if ret.returncode != 0:
34+
raise ConnectionError(f"could not connect with NetworkManager for WiFi")
35+
sleep(0.5) # nmcli errored for less than about 0.2 sec.
36+
# takes several seconds to update, so do it now.
37+
ret = subprocess.run(NMSCAN, timeout=1.0, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
38+
if ret.returncode != 0:
39+
logging.error(f"consider slowing scan cadence. {ret.stderr}")
40+
41+
dat = pandas.read_csv(
42+
StringIO(ret.stdout),
43+
sep=r"(?<!\\):",
44+
index_col=False,
45+
header=0,
46+
encoding="utf8",
47+
engine="python",
48+
dtype=str,
49+
usecols=[0, 1, 3],
50+
names=["ssid", "macAddress", "signalStrength"],
51+
)
52+
# %% optout
53+
dat = dat[~dat["ssid"].str.endswith("_nomap")]
54+
# %% cleanup
55+
dat["ssid"] = dat["ssid"].str.replace("nan", "")
56+
dat["macAddress"] = dat["macAddress"].str.replace(r"\\:", ":")
57+
# %% JSON
58+
jdat = dat.to_json(orient="records")
59+
jdat = '{ "wifiAccessPoints":' + jdat + "}"
60+
logging.debug(jdat)
61+
# %% cloud MLS
62+
try:
63+
req = requests.post(URL, data=jdat)
64+
if req.status_code != 200:
65+
logging.error(req.text)
66+
return None
67+
except requests.exceptions.ConnectionError as e:
68+
logging.error(f"no network connection. {e}")
69+
return None
70+
# %% process MLS response
71+
jres = req.json()
72+
loc = jres["location"]
73+
loc["accuracy"] = jres["accuracy"]
74+
loc["N"] = dat.shape[0] # number of BSSIDs used
75+
loc["t"] = datetime.now()
76+
77+
return loc

0 commit comments

Comments
 (0)