Skip to content

Commit

Permalink
breakup into parse_signal to allow offline files
Browse files Browse the repository at this point in the history
correct netsh windows parsing
  • Loading branch information
scivision committed Oct 18, 2021
1 parent ec3d10a commit e41ba52
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 41 deletions.
22 changes: 10 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Mozilla Location Services from Python

![Actions Status](https://github.com/scivision/mozilla-location-wifi/workflows/ci/badge.svg)
[![Python versions (PyPI)](https://img.shields.io/pypi/pyversions/mozloc.svg)](https://pypi.python.org/pypi/mozloc)
![Actions Status](https://github.com/scivision/mozilla-location-wifi/workflows/ci/badge.svg))
[![PyPi Download stats](http://pepy.tech/badge/mozloc)](http://pepy.tech/project/mozloc)

Uses command line access to WiFi information in a short, simple Mozilla Location Services with Wifi from Python.
Expand All @@ -17,14 +16,14 @@ Let us know if you're interested.

## Install


Get latest release

```sh
pip install mozloc
```

or for latest development version
or for latest development version:

```sh
git clone https://github.com/scivision/mozilla-location-wifi/
pip install -e mozilla-location-wifi/
Expand Down Expand Up @@ -79,26 +78,25 @@ Would like to add Bluetooth beacons.
* [Alternative using Skyhook and geoclue](https://github.com/scivision/python-geoclue)
* [Raspberry Pi NetworkManager](https://raspberrypi.stackexchange.com/a/73816)

### Windows

To print verbose information about nearby WiFi:

```posh
netsh wlan show networks mode=bssid
```
* Windows: `netsh wlan show networks mode=bssid`
* MacOS: `airport -s`
* Linux: `nmcli dev wifi list`

### Raspberry Pi 3 / 4 / Zero W

Debian comes without NetworkManager by default.
Be careful as you lose Wifi password etc. by this procedure
Thus we recommend using Ubuntu or similar on the Raspberry Pi with this program.

If you do use Debian with the procedure below, you lose Wifi password and stored WiFi networks.

1. Install network manager and remove the old
```sh
apt install network-manager
apt purge dhcpcd5
```
reboot
2. upon reboot, try
2. Reboot and try
```sh
nmcli dev wifi list
```
Expand Down
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = mozloc
version = 1.2.0
version = 1.3.0
author = Michael Hirsch, Ph.D.
author_email = scivision@users.noreply.github.com
url = https://github.com/scivision/mozilla-location-wifi
Expand Down Expand Up @@ -42,6 +42,7 @@ tests =
lint =
flake8
mypy
types-requests
io =
simplekml

Expand Down
2 changes: 1 addition & 1 deletion src/mozloc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .base import log_wifi_loc
from .modules import get_signal, cli_config_check
from .modules import get_signal, parse_signal, cli_config_check
9 changes: 6 additions & 3 deletions src/mozloc/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env python
"""
https://mozilla.github.io/ichnaea/api/geolocate.html
https://ichnaea.readthedocs.io/en/latest/api/geolocate.html
Expand All @@ -8,7 +7,7 @@
Don't abuse the API or you'll get banned (excessive polling rate)
"""

from .base import log_wifi_loc
from .base import log_wifi_loc, process_file
from .modules import get_signal

import pandas
Expand Down Expand Up @@ -37,9 +36,13 @@ def mozilla_location():
help="Mozilla location services URL--don't use this default test key",
default="https://location.services.mozilla.com/v1/geolocate?key=test",
)
p.add_argument("-i", "--infile", help="use raw text saved from command line")
p = p.parse_args()

log_wifi_loc(p.cadence, p.url, p.logfile)
if p.infile:
process_file(p.infile, mozilla_url=p.url)
else:
log_wifi_loc(cadence_sec=p.cadence, mozilla_url=p.url, logfile=p.logfile)


def csv2kml():
Expand Down
10 changes: 7 additions & 3 deletions src/mozloc/airport.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations
import typing as T
import shutil
import io
import logging
import subprocess
import re
Expand Down Expand Up @@ -31,17 +30,22 @@ def cli_config_check() -> bool:
return False


def get_signal() -> list[dict[str, T.Any]]:
def get_signal() -> str:

ret = subprocess.run([EXE, "-s"], timeout=30.0, stdout=subprocess.PIPE, text=True)

if ret.returncode != 0:
logging.error("consider slowing scan cadence.")

return ret.stdout


def parse_signal(raw: str) -> list[dict[str, T.Any]]:

pat = re.compile(r"\s*([0-9a-zA-Z\-\.]+)\s+([0-9a-f]{2}(?::[0-9a-f]{2}){5})\s+(-\d{2,3})")
dat: list[dict[str, str]] = []

for line in io.StringIO(ret.stdout):
for line in raw.split("\n"):
mat = pat.match(line)
if mat:
ssid = mat.group(1)
Expand Down
23 changes: 20 additions & 3 deletions src/mozloc/base.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
from time import sleep
from pathlib import Path
import logging
from pprint import pprint

from .modules import get_signal, cli_config_check
from .modules import get_signal, parse_signal, cli_config_check
from .web import get_loc_mozilla

HEADER = "time lat lon accuracy NumBSSIDs"


def process_file(file: Path, mozilla_url: str):
"""
process raw data captured from NetSH etc. by user previously to a file
"""

raw = Path(file).expanduser().read_text()
dat = parse_signal(raw)
pprint(dat)
loc = get_loc_mozilla(dat, url=mozilla_url)

stat = f'{loc["t"].isoformat(timespec="seconds")} {loc["lat"]} {loc["lng"]} {loc["accuracy"]:.1f} {loc["N"]:02d}'

print(stat)


def log_wifi_loc(cadence_sec: float, mozilla_url: str, logfile: Path = None):

if logfile:
Expand All @@ -23,12 +39,13 @@ def log_wifi_loc(cadence_sec: float, mozilla_url: str, logfile: Path = None):
# nmcli errored for less than about 0.2 sec.
sleep(0.5)
while True:
dat = get_signal()
raw = get_signal()
dat = parse_signal(raw)
if len(dat) < 2:
logging.warning(f"cannot locate since at least 2 BSSIDs required\n{dat}")
sleep(cadence_sec)
continue
print(dat)
logging.debug(dat)

loc = get_loc_mozilla(dat, mozilla_url)
if loc is None:
Expand Down
6 changes: 3 additions & 3 deletions src/mozloc/modules.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import sys

if sys.platform == "win32":
from .netsh import cli_config_check, get_signal
from .netsh import cli_config_check, get_signal, parse_signal
elif sys.platform == "linux":
from .netman import cli_config_check, get_signal
from .netman import cli_config_check, get_signal, parse_signal
elif sys.platform == "darwin":
from .airport import cli_config_check, get_signal
from .airport import cli_config_check, get_signal, parse_signal
else:
raise ImportError(f"MozLoc doesn't work with platform {sys.platform}")
9 changes: 7 additions & 2 deletions src/mozloc/netman.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def cli_config_check() -> bool:
return False


def get_signal() -> list[dict[str, T.Any]]:
def get_signal() -> str:

ret = subprocess.run(NMCMD, timeout=1.0)
if ret.returncode != 0:
Expand All @@ -51,8 +51,13 @@ def get_signal() -> list[dict[str, T.Any]]:
if ret.returncode != 0:
logging.error("consider slowing scan cadence.")

return ret.stdout


def parse_signal(raw: str) -> list[dict[str, T.Any]]:

dat = pandas.read_csv(
io.StringIO(ret.stdout),
io.StringIO(raw),
sep=r"(?<!\\):",
index_col=False,
header=0,
Expand Down
39 changes: 27 additions & 12 deletions src/mozloc/netsh.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
import typing as T
import subprocess
import logging
import shutil
import io
from math import log10
import shutil

CLI = shutil.which("netsh")
if not CLI:
Expand All @@ -29,14 +28,24 @@ def cli_config_check() -> bool:
return False


def get_signal() -> list[dict[str, T.Any]]:
""" get signal strength using CLI """
def get_signal() -> str:
"""
get signal strength using CLI
returns dict of data parsed from CLI
"""
ret = subprocess.run(CMD, timeout=1.0, stdout=subprocess.PIPE, text=True)
if ret.returncode != 0:
logging.error("consider slowing scan cadence.")

return ret.stdout


def parse_signal(raw: str) -> list[dict[str, T.Any]]:

dat: list[dict[str, str]] = []
out = io.StringIO(ret.stdout)
out = io.StringIO(raw)

for line in out:
d: dict[str, str] = {}
if not line.startswith("SSID"):
Expand All @@ -45,6 +54,7 @@ def get_signal() -> list[dict[str, T.Any]]:
# optout
if ssid.endswith("_nomap"):
continue

# find BSSID MAC address
for line in out:
if not line[4:9] == "BSSID":
Expand All @@ -62,14 +72,20 @@ def get_signal() -> list[dict[str, T.Any]]:
d["ssid"] = ssid
dat.append(d)
d = {}
# need break at each for level
break
break

return dat


def signal_percent_to_dbm(percent: int) -> int:
"""arbitrary conversion factor from Windows WiFi signal % to dBm
assumes 100% is -30 dBm
"""
arbitrary conversion factor from Windows WiFi signal % to dBm
assumes signal percents map to dBm like:
* 100% is -30 dBm
* 0% is -100 dBm
Parameters
----------
Expand All @@ -81,9 +97,8 @@ def signal_percent_to_dbm(percent: int) -> int:
meas_dBm: int
truncate to nearest integer because of uncertainties
"""
REF = -30 # dBm
ref_mW = 10 ** (REF / 10) / 1000
meas_mW = max(ref_mW * percent / 100, 1e-7)
meas_dBm = 10 * log10(meas_mW) + 30

return int(meas_dBm)
REF = -100 # dBm
assert 0 <= percent <= 100, "percent must be 0...100"

return int(REF + percent * 7 / 10)
2 changes: 1 addition & 1 deletion src/mozloc/tests/test_netman.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
def test_nm_loc():

mozloc = pytest.importorskip("mozloc")
loc = mozloc.get_signal()
loc = mozloc.parse_signal(mozloc.get_signal())

assert isinstance(loc, list)
assert isinstance(loc[0], dict)
Expand Down

0 comments on commit e41ba52

Please sign in to comment.