Skip to main content

How To Use Bluetooth For BlinkUp

A Demo Device-activation Mobile App And Squirrel Code

The Bluetooth LE support offered by the imp004m and imp006 (impOS™ 42 and up) provides customers with an alternative means to provision production devices (ie. blessed devices) with an impCloud™ enrollment token, plan ID and end-user WiFi credentials, a process known as ‘device activation’. This guide shows how this can be achieved.

The Electric Imp BlinkUp™ SDK already incorporates the ability to retrieve an enrollment token and plan ID and make them available to the host mobile app rather than deliver them to the target imp-enabled device using standard optical BlinkUp. Accompanying this guide is a sample iOS app which demonstrates this approach and which works in conjunction with the Squirrel code referenced in this article.

Important Note Working with the BlinkUp SDK requires an API Key which is only made available to Electric Imp customers. If you are not an Electric Imp customer, the app code cannot be used to activate new devices. Additionally, this technique is only intended for production devices; it cannot be used to add new development devices to your account.

An Overview Of The Bluetooth BlinkUp Process

Bluetooth BlinkUp uses the BlinkUp SDK embedded in a mobile app which is operated by the target imp-enabled device’s end-user. The mobile app may guide the end-user through product-specific pre-setup tasks, such as creating an account with the customer and powering up the target device, but such operations are not part of the BlinkUp process.

For BlinkUp, the app first initiates a Bluetooth advertisement scan to discover the target device, which is running Squirrel application firmware that uses impOS’ Bluetooth LE functionality to serve a device discovery advertisement. When the app discovers the device, the end-user is notified and is then able to initiate activation when they are ready to do so.

The target device’s application firmware serves a set of GATT services with which the mobile app interacts to:

  • Receive a list of nearby WiFi networks with which the target device is compatible.
  • Receive data describing the target device.
  • Send BlinkUp credentials to the target device.
  • Send a chosen WiFi network name and password to the target device.
  • Command the target device to proceed with activation.

The mobile app needs to present the list of WiFi networks to the end-user, who will chose one and then, when prompted by the app, enter the network password.

The app now asks the SDK to retrieve a production enrollment token and a production plan ID from the impCloud. The app passes the SDK the customer’s BlinkUp API Key to authorize these requests. On receipt of the enrollment credentials, the app sends it to the target device. It also sends the chosen network credentials. Finally, it sends the command which instructs the target device to activate.

The target device’s application Squirrel stores the WiFi details and enrollment credentials it has received and then reboots itself. Upon restart, it connects to the local network and then to the impCloud, with which it attempts enrollment. In the meantime, the mobile app uses the BlinkUp SDK to poll the impCloud to determine when the target device has been activated.

Once activation has taken place, the app can report this to the end-user, and go on to perform any further, product-specific setup operations required by the target device.

The Bluetooth BlinkUp Example In Detail

The Mobile App

This example makes use of an iOS app, written in Swift 5. Typically a customer will also provide an Android version of the app.

Note The iOS code in Xcode’s Device Simulator, but this will not be able to access Bluetooth. To use Bluetooth, you must run the app on a connected iPhone.

The example app is used to scan for nearby Bluetooth-enabled imp-based devices running the Squirrel application code introduced below, to select one of them, and then to choose a local wireless network, enter its password and transmit that information to the test device.

The example app can perform a full device activation or simply update an already activated device’s WiFi details. Full device activation requires a BlinkUp API key. The app prompts you for a BlinkUp API key, or you can enter your key by tapping Actions in the navigation bar and then Enter your BlinkUp API key from the menu. Your BlinkUp API key will be stored in the iOS keychain. If you wish to clear a stored password, open the Enter your BlinkUp API key panel, ensure no key is entered, and tap Submit.

To test full activation on a development device, the device must have first been added to your Electric Imp account (using the Electric Imp mobile app) and then added to any Development Device Group.

The example app can also be used to clear a device’s WiFi settings.

Device Scanning

If you tap Actions > Start Scan or drag the screen downwards, the example app will begin scanning for nearby Bluetooth LE devices with a pre-set GATT service UUID — the BlinkUp service defined in the Squirrel code. If it detects such a device, the app connects to it and reads three of the standard Device Info service characteristics: the model number, the serial number and the system ID. These values are used by the accompanying Squirrel code to provide, respectively, the host device’s imp type, device ID and agent URL, which are displayed by the app upon receipt. The app uses the imp type to set the device’s icon.

The values are read sequentially, the receipt of the first triggering the request for the second. When the second is received, the app closes the connection. You should always close a Bluetooth LE connection as soon as you have read all the information you need, in order to minimize energy consumption.

Device Configuration

Tapping on a discovered device causes the example app to connect to the device once more and retrieve a list of WiFi networks detected by the device. This list is used to populate a picker from which the user can select which network they would like the device to connect to. The list indicates whether any given network requires a password, and a field is provided to enter that password. Leave the password field blank when you connect to open networks.

Tapping Send BlinkUp causes the app to connect again to the device and this time send it the SSID of the selected network and the password from the entry field. If the user has entered a BlinkUp API key (see above), the app will also connect to the impCloud and retrieve a production plan ID and an enrollment token — these too will be sent to the device.

Finally, the app writes an arbitrary value to the device to trigger the application of the supplied data and to cause the device to reboot, reconnect and (if a plan ID and enrollment token have been provided) activate. The app polls the impCloud to discover when the device has been activated. When activation takes place, the current poll returns the device’s agent URL — the indication that activation has succeeded. The app uses this URL to present the agent-hosted UI served by the example agent code.

Setting only the device’s WiFi credentials does not make use of the BlinkUp SDK. Polling does not take place in this case: you will have to check the device directly to confirm that it has connected. Clearing the WiFi settings does not make use of the BlinkUp SDK.

The Squirrel Application Code

The example device-side Squirrel code sets up the device’s Bluetooth LE sub-system, serves the BlinkUp and Device Info GATT services, and begins advertising the device to other Bluetooth LE-compatible units such as the iPhone on which the example mobile app is running.

Bluetooth Activation

The device code wraps its functionality into a simple structure that represents how a full customer application might work. When the code starts running, it checks the imp’s SPI flash for a signature (written by the application itself) indicating that the device has been activated. If the signature is present, the application code flow is executed (the dummy function startApplication() is called) ; if not, a separate code flow is triggered in which the imp’s Bluetooth radio is enabled and configured (startBluetooth()):

iType <- imp.info().type;
if ("spiflash" in hardware && (iType == "imp004m" || iType == "imp006")) {
    // Read the first four bytes of the SPI flash
    hardware.spiflash.enable();
    local bytes = hardware.spiflash.read(0x0000, 4);
    local check = 0;

    // Are the bytes all 0xC3?
    foreach (byte in bytes) {
        if (byte == 0xC3) check++;
    }

    if (check >= 4) {
        // Device is activated so go to application code
        startApplication();
    } else {
        // Device is not activated so bring up Bluetooth LE
        startBluetooth();
    }
} else {
    // Unsupported imp: just start the app anyway to ignore Bluetooth
    startApplication();
}

Upon a successful transfer of enrollment data from the example mobile app, the Squirrel code configures the compatible imp’s settings accordingly, writes the signature to the SPI flash and triggers a Squirrel restart. This ensures that when Squirrel is running again, it takes the application code path.

A real-world application would need to allow end-users to re-configure their devices, perhaps by pressing a reset button on the product which would trigger code that clears the signature from the SPI flash so that upon restart the unit once more enables Bluetooth and waits for configuration data. Alternatively, such an app might require the Bluetooth subsystem to be continuously active and not just for the initial end-user activation period. This example’s web UI (accessed at the agent URL) presents a button which will clear the code for demo purposes. A second button can be used to restart the device.

Bluetooth Setup

The Squirrel code’s Bluetooth component is straightforward and included in the form of a class library, BTLEBlinkUp. Its constructor takes (in order) a table of Bluetooth service UUIDs, and the objects representing the imp GPIO pins and UART connected to the Bluetooth LE sub-system.

You must provide a table of Bluetooth service UUIDs. This table must include all of the following keys:

  • blinkup_service_uuid — the BlinkUp service.
  • ssid_setter_uuid — the WiFi SSID writing service characteristic.
  • password_setter_uuid — the WiFi password writing service characteristic.
  • planid_setter_uuid — the activation plan ID writing service characteristic.
  • token_setter_uuid — the activation token writing service characteristic.
  • blinkup_trigger_uuid — the activation trigger writing service characteristic.
  • wifi_getter_uuid — the clear WiFi settings trigger writing service characteristic.
  • wifi_clear_trigger_uuid — the device WiFi scan reading service characteristic.

The code will throw an exception if any of these keys are absent. In the example, the keys’ values are set in the function initUUIDs(), which returns a table suitable for demonstration purposes, but should not be included in your production code.

Note If you change any of these keys’ values in the Squirrel code, you will also need to update the example app code, and vice versa.

This is the set-up code:

function startBluetooth() {
    // Instantiate the BTLEBlinkUp library according to type:
    // imp004m and imp006 require different BLE firmware
    bt = BTLEBlinkUp(initUUIDs(), (iType == "imp004m" ? BT_FIRMWARE.CYW_43438 : BT_FIRMWARE.CYW_43455));

    // Don't use security
    bt.setSecurity(1);

    // Register a handler to receive the agent's URL
    agent.on("set.agent.url", function(data) {
        // Agent URL received so run the Bluetooth LE code
        doBluetooth(data);
    }.bindenv(this));

    // Now try and get the agent's URL from the agent
    agent.send("get.agent.url", true);

    // Set up a timer to check if the agent.send() above was un-ACK'd
    // This will be the case with an **unactivated production device**
    // because the agent is not instantiated until after activation
    agentTimer = imp.wakeup(10, function() {
        agentTimer = null;
        doBluetooth();
    }.bindenv(this));
}

function doBluetooth(agentURL = null) {
    // If we didn't call this from the timer, clear the timer
    if (agentTimer != null) {
        imp.cancelwakeup(agentTimer);
        agentTimer = null;
    }

    // Store the agent URL if present
    if (agentURL != null) bt.agentURL = agentURL;

    // Set the device up to listen for BlinkUp data
    bt.listenForBlinkUp(null, function(data) {
        // This is the callback through which the BLE sub-system communicates
        // with the host app, eg. to inform it activation has taken place
        if ("address" in data) server.log("Device " + data.address + " has " + data.state);
        if ("security" in data) server.log("Connection security mode: " + data.security);
        if ("activated" in data && "spiflash" in hardware && imp.info().type == "imp004m") {
            // Write BlinkUp signature post-configuration
            hardware.spiflash.enable();
            local ok = hardware.spiflash.write(0x0000, "\xC3\xC3\xC3\xC3", SPIFLASH_PREVERIFY);
            if (ok != 0) server.error("SPIflash write failed");
        }
    }.bindenv(this));

    server.log("Bluetooth LE listening for BlinkUp...");
}

The BTLEBlinkUp instance’s listenForBlinkUp() now powers up the imp’s Bluetooth radio and configures it for use (the call to the bluetooth class’ open() method. The firmware for the Bluetooth radio, which is passed into open(), is stored within the Squirrel, but a real-world application might place this in the SPI flash within the factory and read it back at this point.

With Bluetooth active, the code establishes the BlinkUp service and seven characteristics using the default or supplied UUIDs (see above). Four of the characteristics receive (‘write’) respectively the WiFi SSID, the WiFi password, the enrollment token and the plan ID. The fifth characteristic works in the opposite direction: it provides information the mobile app can read back from the imp: a list of the WiFi networks the imp can see around it. The fifth is a dummy: its write operation does not store data but is used to trigger the writing of the received WiFi and enrollment data to the imp storage. Likewise, writing to the sixth characteristic triggers the clearance of the imp’s stored WiFi credentials.

The code also configures the Device Info service and then calls servegatt() to make these services available to any devices which subsequently connect to the imp. Establishing a handler to deal with such connections is performed by calling the onconnect() method, which takes the handler as its argument. BTLEBlinkUp makes use of this internally; it supports a separate callback to communicate back with the host Squirrel. This second callback is registered with either the onConnect() (note the case) function or listenForBlinkUp() and triggered by connection and disconnection events, and to inform the host that device activation has taken place.

// Device information service
service = { "uuid": 0x180A,
            "chars": [
                { "uuid": 0x2A29,
                  "value": "Electric Imp" },                          // manufacturer name
                { "uuid": 0x2A25,
                  "value": hardware.getdeviceid() },                  // serial number (device ID)
                { "uuid": 0x2A24,
                  "value": imp.info().type },                         // model number (imp type)
                { "uuid": 0x2A23,
                  "value": (agentURL != null ? agentURL : "null") },  // system ID (agent URL)
                { "uuid": 0x2A26,
                  "value": imp.getsoftwareversion() }]                // firmware version (impOS release)
            };

// Add services to the list of GATT services offered
services.append(service);
ble.servegatt(services);

Finally, we set the Bluetooth LE advertising mechanism to send out a simple advertisement — this is what the mobile app looks for when the user initiates a scan in the iOS app. This is done within the library code by calling startadvertise() on the bluetooth object and passing in the advertising data. This is a sequence of 8-bit values: the first byte is the length of the data that follows (17 bytes), followed by a single-byte data type value set by the Bluetooth LE standard: 0x07 which indicates that what follows is a list of all the 128-bit service UUIDs provided by the device. Here there is only one, as set in the service definition above. In the advertising data it is provided in big endian format.

Device Activation

The enrollment data received from the example app is collated by the imp in a table, _blinkup, which is initialized with keys for the data itself, a flag (updated) which is set when data is written so that Squirrel can be sure that there will be enrollment data to use, and a function, update(), that is called by writing the dummy characteristic described above. This function calls two key imp API methods — imp.setwificonfiguration() and imp.setenroltokens() — to apply the new WiFi credentials and enrollment data. These will be applied when the imp next connects, so the code reboots the imp, forcing the new credentials to be used and connection progress to be shown on the imp’s BlinkUp LED.

// Define the Enrollment Token setter characteristic
chrx = {};
chrx.uuid <- _uuids.token_setter_uuid;
chrx.write <- function(conn, v) {
    _blinkup.token = v.tostring();
    _blinkup.updated = true;
    server.log("Enrolment Token set");
    return 0x0000;
}.bindenv(this);

service.chars.append(chrx);

Before the restart, the code needs to write the ‘I am configured’ signature to SPI flash so that on restart the device will run the application code path. This is triggered via the callback passed into the BTLEBlinkUp listenForBlinkUp() method: the callback receives the "activated" message, as can be seen in the doBluetooth() function in the code snippet above.

Agent Code

The sample Squirrel code provides a web UI which is served by the agent and can be visited by entering the agent URL into a desktop browser, or by selecting the Open Agent URL option presented by the app after the target device has activated.

In addition to displaying device information — in the real world, an application might present a device management UI, for example — the web interface allows you to clear the activation signature the code applies to the device’s SPI flash. This is for the purposes of testing. The signature is set so that, after a restart, the device runs is expected application flow rather than the pre-application BlinkUp flow. Clearing the signature, followed by a restart, puts the device back into BlinkUp mode.

Important Notes On The Example Code

Bluetooth Bonding

While impOS supports the use of a six-digit PIN code to authorize a connection between the imp and a mobile device, it does not support bonding, the process by which the mobile device records that the Bluetooth peripheral may connect multiple times under the same credentials. If you wish to make use of encryption, for which using a PIN is required, every attempt the mobile device makes to the imp will trigger a request for the PIN.

iOS handles the pin request for you automatically, when your code attempts to read or write a characteristic’s value, but will not notify your app whether the user has entered a correct PIN, an in correct PIN, or hit the PIN request dialog’s Cancel button. The only indication provided is the (eventual) successful read of the value.

iOS Bluetooth Attribute Caching

By default, iOS caches the attribute information it discovers from devices, as does Android. This ensures that future scans need not use the radio, conserving power. However, it also means if you change your Squirrel app’s served attributes during development, they will not be immediately detected by the app.

The easiest approach to dealing with this is to disable then re-enable Bluetooth on your Apple device — try switching to Airplane mode and then back again. You may also need to power-cycle the device.

Clearing WiFi Settings

If you tap the mobile app’s Clear WiFi Settings button, the target imp-enabled device will remain connected until it is restarted or power-cycled. At this point it will not be able to reconnect. The mobile app will not detect it for ten seconds — the point at which Squirrel is started up in the situation where the imp cannot connect at start-up.

If you cleared the device’s WiFi settings after transmitting WiFi credentials by Bluetooth, the ‘has activated’ signature will be present and prevent the Bluetooth services being made available. You will therefore not be able to configure the device using the example app. Please use the Electric Imp mobile app to re-connect the device and then use the agent-served Web UI to clear the activation record.

If you wish to avoid this, comment out the line

local ok = hardware.spiflash.write(0x0000, "\xC3\xC3\xC3\xC3", SPIFLASH_PREVERIFY);

in the device code doBluetooth() function to prevent the signature from being written after Bluetooth BlinkUp. In this case, the Bluetooth code path will always be taken; the application code path will never be taken.