Skip to content

Commit 61d3804

Browse files
committed
src/ layout, functionalize for better testing and reuse
1 parent 7abee9a commit 61d3804

File tree

21 files changed

+189
-188
lines changed

21 files changed

+189
-188
lines changed

.github/workflows/ci.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,26 @@ jobs:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- uses: actions/checkout@v2
14-
- uses: actions/setup-python@v1
14+
- uses: actions/setup-python@v2
1515
with:
1616
python-version: '3.x'
17+
1718
- run: pip install .[tests,lint]
19+
1820
- run: flake8
19-
- run: mypy .
21+
- run: mypy . src
22+
2023
- run: pytest
21-
working-directory: tests
24+
2225

2326
windows:
2427
runs-on: windows-latest
2528
steps:
2629
- uses: actions/checkout@v2
27-
- uses: actions/setup-python@v1
30+
- uses: actions/setup-python@v2
2831
with:
2932
python-version: '3.x'
33+
3034
- run: pip install .[tests]
35+
3136
- run: pytest
32-
working-directory: tests

mypy.ini renamed to .mypy.ini

File renamed without changes.

csv2kml.py

Lines changed: 0 additions & 17 deletions
This file was deleted.

mozloc/base.py

Lines changed: 0 additions & 35 deletions
This file was deleted.

mozloc/config.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ requires = ["setuptools", "wheel"]
33

44
[tool.black]
55
line-length = 132
6+
7+
[tool.pytest.ini_options]
8+
addopts = "-ra -v"

pytest.ini

Lines changed: 0 additions & 2 deletions
This file was deleted.

setup.cfg

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = mozloc
3-
version = 1.0.0
3+
version = 1.1.0
44
author = Michael Hirsch, Ph.D.
55
author_email = [email protected]
66
url = https://github.com/scivision/mozilla-location-wifi
@@ -9,13 +9,12 @@ keywords =
99
wifi
1010
geolocation
1111
classifiers =
12-
Development Status :: 4 - Beta
12+
Development Status :: 5 - Production/Stable
1313
Environment :: Console
1414
Intended Audience :: Information Technology
1515
Intended Audience :: System Administrators
1616
Operating System :: POSIX :: Linux
1717
Operating System :: Microsoft :: Windows
18-
Programming Language :: Python :: 3.6
1918
Programming Language :: Python :: 3.7
2019
Programming Language :: Python :: 3.8
2120
Programming Language :: Python :: 3.9
@@ -27,14 +26,16 @@ long_description = file: README.md
2726
long_description_content_type = text/markdown
2827

2928
[options]
30-
python_requires = >= 3.6
29+
python_requires = >= 3.7
3130
packages = find:
32-
scripts =
33-
csv2kml.py
34-
MozLoc.py
3531
install_requires =
3632
pandas
3733
requests
34+
package_dir=
35+
=src
36+
37+
[options.packages.find]
38+
where=src
3839

3940
[options.extras_require]
4041
tests =
@@ -47,4 +48,5 @@ io =
4748

4849
[options.entry_points]
4950
console_scripts =
50-
MozLoc = MozLoc:main
51+
MozLoc = mozloc.__main__:mozilla_location
52+
mozloc_csv2kml = mozloc.__main__:csv2kml
File renamed without changes.

MozLoc.py renamed to src/mozloc/__main__.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
88
Don't abuse the API or you'll get banned (excessive polling rate)
99
"""
10-
from mozloc import log_wifi_loc
10+
from .base import log_wifi_loc
1111
from argparse import ArgumentParser
1212

1313

14-
def main():
14+
def mozilla_location():
1515
"""
1616
output: lat lon [deg] accuracy [m]
1717
"""
@@ -20,10 +20,23 @@ def main():
2020
p.add_argument(
2121
"-T", "--cadence", help="how often to ping [sec]. Some laptops cannot go faster than 30 sec.", default=60, type=float
2222
)
23+
p.add_argument(
24+
"-url",
25+
help="Mozilla location services URL--don't use this default test key",
26+
default="https://location.services.mozilla.com/v1/geolocate?key=test",
27+
)
2328
p = p.parse_args()
2429

25-
log_wifi_loc(p.cadence, p.logfile)
30+
log_wifi_loc(p.cadence, p.url, p.logfile)
31+
2632

33+
def csv2kml():
34+
"""convert logged positions to KML"""
35+
from .utils import csv2kml
36+
37+
p = ArgumentParser()
38+
p.add_argument("logfn", help="csv logfile to read")
39+
p.add_argument("kmlfn", help="kml filename to write")
40+
p = p.parse_args()
2741

28-
if __name__ == "__main__":
29-
main()
42+
csv2kml(p.logfn, p.kmlfn)

src/mozloc/base.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from time import sleep
2+
from pathlib import Path
3+
import logging
4+
5+
from .modules import get_cli, cli_config_check
6+
from .web import get_loc_mozilla
7+
8+
HEADER = "time lat lon accuracy NumBSSIDs"
9+
10+
11+
def log_wifi_loc(cadence_sec: float, mozilla_url: str, logfile: Path = None):
12+
13+
if logfile:
14+
logfile = Path(logfile).expanduser()
15+
with logfile.open("a") as f:
16+
f.write(HEADER + "\n")
17+
18+
print(f"updating every {cadence_sec} seconds")
19+
print(HEADER)
20+
21+
if not cli_config_check():
22+
raise ConnectionError("Could not connect to WiFi hardware")
23+
# nmcli errored for less than about 0.2 sec.
24+
sleep(0.5)
25+
while True:
26+
dat = get_cli()
27+
if len(dat) < 2:
28+
logging.warning(f"cannot locate since at least 2 BSSIDs required\n{dat}")
29+
sleep(cadence_sec)
30+
continue
31+
32+
loc = get_loc_mozilla(dat, mozilla_url)
33+
if loc is None:
34+
logging.warning(f"Did not get location from {len(dat)} BSSIDs")
35+
sleep(cadence_sec)
36+
continue
37+
38+
stat = f'{loc["t"].isoformat(timespec="seconds")} {loc["lat"]} {loc["lng"]} {loc["accuracy"]:.1f} {loc["N"]:02d}'
39+
print(stat)
40+
41+
if logfile:
42+
with logfile.open("a") as f:
43+
f.write(stat + "\n")
44+
45+
sleep(cadence_sec)
File renamed without changes.
Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
""" Network Manager CLI (nmcli) functions """
22
import subprocess
3-
import typing
43
import logging
54
import shutil
65
import pandas
6+
import typing as T
77
from io import StringIO
88
from time import sleep
9-
import requests
10-
from datetime import datetime
11-
12-
from .config import URL
139

1410
NMCLI = shutil.which("nmcli")
1511
if not NMCLI:
@@ -20,22 +16,36 @@
2016
NMSCAN = [NMCLI, "device", "wifi", "rescan"]
2117

2218

23-
def cli_config_check():
19+
def cli_config_check() -> bool:
2420
# %% check that NetworkManager CLI is available and WiFi is active
25-
ret = subprocess.check_output([NMCLI, "-t", "radio", "wifi"], universal_newlines=True, timeout=1.0).strip().split(":")
21+
ret = subprocess.run([NMCLI, "-t", "radio", "wifi"], stdout=subprocess.PIPE, text=True, timeout=2)
22+
23+
if ret.returncode != 0:
24+
return False
2625

27-
if "enabled" not in ret and "disabled" in ret:
28-
raise ConnectionError("must enable WiFi, perhaps via nmcli radio wifi on")
26+
stdout = ret.stdout.strip().split(":")
27+
if "enabled" in stdout:
28+
return True
2929

30+
if "disabled" in stdout:
31+
logging.error(
32+
"""must enable WiFi, perhaps via:
33+
nmcli radio wifi on"""
34+
)
35+
return False
3036

31-
def get_cli() -> typing.Dict[str, typing.Any]:
37+
logging.error("could not determine WiFi state.")
38+
return False
39+
40+
41+
def get_cli() -> T.List[T.Dict[str, T.Any]]:
3242

3343
ret = subprocess.run(NMCMD, timeout=1.0)
3444
if ret.returncode != 0:
35-
raise ConnectionError(f"could not connect with NetworkManager for WiFi")
45+
raise ConnectionError("could not connect with NetworkManager for WiFi")
3646
sleep(0.5) # nmcli errored for less than about 0.2 sec.
3747
# takes several seconds to update, so do it now.
38-
ret = subprocess.run(NMSCAN, timeout=1.0, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
48+
ret = subprocess.run(NMSCAN, timeout=1.0, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
3949
if ret.returncode != 0:
4050
logging.error(f"consider slowing scan cadence. {ret.stderr}")
4151

@@ -52,30 +62,9 @@ def get_cli() -> typing.Dict[str, typing.Any]:
5262
)
5363
# %% optout
5464
dat = dat[~dat["ssid"].str.endswith("_nomap")]
55-
if dat.shape[0] < 2:
56-
logging.warning("cannot locate since at least 2 BSSIDs required")
57-
return None
65+
5866
# %% cleanup
5967
dat["ssid"] = dat["ssid"].str.replace("nan", "")
6068
dat["macAddress"] = dat["macAddress"].str.replace(r"\\:", ":")
61-
# %% JSON
62-
jdat = dat.to_json(orient="records")
63-
jdat = '{ "wifiAccessPoints":' + jdat + "}"
64-
logging.debug(jdat)
65-
# %% cloud MLS
66-
try:
67-
req = requests.post(URL, data=jdat)
68-
if req.status_code != 200:
69-
logging.error(req.text)
70-
return None
71-
except requests.exceptions.ConnectionError as e:
72-
logging.error(f"no network connection. {e}")
73-
return None
74-
# %% process MLS response
75-
jres = req.json()
76-
loc = jres["location"]
77-
loc["accuracy"] = jres["accuracy"]
78-
loc["N"] = dat.shape[0] # number of BSSIDs used
79-
loc["t"] = datetime.now()
8069

81-
return loc
70+
return dat

0 commit comments

Comments
 (0)