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:
rgrover1
Date:
Wed Jan 21 10:14:04 2015 +0000
Revision:
11:c77cc2b74101
adding implementation for nrfURIBeaconConfigService, which ports the platform-agnostic URIBeaconConfigService to the nRF51822 and implements lock persistence.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
rgrover1 11:c77cc2b74101 1 /* mbed Microcontroller Library
rgrover1 11:c77cc2b74101 2 * Copyright (c) 2006-2013 ARM Limited
rgrover1 11:c77cc2b74101 3 *
rgrover1 11:c77cc2b74101 4 * Licensed under the Apache License, Version 2.0 (the "License");
rgrover1 11:c77cc2b74101 5 * you may not use this file except in compliance with the License.
rgrover1 11:c77cc2b74101 6 * You may obtain a copy of the License at
rgrover1 11:c77cc2b74101 7 *
rgrover1 11:c77cc2b74101 8 * http://www.apache.org/licenses/LICENSE-2.0
rgrover1 11:c77cc2b74101 9 *
rgrover1 11:c77cc2b74101 10 * Unless required by applicable law or agreed to in writing, software
rgrover1 11:c77cc2b74101 11 * distributed under the License is distributed on an "AS IS" BASIS,
rgrover1 11:c77cc2b74101 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
rgrover1 11:c77cc2b74101 13 * See the License for the specific language governing permissions and
rgrover1 11:c77cc2b74101 14 * limitations under the License.
rgrover1 11:c77cc2b74101 15 */
rgrover1 11:c77cc2b74101 16
rgrover1 11:c77cc2b74101 17 #ifndef __NRF_BLE_URI_BEACON_CONFIG_SERVICE_H__
rgrover1 11:c77cc2b74101 18 #define __NRF_BLE_URI_BEACON_CONFIG_SERVICE_H__
rgrover1 11:c77cc2b74101 19
rgrover1 11:c77cc2b74101 20 #include "pstorage.h"
rgrover1 11:c77cc2b74101 21 #include <nrf_error.h>
rgrover1 11:c77cc2b74101 22 #include "URIBeaconConfigService.h"
rgrover1 11:c77cc2b74101 23
rgrover1 11:c77cc2b74101 24 /**
rgrover1 11:c77cc2b74101 25 * @class nrfURIBeaconConfigService
rgrover1 11:c77cc2b74101 26 * @brief UriBeacon Configuration Service for nordic devices.
rgrover1 11:c77cc2b74101 27 */
rgrover1 11:c77cc2b74101 28 class nrfURIBeaconConfigService : public URIBeaconConfigService {
rgrover1 11:c77cc2b74101 29 public:
rgrover1 11:c77cc2b74101 30 /**
rgrover1 11:c77cc2b74101 31 * @param[ref] ble
rgrover1 11:c77cc2b74101 32 * BLEDevice object for the underlying controller.
rgrover1 11:c77cc2b74101 33 * @param[in] uridata
rgrover1 11:c77cc2b74101 34 * URI as a null-terminated string.
rgrover1 11:c77cc2b74101 35 * @param[in] flagsIn
rgrover1 11:c77cc2b74101 36 * UriBeacon Flags.
rgrover1 11:c77cc2b74101 37 * @param[in] powerLevels[]
rgrover1 11:c77cc2b74101 38 * Table of UriBeacon Tx Power Levels in dBm.
rgrover1 11:c77cc2b74101 39 * @param[in] powerMode
rgrover1 11:c77cc2b74101 40 * Currently effective power mode.
rgrover1 11:c77cc2b74101 41 * @param[in] beaconPeriodIn
rgrover1 11:c77cc2b74101 42 * The period in milliseconds that a UriBeacon packet is
rgrover1 11:c77cc2b74101 43 * transmitted. A value of zero disables UriBeacon
rgrover1 11:c77cc2b74101 44 * transmissions.
rgrover1 11:c77cc2b74101 45 */
rgrover1 11:c77cc2b74101 46 nrfURIBeaconConfigService(BLEDevice &bleIn,
rgrover1 11:c77cc2b74101 47 const char *uriDataIn,
rgrover1 11:c77cc2b74101 48 uint8_t flagsIn = 0,
rgrover1 11:c77cc2b74101 49 const int8_t powerLevelsIn[NUM_POWER_MODES] = NULL,
rgrover1 11:c77cc2b74101 50 URIBeaconConfigService::TXPowerModes_t powerModeIn = URIBeaconConfigService::TX_POWER_MODE_LOW,
rgrover1 11:c77cc2b74101 51 uint16_t beaconPeriodIn = 1000) :
rgrover1 11:c77cc2b74101 52 URIBeaconConfigService(bleIn, uriDataIn, flagsIn, powerLevelsIn, powerModeIn, beaconPeriodIn),
rgrover1 11:c77cc2b74101 53 pstorageHandle(),
rgrover1 11:c77cc2b74101 54 persistentData() {
rgrover1 11:c77cc2b74101 55 pstorage_init();
rgrover1 11:c77cc2b74101 56 pstorage_module_param_t pstorageParams = {
rgrover1 11:c77cc2b74101 57 .cb = pstorageNotificationCallback,
rgrover1 11:c77cc2b74101 58 .block_size = sizeof(PersistentData_t),
rgrover1 11:c77cc2b74101 59 .block_count = 1
rgrover1 11:c77cc2b74101 60 };
rgrover1 11:c77cc2b74101 61 pstorage_register(&pstorageParams, &pstorageHandle);
rgrover1 11:c77cc2b74101 62 storage_loadLockBits();
rgrover1 11:c77cc2b74101 63 }
rgrover1 11:c77cc2b74101 64
rgrover1 11:c77cc2b74101 65 private:
rgrover1 11:c77cc2b74101 66 static const uint32_t MAGIC = 0x1BEAC000;
rgrover1 11:c77cc2b74101 67 struct PersistentData_t {
rgrover1 11:c77cc2b74101 68 uint32_t magic; /* indicates a successful previous write */
rgrover1 11:c77cc2b74101 69 URIBeaconConfigService::LockBits_t lockBits;
rgrover1 11:c77cc2b74101 70 };
rgrover1 11:c77cc2b74101 71
rgrover1 11:c77cc2b74101 72 /**
rgrover1 11:c77cc2b74101 73 * APIs around making lockBits persistent.
rgrover1 11:c77cc2b74101 74 */
rgrover1 11:c77cc2b74101 75 private:
rgrover1 11:c77cc2b74101 76 /**
rgrover1 11:c77cc2b74101 77 * Have we previously saved lockedBits? Once set, this state is expected to persist.
rgrover1 11:c77cc2b74101 78 * @return true if we've previously saved locked bits.
rgrover1 11:c77cc2b74101 79 */
rgrover1 11:c77cc2b74101 80 virtual bool storage_haveSavedLockBits() const {
rgrover1 11:c77cc2b74101 81 return (persistentData.magic == MAGIC);
rgrover1 11:c77cc2b74101 82 }
rgrover1 11:c77cc2b74101 83
rgrover1 11:c77cc2b74101 84 /**
rgrover1 11:c77cc2b74101 85 * Save the current value of lockBits into persistent storage; this value is
rgrover1 11:c77cc2b74101 86 * then retrievable by lockLockBits() until a subsequent call to saveLockBits().
rgrover1 11:c77cc2b74101 87 */
rgrover1 11:c77cc2b74101 88 virtual void storage_saveLockBits() {
rgrover1 11:c77cc2b74101 89 bool initialSave = !storage_haveSavedLockBits();
rgrover1 11:c77cc2b74101 90
rgrover1 11:c77cc2b74101 91 persistentData.magic = MAGIC;
rgrover1 11:c77cc2b74101 92 copyLockBitsInto(persistentData.lockBits);
rgrover1 11:c77cc2b74101 93
rgrover1 11:c77cc2b74101 94 if (initialSave) {
rgrover1 11:c77cc2b74101 95 if (pstorage_store(&pstorageHandle,
rgrover1 11:c77cc2b74101 96 reinterpret_cast<std::uint8_t *>(&persistentData),
rgrover1 11:c77cc2b74101 97 sizeof(PersistentData_t),
rgrover1 11:c77cc2b74101 98 0 /* offset */) != NRF_SUCCESS) {
rgrover1 11:c77cc2b74101 99 // printf("nrfURIBeaconConfigService::saveLockBits: store() failed\r\n");
rgrover1 11:c77cc2b74101 100 return;
rgrover1 11:c77cc2b74101 101 }
rgrover1 11:c77cc2b74101 102 } else {
rgrover1 11:c77cc2b74101 103 if (pstorage_update(&pstorageHandle,
rgrover1 11:c77cc2b74101 104 reinterpret_cast<std::uint8_t *>(&persistentData),
rgrover1 11:c77cc2b74101 105 sizeof(PersistentData_t),
rgrover1 11:c77cc2b74101 106 0 /* offset */) != NRF_SUCCESS) {
rgrover1 11:c77cc2b74101 107 // printf("nrfURIBeaconConfigService::saveLockBits: update() failed\r\n");
rgrover1 11:c77cc2b74101 108 return;
rgrover1 11:c77cc2b74101 109 }
rgrover1 11:c77cc2b74101 110 }
rgrover1 11:c77cc2b74101 111 }
rgrover1 11:c77cc2b74101 112
rgrover1 11:c77cc2b74101 113 /**
rgrover1 11:c77cc2b74101 114 * Retrieve the saved lockBits from persistent storage and update the class
rgrover1 11:c77cc2b74101 115 * member 'lockBits'.
rgrover1 11:c77cc2b74101 116 */
rgrover1 11:c77cc2b74101 117 virtual void storage_loadLockBits() {
rgrover1 11:c77cc2b74101 118 if (pstorage_load(reinterpret_cast<std::uint8_t *>(&persistentData),
rgrover1 11:c77cc2b74101 119 &pstorageHandle,
rgrover1 11:c77cc2b74101 120 sizeof(PersistentData_t),
rgrover1 11:c77cc2b74101 121 0 /* offset */) != NRF_SUCCESS) {
rgrover1 11:c77cc2b74101 122 // printf("nrfURIBeaconConfigService::loadLockBits: load() failed\r\n");
rgrover1 11:c77cc2b74101 123 return;
rgrover1 11:c77cc2b74101 124 }
rgrover1 11:c77cc2b74101 125 if (storage_haveSavedLockBits()) {
rgrover1 11:c77cc2b74101 126 updateLockBits(persistentData.lockBits);
rgrover1 11:c77cc2b74101 127 }
rgrover1 11:c77cc2b74101 128 }
rgrover1 11:c77cc2b74101 129
rgrover1 11:c77cc2b74101 130 /* Dummy callback handler needed by Nordic's pstorage module. */
rgrover1 11:c77cc2b74101 131 static void pstorageNotificationCallback(pstorage_handle_t *p_handle,
rgrover1 11:c77cc2b74101 132 uint8_t op_code,
rgrover1 11:c77cc2b74101 133 uint32_t result,
rgrover1 11:c77cc2b74101 134 uint8_t * p_data,
rgrover1 11:c77cc2b74101 135 uint32_t data_len) {
rgrover1 11:c77cc2b74101 136 /* APP_ERROR_CHECK(result); */
rgrover1 11:c77cc2b74101 137 }
rgrover1 11:c77cc2b74101 138
rgrover1 11:c77cc2b74101 139 /* private state related to operation on nRF micros */
rgrover1 11:c77cc2b74101 140 private:
rgrover1 11:c77cc2b74101 141 pstorage_handle_t pstorageHandle;
rgrover1 11:c77cc2b74101 142 PersistentData_t persistentData;
rgrover1 11:c77cc2b74101 143 };
rgrover1 11:c77cc2b74101 144
rgrover1 11:c77cc2b74101 145 #endif /* #ifndef __NRF_BLE_URI_BEACON_CONFIG_SERVICE_H__*/