macOSのairport
コマンドで周囲のWiFiをスキャンして、得られたBSSIDをMozilla Location Serviceに送って現在地を取得してみます。
MacBook Pro (13-inch, 2016, Two Thunderbolt 3 ports)で試しました。
最初に断っておきますがMacで位置情報を取得したいなら通常はCoreLocationを使うべきですし、ブラウザから位置情報を取得したいならGeolocation APIを使うべきです。
airport
に限らずBSSIDの一覧を取得できるコマンド(Linuxならnmcliなど)とMozilla Location Serviceを組み合わせると、OSを問わずGPSなしで位置情報を取得できます。
airport
で周囲のWiFiをスキャンする
airport
コマンドは/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport
にあります。
なんでパスが通っていないのかは長年の疑問です。
/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s
SSID BSSID RSSI CHANNEL HT CC SECURITY (auth/unicast/group)
pr500m-9ec840-2 xx:xx:xx:xx:xx:xx -88 11 Y JP WPA2(PSK/AES/AES)
elecom5g-CBCA28 xx:xx:xx:xx:xx:xx -91 116 Y US WPA2(PSK/AES/AES)
A8A795523D8B-5G xx:xx:xx:xx:xx:xx -87 100 Y JP WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
aterm-25d691-a xx:xx:xx:xx:xx:xx -37 36 Y -- WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
aterm-c1ff43-g xx:xx:xx:xx:xx:xx -81 11 Y JP WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
penguin piyopiyo xx:xx:xx:xx:xx:xx -81 9 Y -- WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
W04_54B121E2BF7B xx:xx:xx:xx:xx:xx -46 9 Y -- WPA(PSK/AES,TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP)
リビング.o xx:xx:xx:xx:xx:xx -70 6 Y -- NONE
aterm-58c56b-g xx:xx:xx:xx:xx:xx -78 4 Y JP WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
ssw-pc-a1a9c9 xx:xx:xx:xx:xx:xx -81 1 Y -- WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
実際は上記コマンドに | sed 's/..:..:..:..:..:../xx:xx:xx:xx:xx:xx/g'
と続けてBSSIDを伏せ字にしています。
1行目を削る
1行目はいらないので sed -n '2,$p'
で削ります。
/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s |
sed -n '2,$p'
ssw-pc-a1a9c9 xx:xx:xx:xx:xx:xx -80 1 Y -- WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
aterm-58c56b-g xx:xx:xx:xx:xx:xx -83 4 Y JP WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
4CE67673D86F-1 xx:xx:xx:xx:xx:xx -89 6 Y -- WPA(PSK/AES/AES)
aterm-c1ff43-g xx:xx:xx:xx:xx:xx -89 11 Y JP WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
elecom5g-CBCA28 xx:xx:xx:xx:xx:xx -90 116 Y US WPA2(PSK/AES/AES)
A8A795523D8B-5G xx:xx:xx:xx:xx:xx -85 100 Y JP WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
aterm-25d691-a xx:xx:xx:xx:xx:xx -41 36 Y -- WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
penguin piyopiyo xx:xx:xx:xx:xx:xx -83 9 Y -- WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
W04_54B121E2BF7B xx:xx:xx:xx:xx:xx -45 9 Y -- WPA(PSK/AES,TKIP/TKIP) WPA2(PSK/AES,TKIP/TKIP)
リビング.o xx:xx:xx:xx:xx:xx -68 6 Y -- NONE
aterm-cba967-gw xx:xx:xx:xx:xx:xx -84 3 N JP WEP
aterm-cba967-g xx:xx:xx:xx:xx:xx -85 3 Y JP WPA(PSK/AES/AES) WPA2(PSK/AES/AES)
BSSIDとRSSIだけ抜き出す
続いて、BSSIDとRSSIだけ抜き出します。
sed 's/^.*\(..:..:..:..:..:..\) *\(-[0-9]\{1,3\}\).*$/\1 \2/'
とやったらできました。
正規表現がなかなかおぼえられないのでいつもここを見ています。
/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s |
sed -n '2,$p' | # 1行目を削除する
sed 's/^.*\(..:..:..:..:..:..\) *\(-[0-9]\{1,3\}\).*$/\1 \2/' # BSSIDとRSSIだけ抜き出す
xx:xx:xx:xx:xx:xx -87
xx:xx:xx:xx:xx:xx -85
xx:xx:xx:xx:xx:xx -88
xx:xx:xx:xx:xx:xx -77
xx:xx:xx:xx:xx:xx -93
xx:xx:xx:xx:xx:xx -83
xx:xx:xx:xx:xx:xx -86
xx:xx:xx:xx:xx:xx -41
xx:xx:xx:xx:xx:xx -48
xx:xx:xx:xx:xx:xx -80
xx:xx:xx:xx:xx:xx -79
xx:xx:xx:xx:xx:xx -80
xx:xx:xx:xx:xx:xx -40
xx:xx:xx:xx:xx:xx -22
JSONに変換する
Geolocate APIのリクエストボディは次のようなJSONです。
{
"wifiAccessPoints": [{
"macAddress": "01:23:45:67:89:ab",
"signalStrength": -51
}, {
"macAddress": "01:23:45:67:89:cd"
}]
}
さきほどのSSIDの一覧をJSONに変換します。
/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s |
sed -n '2,$p' |
sed 's/^.*\(..:..:..:..:..:..\) *\(-[0-9]\{1,3\}\).*$/\1 \2/' |
awk '{printf("{\"macAddress\": \"%s\", \"signalStrength\": %s}\n", $1, $2)}' | # BSSIDとRSSIの組をJSONのオブジェクトに変換する
awk 'BEGIN{printf "["} {if(NR != 1){printf ","};printf $0} END{printf "]"}' | # オブジェクトを配列にまとめる
awk '{printf("{\"wifiAccessPoints\": %s}\n", $0)}' # 外側のJSONを付け加える
{"wifiAccessPoints": [{"macAddress": "xx:xx:xx:xx:xx:xx", "signalStrength": -81},{"macAddress": "xx:xx:xx:xx:xx:xx", "signalStrength": -88},{"macAddress": "xx:xx:xx:xx:xx:xx", "signalStrength": -43},{"macAddress": "xx:xx:xx:xx:xx:xx", "signalStrength": -82},{"macAddress": "xx:xx:xx:xx:xx:xx", "signalStrength": -83},{"macAddress": "xx:xx:xx:xx:xx:xx", "signalStrength": -80},{"macAddress": "xx:xx:xx:xx:xx:xx", "signalStrength": -52},{"macAddress": "xx:xx:xx:xx:xx:xx", "signalStrength": -72},{"macAddress": "xx:xx:xx:xx:xx:xx", "signalStrength": -76},{"macAddress": "xx:xx:xx:xx:xx:xx", "signalStrength": -82},{"macAddress": "xx:xx:xx:xx:xx:xx", "signalStrength": -39},{"macAddress": "xx:xx:xx:xx:xx:xx", "signalStrength": -22}]}
curlでHTTPリクエストを行う
curlはリクエストボディの内容を標準入力から受け取れるので、さきほどの結果をcurlに渡して、実際にリクエストを送ってみます。
APIのURLは、https://location.services.mozilla.com/v1/geolocate?key=<API_KEY>
で、開発中などに試しに使ってみるときはAPI_KEYにはtest
といれておけばよいようです。
/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s |
sed -n '2,$p' |
sed 's/^.*\(..:..:..:..:..:..\) *\(-[0-9]\{1,3\}\).*$/\1 \2/' |
awk '{printf("{\"macAddress\": \"%s\", \"signalStrength\": %s}\n", $1, $2)}' |
awk 'BEGIN{printf "["} {if(NR != 1){printf ","};printf $0} END{printf "]"}' |
awk '{printf("{\"wifiAccessPoints\": %s}\n", $0)}' |
curl -H "Content-type: application/json" -d @- https://location.services.mozilla.com/v1/geolocate?key=test
{"location": {"lat": 35.xxxxxxx, "lng": 139.xxxxxxx}, "accuracy": 25.7291953}
緯度経度が取得できました。
OpenStreetMapに緯度経度を入力してみたら、accuracy
で示されているくらいの精度で現在地が表示されました。
これは良い・・・
ついでなのでOSMのページをブラウザで開くところまで自動化してみます。
/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s |
sed -n '2,$p' |
sed 's/^.*\(..:..:..:..:..:..\) *\(-[0-9]\{1,3\}\).*$/\1 \2/' |
awk '{printf("{\"macAddress\": \"%s\", \"signalStrength\": %s}\n", $1, $2)}' |
awk 'BEGIN{printf "["} {if(NR != 1){printf ","};printf $0} END{printf "]"}' |
awk '{printf("{\"wifiAccessPoints\": %s}\n", $0)}' |
curl -H "Content-type: application/json" -d @- https://location.services.mozilla.com/v1/geolocate?key=test |
sed 's/^.*"lat": \([0-9][0-9]*\.[0-9][0-9]*\).*"lng": \([0-9][0-9]*\.[0-9][0-9]*\).*$/\1 \2/' |
awk '{printf("https://www.openstreetmap.org/#map=19/%s/%s\n", $1, $2)}' |
xargs open