diff options
Diffstat (limited to 'examples/geolocation/README.org')
| -rw-r--r-- | examples/geolocation/README.org | 164 |
1 files changed, 164 insertions, 0 deletions
diff --git a/examples/geolocation/README.org b/examples/geolocation/README.org new file mode 100644 index 0000000..2a5462e --- /dev/null +++ b/examples/geolocation/README.org @@ -0,0 +1,164 @@ +#+TITLE: wttrin geolocation command adapters + +These are example programs for ~wttrin-geolocation-command~. wttrin can run an +external command to find your location with far better accuracy than IP +geolocation (which only finds your network's exit point — wrong on a VPN or a +cellular hotspot). The command scans nearby WiFi access points, looks them up, +and prints coordinates; wttrin then queries wttr.in by those coordinates. + +These are *examples*, not part of the installed package. Copy one, adapt it to +your system, and point the variable at it: + +#+begin_src emacs-lisp + (setq wttrin-geolocation-command "/path/to/google-geolocate.py") +#+end_src + +* The contract + +A command prints one JSON object to standard output and exits zero: + +#+begin_src json + {"lat": 41.3222, "lng": -71.8113, "accuracy_m": 25.0, "address": "Westerly, Rhode Island, USA"} +#+end_src + +- ~lat~ and ~lng~ (numbers) are required. wttrin fetches weather for them. +- ~address~ (or ~label~) is optional. When present, wttrin shows it on a + "Location:" line in the weather buffer, so the resolved place is readable even + though the weather was fetched by raw coordinates. +- Any other keys are ignored. + +On any failure the command should exit non-zero (and may print a message to +standard error). wttrin then falls back to the IP provider named by +~wttrin-geolocation-provider~, so geolocation still works. + +* The adapters + +** google-geolocate.py +Uses the Google Geolocation API. Documented, supported, accurate, broad +coverage. Needs an API key (see below). Recommended starting point. + +** apple-wps.py +Uses Apple's WiFi positioning service. Keyless and accurate, but it queries an +*undocumented* Apple endpoint via a reverse-engineered protocol, which may be +contrary to Apple's terms of service. Read the caveat at the top of the file and +decide for yourself. Provided as an example only. + +* Requirements + +- Python 3 (standard library only — no packages to install). +- A WiFi scanner. Both examples use ~nmcli~ (NetworkManager), which is common on + Linux. On a system without NetworkManager, replace the ~scan_wifi~ function + with your platform's scanner; that is the only platform-specific part. See + "Adapting the WiFi scan" below for systemd-networkd, iwd, and macOS. +- Network access. Both also do a best-effort reverse-geocode via OpenStreetMap + Nominatim to fill in ~address~; if that call fails the coordinates are still + returned, just without an address. + +* Adapting the WiFi scan + +~scan_wifi~ needs, for each nearby access point, its BSSID (MAC address) and a +signal strength. nmcli gives a 0-100 quality (the examples convert it to dBm); +the sources below give dBm directly. Swap in whichever your system has and adjust +the parsing to match the sample line. + +** systemd-networkd (via wpa_supplicant) +networkd handles addressing, not WiFi scanning — the scan comes from +wpa_supplicant. No root needed when wpa_supplicant is already running. + +#+begin_src sh + wpa_cli -i wlan0 scan + wpa_cli -i wlan0 scan_results +#+end_src + +Output columns are =BSSID frequency signal(dBm) flags SSID= (tab-separated): + +#+begin_example + 00:11:22:33:44:55 2437 -65 [WPA2-PSK-CCMP][ESS] MyNetwork +#+end_example + +Take field 1 (BSSID) and field 3 (signal in dBm). + +** iwd +iwd has no scriptable way to list per-AP BSSIDs (=iwctl station <iface> +get-networks= shows SSIDs and aggregated signal, not BSSIDs). On an iwd system, +use =iw= below — it works regardless of the network manager. + +** Any Linux (iw, the low-level tool) +=iw= talks to the kernel directly (nl80211), so it works under NetworkManager, +iwd, wpa_supplicant, or networkd. Triggering a scan needs root. + +#+begin_src sh + sudo iw dev wlan0 scan | grep -E "^BSS|signal:" +#+end_src + +#+begin_example + BSS 00:11:22:33:44:55(on wlan0) + signal: -65.00 dBm +#+end_example + +Pair each =BSS <bssid>= line with the =signal: <dBm>= line that follows it. + +** macOS +macOS restricts WiFi scanning. The old =airport -s= was removed in macOS 14, and +=system_profiler SPAirPortDataType= redacts BSSIDs unless the calling process has +Location Services permission. The reliable path today is a small CoreWLAN helper +(Swift/PyObjC) that, with Location Services granted, reads =CWInterface +scanForNetworksWithName= and returns each network's =bssid= and =rssiValue= +(dBm). Treat macOS as needing that helper rather than a one-liner. + +* Setting up a Google API key + +1. Create a project in the Google Cloud console (https://console.cloud.google.com/). +2. Enable the "Geolocation API" for that project. +3. Create an API key (APIs & Services -> Credentials -> Create credentials -> API key). + Restrict it to the Geolocation API. +4. Note Google's pricing: the Geolocation API is a paid API with a monthly free + allowance. Review current terms before relying on it. +5. Make the key available to the command. The script checks the environment + first, then ~~/.authinfo.gpg~; use whichever you prefer. + +** Option A: an environment variable + +#+begin_src sh + export GOOGLE_GEOLOCATION_API_KEY="your-key-here" +#+end_src + +Put it somewhere your Emacs inherits it (a login shell profile). The script reads +~GOOGLE_GEOLOCATION_API_KEY~ and never stores the key. + +** Option B: ~/.authinfo.gpg (encrypted, recommended) + +Keep the key in the same encrypted store Emacs's auth-source uses. The script +reads it whenever the environment variable is unset. + +1. Open ~~/.authinfo.gpg~ in Emacs. It decrypts transparently for editing. (If + the file doesn't exist, create it; Emacs encrypts it on save once your GnuPG + key is set up.) +2. Add one netrc-style line: + +#+begin_src authinfo + machine googleapis.com login geolocation password YOUR-KEY-HERE +#+end_src + +3. Save the buffer. Emacs re-encrypts the file. +4. Confirm gpg can read it without prompting (gpg-agent caches the passphrase): + +#+begin_src sh + gpg --quiet --decrypt ~/.authinfo.gpg | grep googleapis.com +#+end_src + +The ~login~ field (~geolocation~) is just a label. The script matches on +~machine googleapis.com~ and uses the ~password~ value; the key is never printed +or written in plaintext. + +* Testing an adapter by hand + +Run it in a terminal and check the JSON: + +#+begin_src sh + ./google-geolocate.py + # {"lat": 41.3222, "lng": -71.8113, "accuracy_m": 25.0, "address": "..."} +#+end_src + +If it prints coordinates near you, point ~wttrin-geolocation-command~ at it and +pick "Current location (detect)" in ~M-x wttrin~. |
