This code is used as part of a BLE scavenger hunt game where multiple BLE beacons are scattered around an area, hidden. The user has to walk around and find them with their phone, collecting passphrases as they go from the server the beacons point to. Each beacon passes its MAC address in the query string to the server to uniqly identify it.

Dependencies:   BLE_API mbed nRF51822

Fork of BLE_URIBeacon by Bluetooth Low Energy

Intro

This code demonstrates one possible use of BLE URIBeacons. In this demo multiple beacons are loaded with the same piece of code. Each beacon will add its mac address to the query string to be sent to the server. This allows each board to be paired wtih unique information in the server database. In this instance a company ID and passphrase are associated with each beacon. This information is then displayed either in the notification tray on the phone or on the webpage when connected to.

Using it

To use the app please make sure the shortened URL in the mbed code points to a server running the server code (in this case we are providing Google App Engine codeas an example). When a board is first connected to the server the user will need assign it a company ID and a passphrase. On later connections to the server this information will be returned to the requesting device (usually a smartphone / tablet)

In short there are two phases of operation, setup and use.

Setup:

  1. make sure shortened url in mbed code points to app engine server running code
  2. load code to mbed enabled BLE board
  3. load app engine with provided code
  4. connect for first time to server via physical web app and register the device

Use:

  1. scan with physical web app
  2. uribeacon is found, companyID and passphrase are displayed in notification bar (alternatively can connect to webpage for same info)

Examples

This photo shows information from the server being shown in the notification drawer on android. There are 2 beacons shown, the first beacon is in phase 2: Use, it has been initialized on the server with a company name of 'Random company' and a passphrase of 'Wombats are awesome!!'. The other beacon is in phase 1: setup, and has not been initialized, so it displays the "please configure me" message. /media/uploads/mbedAustin/screenshot_2015-04-16-21-10-16-1-.png

Constraints

This example relies on passing along a query string '?q=" from the shortened url to the long url. Not all shortening services do this. We recommend using tiny.cc or snurl.com as they will pass along the '?q=.....' to the server. Currently the shortened URL in the example points to mbeduribeacon.appspot.com . The duration of the shortened URL is not guaranteed, so it may revert back to nothing in the future. Also the appspot server may go down in the future, thus we have provided the source code for both sides to the user.

This example also relies on the Physical Web app (available for iOS and Android). As of the date of this writing it works just fine, but since we at mbed do not control this app, this may change in the future.

Sometimes something will take a little time to buffer things, im not sure if its the google app engine or the proxy that URIBeacons are run through on the PhyscialWeb app. This means there may be up to a 20min delay between when you do the initial setup of the device and when it starts showing up correctly on your devices notification tray, but if you click through to the webpage it should work immediately after being setup.

Technical Details

This example works by forwarding the mac address of the device in the query string '?q=MACADDR' to the server. The server will return a webpage based on the mac address in the query string. Not all url shortening services do this, so we used snurl.com, because it does. The PhysicalWeb phone app will scan the beacon, ping the server, and display the MetaData (description, favicon, title, url) fields in the notification drawer and in the physical web app. This entire demo relies on the PhysicalWeb app to handle the phone side interaction.

Summary

This is a cool demo of how to use URIBeacons to facilitate connecting the physical and digital worlds. It is by no means an end all be all, but is just one way to use them. Go build something awesome!

Other things

This is the the google app engine code necessary to run this example. If you would like to run your own server you can sign up for a free app engine account and use this python code as your example code.

ServerCode.py

from google.appengine.api import users
import cgi

import webapp2

import pprint

register = {
	'test':{'companyid':'Awesomeness Inc.','passphrase':'wombats of the doom'},
}

ADD_PAGE_HTML = '''\
<html>
	<head>
		<title>mbed Powered Scavenger Hunt</title>
		<meta name="description" content="This device is not recognized. Please click to add device to database.">
	</head>
  <body>
  	<h2>This device is not recognized by the web server, please register it.</h2>
    <form action="/add" method="post">
      <div>Board:<textarea name="boardid">DEFAULTKEY</textarea></div>
      <div>CompanyID:<textarea name="companyid" rows="1" cols="60"></textarea></div>
      <div>passphrase<textarea name="passphrase" rows="1" cols="60"></textarea></div>
      <div><input type="submit" value="submit"></div>
    </form>
  </body>
</html>
'''

RESPONSE_PAGE_HTML = '''\
<html>
	<head>
		<title>mbed Powered Scavenger Hunt</title>
		<meta name="description" content="DEFAULTCOMPANY's password is 'DEFAULTVALUE'">
	</head>
	<body>
		<b>DEFAULTCOMPANY</b>'s password is '<i>DEFAULTVALUE</i>'

	</body>

</html>
'''

# main page checks dictionary 
#	if key exists display value
#	if key doesnt exist add it to dictionary
class MainPage(webapp2.RequestHandler):
	def get(self):
		x = self.request.get('q').lower()
		if x in register.keys():
			self.response.write(RESPONSE_PAGE_HTML.replace('DEFAULTVALUE',register[x]['passphrase']).replace('DEFAULTCOMPANY',register[x]['companyid']))
		else:
			self.response.write(ADD_PAGE_HTML.replace('DEFAULTKEY',x))


# the debug page prints out all key value pairs
class DebugPage(webapp2.RequestHandler):
	def get(self):
		pp = pprint.pformat(register,indent=6)
		self.response.write(pp)

# the add page takes a post message and adds the keys and values to the dictionary
class AddPage(webapp2.RequestHandler):
	def post(self):
		boardid   	= cgi.escape(self.request.get('boardid'))
		companyid 	= cgi.escape(self.request.get('companyid'))
		passphrase 	= cgi.escape(self.request.get('passphrase'))
		register[boardid] = {'passphrase':passphrase,'companyid':companyid}
		self.response.write("Added company:'<b>"+companyid+"</b>' with passphrase:'<i>"+passphrase+"</i>'")

application = webapp2.WSGIApplication(
	[
    	('/', MainPage),
    	('/debug',DebugPage),
    	('/add',AddPage),
], debug=True)
Committer:
mbedAustin
Date:
Thu Apr 16 20:27:05 2015 +0000
Revision:
21:3c518a637de6
Parent:
17:e2c0a1696e39
Fixed mac address conversion issue

Who changed what in which revision?

UserRevisionLine numberNew contents of line
rgrover1 16:1daa78939a3b 1 /* mbed Microcontroller Library
rgrover1 16:1daa78939a3b 2 * Copyright (c) 2006-2015 ARM Limited
rgrover1 16:1daa78939a3b 3 *
rgrover1 16:1daa78939a3b 4 * Licensed under the Apache License, Version 2.0 (the "License");
rgrover1 16:1daa78939a3b 5 * you may not use this file except in compliance with the License.
rgrover1 16:1daa78939a3b 6 * You may obtain a copy of the License at
rgrover1 16:1daa78939a3b 7 *
rgrover1 16:1daa78939a3b 8 * http://www.apache.org/licenses/LICENSE-2.0
rgrover1 16:1daa78939a3b 9 *
rgrover1 16:1daa78939a3b 10 * Unless required by applicable law or agreed to in writing, software
rgrover1 16:1daa78939a3b 11 * distributed under the License is distributed on an "AS IS" BASIS,
rgrover1 16:1daa78939a3b 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
rgrover1 16:1daa78939a3b 13 * See the License for the specific language governing permissions and
rgrover1 16:1daa78939a3b 14 * limitations under the License.
rgrover1 16:1daa78939a3b 15 */
rgrover1 16:1daa78939a3b 16
rgrover1 16:1daa78939a3b 17 #include "pstorage.h"
rgrover1 16:1daa78939a3b 18 #include "nrf_error.h"
rgrover1 16:1daa78939a3b 19 #include "ConfigParamsPersistence.h"
rgrover1 16:1daa78939a3b 20
rgrover1 17:e2c0a1696e39 21 /**
rgrover1 17:e2c0a1696e39 22 * Nordic specific structure used to store params persistently.
rgrover1 17:e2c0a1696e39 23 * It extends URIBeaconConfigService::Params_t with a persistence signature.
rgrover1 17:e2c0a1696e39 24 */
rgrover1 17:e2c0a1696e39 25 struct PersistentParams_t {
rgrover1 17:e2c0a1696e39 26 URIBeaconConfigService::Params_t params;
rgrover1 17:e2c0a1696e39 27 uint32_t persistenceSignature; /* This isn't really a parameter, but having the expected
rgrover1 17:e2c0a1696e39 28 * magic value in this field indicates persistence. */
rgrover1 17:e2c0a1696e39 29
rgrover1 17:e2c0a1696e39 30 static const uint32_t MAGIC = 0x1BEAC000; /* Magic that identifies persistence */
rgrover1 17:e2c0a1696e39 31 };
rgrover1 17:e2c0a1696e39 32
rgrover1 17:e2c0a1696e39 33 /**
rgrover1 17:e2c0a1696e39 34 * The following is a module-local variable to hold configuration parameters for
rgrover1 17:e2c0a1696e39 35 * short periods during flash access. This is necessary because the pstorage
rgrover1 17:e2c0a1696e39 36 * APIs don't copy in the memory provided as data source. The memory cannot be
rgrover1 17:e2c0a1696e39 37 * freed or reused by the application until this flash access is complete. The
rgrover1 17:e2c0a1696e39 38 * load and store operations in this module initialize persistentParams and then
rgrover1 17:e2c0a1696e39 39 * pass it on to the 'pstorage' APIs.
rgrover1 17:e2c0a1696e39 40 */
rgrover1 17:e2c0a1696e39 41 static PersistentParams_t persistentParams;
rgrover1 17:e2c0a1696e39 42
rgrover1 16:1daa78939a3b 43 static pstorage_handle_t pstorageHandle;
rgrover1 16:1daa78939a3b 44
rgrover1 17:e2c0a1696e39 45 /**
rgrover1 17:e2c0a1696e39 46 * Dummy callback handler needed by Nordic's pstorage module. This is called
rgrover1 17:e2c0a1696e39 47 * after every flash access.
rgrover1 17:e2c0a1696e39 48 */
rgrover1 16:1daa78939a3b 49 static void pstorageNotificationCallback(pstorage_handle_t *p_handle,
rgrover1 17:e2c0a1696e39 50 uint8_t op_code,
rgrover1 17:e2c0a1696e39 51 uint32_t result,
rgrover1 17:e2c0a1696e39 52 uint8_t *p_data,
rgrover1 17:e2c0a1696e39 53 uint32_t data_len)
rgrover1 16:1daa78939a3b 54 {
rgrover1 16:1daa78939a3b 55 /* APP_ERROR_CHECK(result); */
rgrover1 16:1daa78939a3b 56 }
rgrover1 16:1daa78939a3b 57
rgrover1 16:1daa78939a3b 58 /* Platform-specific implementation for persistence on the nRF5x. Based on the
rgrover1 16:1daa78939a3b 59 * pstorage module provided by the Nordic SDK. */
rgrover1 17:e2c0a1696e39 60 bool loadURIBeaconConfigParams(URIBeaconConfigService::Params_t *paramsP)
rgrover1 16:1daa78939a3b 61 {
rgrover1 17:e2c0a1696e39 62 static bool pstorageInitied = false;
rgrover1 17:e2c0a1696e39 63 if (!pstorageInitied) {
rgrover1 17:e2c0a1696e39 64 pstorage_init();
rgrover1 16:1daa78939a3b 65
rgrover1 17:e2c0a1696e39 66 static pstorage_module_param_t pstorageParams = {
rgrover1 17:e2c0a1696e39 67 .cb = pstorageNotificationCallback,
rgrover1 17:e2c0a1696e39 68 .block_size = sizeof(PersistentParams_t),
rgrover1 17:e2c0a1696e39 69 .block_count = 1
rgrover1 17:e2c0a1696e39 70 };
rgrover1 17:e2c0a1696e39 71 pstorage_register(&pstorageParams, &pstorageHandle);
rgrover1 17:e2c0a1696e39 72 pstorageInitied = true;
rgrover1 17:e2c0a1696e39 73 }
rgrover1 17:e2c0a1696e39 74
rgrover1 17:e2c0a1696e39 75 if ((pstorage_load(reinterpret_cast<uint8_t *>(&persistentParams), &pstorageHandle, sizeof(PersistentParams_t), 0) != NRF_SUCCESS) ||
rgrover1 17:e2c0a1696e39 76 (persistentParams.persistenceSignature != PersistentParams_t::MAGIC)) {
rgrover1 16:1daa78939a3b 77 // On failure zero out and let the service reset to defaults
rgrover1 16:1daa78939a3b 78 memset(paramsP, 0, sizeof(URIBeaconConfigService::Params_t));
rgrover1 17:e2c0a1696e39 79 return false;
rgrover1 16:1daa78939a3b 80 }
rgrover1 17:e2c0a1696e39 81
rgrover1 17:e2c0a1696e39 82 memcpy(paramsP, &persistentParams.params, sizeof(URIBeaconConfigService::Params_t));
rgrover1 17:e2c0a1696e39 83 return true;
rgrover1 16:1daa78939a3b 84 }
rgrover1 16:1daa78939a3b 85
rgrover1 16:1daa78939a3b 86 /* Platform-specific implementation for persistence on the nRF5x. Based on the
rgrover1 16:1daa78939a3b 87 * pstorage module provided by the Nordic SDK. */
rgrover1 17:e2c0a1696e39 88 void saveURIBeaconConfigParams(const URIBeaconConfigService::Params_t *paramsP)
rgrover1 16:1daa78939a3b 89 {
rgrover1 17:e2c0a1696e39 90 memcpy(&persistentParams.params, paramsP, sizeof(URIBeaconConfigService::Params_t));
rgrover1 17:e2c0a1696e39 91 if (persistentParams.persistenceSignature != PersistentParams_t::MAGIC) {
rgrover1 17:e2c0a1696e39 92 persistentParams.persistenceSignature = PersistentParams_t::MAGIC;
rgrover1 16:1daa78939a3b 93 pstorage_store(&pstorageHandle,
rgrover1 17:e2c0a1696e39 94 reinterpret_cast<uint8_t *>(&persistentParams),
rgrover1 17:e2c0a1696e39 95 sizeof(PersistentParams_t),
rgrover1 16:1daa78939a3b 96 0 /* offset */);
rgrover1 16:1daa78939a3b 97 } else {
rgrover1 16:1daa78939a3b 98 pstorage_update(&pstorageHandle,
rgrover1 17:e2c0a1696e39 99 reinterpret_cast<uint8_t *>(&persistentParams),
rgrover1 17:e2c0a1696e39 100 sizeof(PersistentParams_t),
rgrover1 16:1daa78939a3b 101 0 /* offset */);
rgrover1 16:1daa78939a3b 102 }
rgrover1 16:1daa78939a3b 103 }