I wrote yesterday about a quick hack I did at Over The Air using the BlueVia API. I thought it was worth a quick post to show just how simple it was.
Read yesterday’s post for background to the idea behind the hack, but in essence, what I wanted was:
- monitor the location of my mobile phone
- send an SMS to a different mobile number when my phone goes into a predefined known area
BlueVia provides an API that let me doing this using network operator data. In other words, nothing needs to run on my phone itself as location data is obtained from where O2 thinks my phone is.
This means there is no battery-life impact on the phone for this monitoring.
It also means this will work with any phone – from iPhones and Androids to cheap feature phones.
The whole thing took me less than an hour and needed only 90 lines of Python.
This is how I did it.
1. Downloaded the zip file from BlueVia’s github page
2. Opened the Readme.html file in the examples/python/Sample Code
folder
3. Installed the Python prereqs listed in the Readme.html file using easy_install
4. Created an app at bluevia.com requesting the Send SMS and the Location API
I got keys and IDs immediately.
5. The Readme.html contained enough sample code to show me how to do their OAuth process:
# overtheair-auth.py import bluevia consumer = 'XXXXXXXXXXXXXX' secret = 'XXXXXXXXXXXXX' o3 = bluevia.BlueViaOauth(consumer, secret) urlobj = o3.fetch_request_token() code = urlobj[0] url = urlobj[1] if code == 200: access_token = raw_input('go to ' + url + ' then come back here and enter the verifier\n\nverifier: ') o3.fetch_access_token(access_token) o3.saveAccessToken("blueviaauthtok.pkl") else: print 'fail ' + code
6. The Readme.html file also contained enough sample code to show me how to get my mobile’s current location
# overtheair-location-attempt1.py import bluevia import pprint l = bluevia.BlueViaLocation() l.loadAccessToken("blueviaauthtok.pkl") location = l.locateTerminal() pprint.pprint(location)
7. That wasn’t very accurate.
It gave me a location somewhere in Slough, which was nowhere near me in Bletchley Park. That confused me for a minute until I remembered that O2 are in Slough.
Comparing the location I got out of the BlueVia API with the O2 UK Head Office…
Ah… I guessed it wasn’t returning me inaccurate location data. It was returning me canned data, which they’ve set to their head office location.
Which is when I remembered that in the BlueVia APIs talk I went to, they mentioned that the REST API included a sandbox endpoint for testing and development.
I took a quick peek at the source code of the Python bluevia library I downloaded from github, and spotted that the BlueViaLocation function had an optional sandbox parameter which defaulted to ‘sandbox’.
A quick tweak:
# overtheair-location-attempt2.py import bluevia import pprint l = bluevia.BlueViaLocation(sandbox="") l.loadAccessToken("blueviaauthtok.pkl") location = l.locateTerminal() pprint.pprint(location)
Bingo – I had the latitude/longitude of my location in Bletchley Park.
8. I needed to know how to see how far this location was from the predefined known area. A quick Google turned some public domain Python for doing this at johndcook.com – so big thanks to John D. Cook for saving me having to do any complicated maths.
9. Back to the Readme.html file, to get sample code for sending an SMS:
# overtheair-sendsms.py import bluevia import pprint targetMobileNumber = "447654123456" s = bluevia.BlueViaOutboundSms(sandbox="") s.loadAccessToken("blueviaauthtok.pkl") r = s.sendSMS([targetMobileNumber], "message to send") pprint.pprint(s.deliveryStatus(r[1]))
10. I did a little tinkering to dynamically set a polling frequency based on my distance from the target
# 1 miles per hour = 0.44704 metres per second # my max speed = 70 mph # my max speed = (70 * 0.44704) metres per second pollinterval = ((70 * 0.44704) / distanceInMetres) # lower limit - don't poll more frequently than once a minute if pollinterval < 60: return 60 else: return pollinterval
11. Putting it all together, I had this:
# overtheair.py import bluevia import pprint import math import time # borrowed with thanks from http://www.johndcook.com/python_longitude_latitude.html def distance_on_unit_sphere(lat1, long1, lat2, long2): # Convert latitude and longitude to # spherical coordinates in radians. degrees_to_radians = math.pi/180.0 # phi = 90 - latitude phi1 = (90.0 - lat1)*degrees_to_radians phi2 = (90.0 - lat2)*degrees_to_radians # theta = longitude theta1 = long1*degrees_to_radians theta2 = long2*degrees_to_radians # Compute spherical distance from spherical coordinates. cos = (math.sin(phi1)*math.sin(phi2)*math.cos(theta1 - theta2) + math.cos(phi1)*math.cos(phi2)) arc = math.acos( cos ) # return in metres return arc * 6373 * 1000 def getlocationtoken(): l = bluevia.BlueViaLocation(sandbox="") l.loadAccessToken("blueviaauthtok.pkl") return l def getlocation(l): loc = l.locateTerminal() return loc def getdistanceinmetres(loc, target): currentlat = float(loc[1]['terminalLocation']['currentLocation']['coordinates']['latitude']) currentlon = float(loc[1]['terminalLocation']['currentLocation']['coordinates']['longitude']) return distance_on_unit_sphere(target['latitude'], target['longitude'], currentlat, currentlon) def sendsms(place): s = bluevia.BlueViaOutboundSms(sandbox="") s.loadAccessToken("blueviaauthtok.pkl") r = s.sendSMS(["447654123456"], "I'm near " + place['name'] + " now. Do you need me to get you anything?") pprint.pprint(s.deliveryStatus(r[1])) def choosesleeptime(metres): # 1 miles per hour = 0.44704 metres per second # my max speed = 70 mph # my max speed = (70 * 0.44704) metres per second quickesttraveltime = ((70 * 0.44704) / metres) # fix lower limit - don't poll more frequently than once a minute if quickesttraveltime < 60: return 60 else: return quickesttraveltime loctok = getlocationtoken() while True: loc = getlocation(loctok) closestPlace = None places = [ { 'latitude' : 51.99684, 'longitude' : -0.7384, 'name' : 'Bletchley Park' }, { 'latitude' : 50.96948, 'longitude' : -1.35246, 'name' : 'Sainsburys' }, { 'latitude' : 50.97966, 'longitude' : -1.34928, 'name' : 'Tesco Express' } ] for place in places: place['distance'] = getdistanceinmetres(loc, place) if closestPlace is None: closestPlace = place elif closestPlace['distance'] > place['distance']: closestPlace = place if closestPlace is None: print "something went wrong. fail." exit() # network location wont be precise, so tolerate anything within 500 metres goodenough = 500 if closestPlace['distance'] < goodenough: sendsms(closestPlace) print "finished - don't do this again for at least 12 hours" time.sleep(12 * 60 * 60) # 12 hours else: seconds = choosesleeptime(closestPlace['distance']) print str(closestPlace['distance']) + " from nearest place. waiting for " + str(seconds) + " seconds before checking again" time.sleep(seconds)
12. It worked. 🙂
Okay, so it was a silly idea.
But the point is - it was amazingly easy to get this working. To do this by writing a native mobile app would have needed much more effort, and would have only worked on one platform.
The downside? You need to be on a mobile network that supports BlueVia - which in the UK means O2. I couldn't have done this if I was on Vodafone. Which restricts the usability a little.
But still. It is pretty cool.
Tags: bluevia, geolocation, location, mobile, o2, ota11, overtheair, python
[…] Dale Lane on using BlueVia APIs […]
Hey dude,
Well done, Im a stakeholder in BV and good to know you’ve managed to get it beyond what it was designed for.
Would be great if you could walk me thru this on android….
bladerunner204@gmail.com
cheers
Hammad
I’m not sure that Android would be any different to any other platform – what sort of thing did you have in mind?
Kind regards, D
Hey Dale
apoligies for the late reply, I would like what you have setup on android, when I get near a location a text to go out to the Mrs etc… w
Thanks in advance
Hammad
Hi Hammad
All the source code to do it is on this page.
Unless the API has changed significantly in the five months since I did this, you shouldn’t need anything else in order to do that.
Kind regards, D
Interesting article – I am primarily reading it in order to find out how best to document APIs similar to this one.
I notice that your formula for “polling interval” gives a frequency in units of s^-1, which is fine, but you then treat it as an interval as if it were in units of seconds. No wonder that it gives an interval of 0 for distances from target of anything over 35 metres! Your guard condition (if pollinterval < 60) will always fire unless your mobile device is right on top of the transmission mast. 🙂
Where do you get the blueviaauthtok.pkl file? If you made it, how does it look inside? I’m having a problem with this part since it’s my first time to encounter a .pkl file… Thank you
I’m sorry… but I got the answer already. I didn’t see the o3.saveAccessToken part.