WiPy & Geolocalization


#MicroPython & #WiPy2.0: Geolocation using WLAN

I was surfing the services that Google offers, I found the Geolocation API. As Google describes the API: it returns a location and accuracy radius based on information about cell towers and WiFi nodes that the mobile client can detect. As I was writing the last post using MicroPython, I thought the WiPy 2.0 could be a nice "mobile client".

To use the API you need to get an API key from Google. As a standard user of the API, you get:

  • 2,500 free requests per day, and
  • 50 requests per second, per user.

I thought I could play with it to test the accuracy of the geolocation.

Python Code: https://github.com/lemariva/uPyGeo

If you’re not up to speed with MicroPython, Wipy see earlier articles in here.

Hardware & Software

Wipy 2.0 Wipy 2.0 x 1
Expansion BoardExpansion board x 1 buy
Google Maps API Google Maps Geolocation API x 1
Python uPyGeo x 1

Geolocate Class

I wrote a geolocate() class. This class has an initializer that needs a Google API key, and the ssid of the network that you are using to send the POST requests to the Google URL. Here is the class initializer:

def __init__(self, google_api_key, my_ssid, wlan_check_interval = 1, mcc=262, mnc=11):

Summarizing:

  • google_api_key: Google API key that you get clicking on the link available here;
  • wlan_check_interval: It defines the wait time between checking for new WiFi signals (in seconds (default = 1));
  • mcc: Mobile Country Codes (MCC) (default=262 -o2 Germany), you can find some values here;
  • mnc: Mobile Network Codes (MNC) (default=11 -o2 Germany), same as mcc.

A code example to explain how to use the geolocate class is the following

from geoposition import geolocate

ssid_ = <your ssid>                              #usually defined in your boot.py file
google_api_key = <API_KEY>                       #get from google
geo_locate = geolocate(google_api_key, ssid_)    #geo_locate object

valid, location = geo_locate.get_location()
if(valid):
    print("The geo position results: " + geo_locate.get_location_string())

The method get_location() starts a WiFi scan to search for available WiFi signals. It uses the WLAN class from the machine library.

from network import WLAN
wlan = WLAN(mode=WLAN.STA)
nets = wlan.scan()

It initializes an object WLAN with mode station (STA). Using the method scan() from the object wlan, it performs a network scan and returns a list of named tuples with (ssid, bssid, sec, channel, rssi) where

  • ssid: service set identifier (WiFi name of the network);
  • bssid: basic service set identifier (MAC address of the node);
  • sec: network security descriptor (WPA, WEP (I hope I don't find this... ;)) etc);
  • channel: WiFi channel (a number between 1 and 14: wiki);
  • rssi: received signal strength indication (the power present in a received radio signal).

With this information, a JSON is generated. Looking to the documentation of the API, the POST request should look like this

{
    "homeMobileCountryCode": 262,
    "homeMobileNetworkCode": 11,
    "radioType": "gsm",
    "carrier": "o2",
    "considerIp": "false",
    "wifiAccessPoints": [
        {
            "macAddress": "00:25:9c:cf:1c:ac",
            "signalStrength": -43,
            "age": 0,
            "channel": 11,
          },
        ...
    ]
  }

Then, the JSON array wifiAccessPoints is generated looping and writing the information saved on the nets tuple. The ssid_ that you provide to the class initializer is avoided in this loop. If you read the documentation of the API, you will find that the cellTowers is an optional parameter, and we don't need and have this information here.

It is important to set considerIp to false, otherwise, Google takes the IP of the connection to identify the position (which is not always a good choice). Doing this, you have to be sure to handle the errors that you get when there is no WiFi nodes, or Google has no position information about the WiFi node that you are sending. The get_location returns valid = True if the location that you get doesn't contain errors. Then, you can use the method geo_locate.get_location_string() in which all the received information that you received from Google is convert to a string separated with commas (e.g. for an eventually csv file).

Testing files

The main.py file works on the Wipy 2.0 using the extension board to mount a micro SD card (you need to make minor changes, if you want to use it on a WeMos board -you need to change the SD, the machine.RTC(), and the pycom parts). It first corrects the RTC internal clock using a NTP server as

from machine import RTC
import logging

logger = logging.getLogger(__name__)

def setRTCLocalTime():
    rtc = RTC()
    rtc.ntp_sync("time1.google.com")
    time.sleep_ms(750)
    logger.info(' RTC Set from NTP to UTC: %s' % str(rtc.now()))

setRTCLocalTime()

It initializes then the geolocate object, and an object to write to the micro SD (mounting a /sd folder) as

import os
from machine import SD

sd = SD()

try:
    os.mount(sd, '/sd')
except Exception as error:
    logger.error(str(error))

After that, the code stars the infinite loop (while True:) in which the position is estimated every position_interval seconds and saved together with the time information on the micro SD. One register of the gps_file file look like this line

year,month,day,hours,minutes,seconds,milliseconds,zone,latitude,longitude,accuracy
...
2017,10,22,15,49,2,788688,None,52.39774,9.736073,40.0
...

To summarized, you need to define the following global parameters

gps_file = "gps.csv"            # file name written on the SD card        
google_api_key = <API_KEY>      # https://goo.gl/hTU1sZ
position_interval = 5           # inquiry interval in seconds
ssid_ = <your ssid>             # you ssid -> wlan name

Performance Tests

To check the performance and acurracy of the code and the Google API, I took my Smartphone and I set it as a WiFi Hotspot. Then I took my GPS-4044 Datalogger to use it as reference, and I walked over 5 km (about 348 position points). The maximal accuracy was about 20 meters, and the minimal accuracy 187 meters. The median over 348 points was 39 meters with a standard deviation of 21.63 meters. It is quite impressive!

I converted the saved csv file to a geojson format using this tool available also in GitHub here, and then I used the Google Map API and I got the following map

The red bubbles correspond to the geolocation without GPS, the blue bubbles are the reference points saved using the GPS-4044. As you see, there are two regions (upper left corner, and in the middle in a park - no red points) where no WiFi signals, or no information about the WiFi signals were available to estimate the current position.

To summarize: I think, this is a great Google API and the restrictions of

  • 2,500 free requests per day (3 hours location @ 5 seconds)
  • 50 requests per second, per user

are not a big problem. I am really impressed by the accuracy of the estimation.

You can combine this class with the ST7735 drivers and you can get a nice GPS data logger with display!

{{ message }}

{{ 'Comments are closed.' | trans }}