Skip to main content

How To Use Bluetooth With The imp006

Advertising, Scanning and Data-exchange

impOS™ 42 supports the use of Bluetooth LE (Low Energy) on the imp006. Though the imp006 does not incorporate Bluetooth LE itself, it is able to connect to Bluetooth LE modules via dedicated pins. For example, Electric Imp’s imp006 Breakout Board features a Murata LBEE5HY1MW module which contains a Cypress Semiconductor CYW43455 chip that delivers Bluetooth 5.0.

Working With Bluetooth

The following guide focuses on the imp006 Breakout Board, but the principles it covers apply to all imp006-based devices intended to be used for Bluetooth communications.

For full details of impOS’ Bluetooth API, please see the imp API documentation.

Initializing Bluetooth

In order to make use of the imp006’s Bluetooth capability, you will need to initialize the Bluetooth hardware using the imp API method open(). This returns an instance of the imp API’s bluetooth class, and you will use this instance to manage all Bluetooth communications.

On the imp006, running impOS 41.28 and up, open() has no parameters. For impOS 41.27 anmd under, pass in the single argument: BT_FIRMWARE.CYW_43455 for which you will also need to add #require "bt_firmware.lib.nut:1.0.0" at the top of your code.

It’s a good idea while debugging to wrap your open() call in a try... catch structure to trap any errors you may encounter during development. For example:

// Add a global variable to hold the bluetooth instance
bt <- null;

local start = hardware.millis();

try {
    // Instantiate BT on imp006 breakout
    bt = hardware.bluetooth.open();
    server.log("BLE initialized after " + (hardware.millis() - start) + " ms");
} catch (err) {
    server.error(err);
    server.log("BLE failed after " + (hardware.millis() - start) + " ms");
    return;
}

The global variable bt now points to a bluetooth instance that is ready for use. All of the methods described in this article target this instance.

Squirrel Suspension And Bluetooth

When a device’s Squirrel Virtual Machine is suspended, no new callbacks (for Bluetooth or for any other reason) can be executed until the VM is resumed. When this happens, the whole Bluetooth stack is paused so that it does not cause new events requiring Squirrel execution. This can mean that any in-progress operations will be paused, and may cause large latency spikes in Bluetooth operations — even ones which don’t require Squirrel execution.

In addition, GATT read and write callbacks are called in a context where suspending is not possible. Therefore, if any operation that suspends the VM occurs within these callbacks, a Squirrel error will be thrown.

With these two factors in mind, it is strongly recommended that the RETURN_ON_ERROR policy is selected as the first line in Squirrel that uses Bluetooth features.

Shutting Down Bluetooth

Should you need to shut down the Bluetooth sub-system, call the imp API method close():

bt.close();

This is invoked implicitly if bt goes out of scope, which is why we establish bt as a global variable in the code above.

Bluetooth Operation

The Generic Access Profile (GAP)

The Bluetooth standard’s Generic Access Profile (GAP) defines a number of core functions which a Bluetooth LE device can perform, primarily to advertise its presence, to look for other Bluetooth devices with which it might communicate, and to talk to those devices.

Let’s look at the first of these.

GAP Advertising

The imp API provides the method startadvertise() to manage the process of informing other Bluetooth devices that the imp006 can communicate with them. The GAP specification mandates a structure for the information a device will transmit to the network and this includes up to 31 bytes of advertising data. The organization of this data is beyond the scope of this article — for more information, please consult the Bluetooth 5.0 specification and the Bluetooth Core Specification Supplement.

However you format your device’s advertising data, you will need to pass this data as a string or blob into startadvertise()’s first parameter, advert. You will also need to provide arguments for two further parameters: minAdInterval and maxAdInterval. These are the minimum and maximum intervals, in milliseconds, between which the imp006 will send out advertising signals. Both must be between 20ms and 10,240ms, and the maximum must be greater than the minimum, though they can be the same. A common value for both is 100ms.

There is a fourth, optional parameter called scanResponse, but we’ll examine this later, in the section on GAP scanning.

Example 1: Apple iBeacon

The following code shows just one of many advertising applications: the use of the imp006 Breakout Board as a transmitter following Apple’s iBeacon specification.

// Add a global variable to hold the bluetooth instance
bt <- null;

// Define the iBeacon advertising data
iBeacon <- "\x02\x01\x06\x1A\xFF\x4C\x00\x02\x15\x92\x77\x83\x0A\xB2\xEB\x49\x0F\xA1\xDD\x7F\xE3\x8C\x49\x2E\xDE\x00\x01\x00\x02\xC5";

// Start up Bluetooth on the imp006
try {
    bt = hardware.bluetooth.open();
} catch (err) {
    server.error(err);
    return;
}

// Convert the iBeacon data into a hex string for logging
local beaconString = "";
for (local i = 0 ; i < iBeacon.len() ; i++) {
    // Write each byte as hex
    beaconString += format("%02x", iBeacon[i]);

    // Add dashes to separate the Proximity UUID sub-sections
    if (i == 12 || i == 14 || i == 16 || i == 18) beaconString += "-";
}

// Log the details of the iBeacon
server.log("Advertising the following iBeacon:");
server.log("UUID: "  + beaconString.slice(18,54));
server.log("Major: " + beaconString.slice(54,58));
server.log("Minor: " + beaconString.slice(58,62));

// Send out the iBeacon advertisement
bt.startadvertise(iBeacon, 100, 100);

The advertising data is structured in a sequence of data fields, each comprising the size of the field data, the field data type and the field data itself:

  • Data Field 1
    • Size: 0x02 — This field is two bytes long.
    • Data Type: 0x01 — Indicates that the following data contains Bluetooth flags. Note This is the AD Type byte.
    • Data: 0x06 — The flags define the advertising packet as BLE General Discoverable and BR/EDR high-speed incompatible, ie. only broadcasting, not connecting.
  • Data Field 2
    • Size: 0x1A — This field is 26 bytes long
    • Data Type: 0xFF — Indicates that the following data is manufacturer specific.
    • Data:
      • 0x4C00 — Apple’s Bluetooth manufacturer ID in little-endian form (ie. the value is 0x004C).
      • 0x15 — The following iBeacon data size: 21 bytes.
      • 0x9277830AB2EB490FA1DD7FE38C492EDE — The 16-byte iBeacon ‘Proximity UUID’.
      • 0x0001 — The two-byte iBeacon ‘Major’ value (big-endian).
      • 0x0002 — The two-byte iBeacon ‘Minor’ value (big-endian).
      • 0xC5 — A reference RSSI value: the power in dBM at one meter.

Typically, all the iBeacons at a given location, or used by the same organization, will use the same Proximity UUID, with the major and minor values allowing the beacons to be subdivided by use-case or zone. For example:

  • Proximity UUID — Indicates iBeacons at a certain location.
    • Major — Indicates iBeacons on a given floor of that location.
      • Minor — Indicates an iBeacon at a particular place on that floor.

You can use a mobile app like Radius Networks’ Locate (iTunes/Google Play) to detect an imp006 operating as an iBeacon using the above code. You will need to enter the proximity UUID, and major and minor values into the app, which only reports beacons it has been set up to scan for.

To cease broadcasting the advert, just call stopadvertise().

GAP Scanning

While an imp006 is advertising is presence, it may also scan for other Bluetooth devices in the vicinity. Such scans are initiated by calling startscan(), but you will typically want to refine the scan parameters before making that call. The imp API provides two further methods to help you: setscanparams() and setscanfilter().

The first of these establishes the basic parameters of the scan: will it be active or passive, how long will it pause between scans, and will it scan continuously or at some other duty cycle. Each of these scan parameters form parameters for the method: active, interval and window. All are optional, as is setscanparams() itself.

The latter two parameters take integers between 3ms and 10,240ms; window specifically takes a time value derived from the duty cycle percentage. For example, if interval is 200ms and you require a 50 per cent duty cycle, then window needs to be passed 100ms.

The active parameter takes true or false, the latter indicating the scan should be passive. Active scans involve requesting a scan response when an appropriate advertisement is detected. This request is not made during passive scanning, ie. the imp006 does not reveal that it has detected the advertiser.

Filtering Scans

The second scan configuration method, setscanfilter(), allows you to define rules against which all scan results are matched. Only those which are allowed by your rules will be issued to your application by way of the callback function you register using startscan().

You provide setscanfilter() with an array of tables. Each table is a rule configured by its keys and their values. The setscanfilter() documentation provides a full list of these, and they allow you to filter by criteria such as Bluetooth address, the received signal strength, the type of advertisement being detected, and/or byte sequences within the advertisement data.

For example, the following code snippet uses setscanfilter() to ignore all advertisements but the iBeacon mentioned earlier:

// Set scan parameters for passive scanning, a 100ms interval and a 100% duty cycle
bt.setscanparams(false, 100, 100);

// Filter out iBeacons,
// ie. advertisements of 'type' 3 (Non-connectable undirected advertisements),
// and whose 'data' contains the iBeacon preamble bytes
bt.setscanfilter([{ "type" : 3, "data" : "\x02\x01\x06\x1A\xFF\x4C\x00" }]);

With the filter prepared, the code can call startscan(). This method has a single parameter, callback, into which you pass a function that will be called whenever the imp006 detects an advertisement that matches any rules you have provided. The callback has a parameter of its own, called adverts, into which an array of detected advertisements are passed. Each is a table containing a number of keys as listed in the startscan() documentation.

Example 2: Scanning For iBeacons

With the scan set up as above, the startscan() might look something like this:

// Add a global variable to hold the bluetooth instance
bt <- null;

// Add a global array to hold identified beacons
beacons <- [];

// Format the supplied UUID nicely
function makePrettyUUID(uuid) {
    return uuid.slice(0,9) + "-" + uuid.slice(8,12) + "-" + uuid.slice(12,16) + "-" + uuid.slice(16);
}

// Add a discovered beacon to the list of identified beacons and log it
function addBeacon(beacon) {
    beacons.append(beacon);
    server.log("Beacon added: " + makePrettyUUID(beacon.uuid) + " (" + beacon.majorString + "/" + beacon.minorString + ")");
}

// Start up Bluetooth on the imp006
try {
    bt = hardware.bluetooth.open();
} catch (err) {
    server.error(err);
    return;
}

// Set scan parameters for passive scanning
bt.setscanparams(false, 100, 100);

// Block all advertisements but those whose 'data' contains the iBeacon preamble bytes,
// and whose type matches Bluetooth type ADV_NONCONN_IND
bt.setscanfilter([{ "type" : 3, "data" : "\x02\x01\x06\x1A\xFF\x4C\x00" }]);

// Start scanning for beacons
bt.startscan(function(adverts) {
    foreach (advert in adverts) {
        // Convert the advert's data payload to hex string
        local payload = "";
        for (local i = 0 ; i < advert.data.len() ; i++) {
            payload += format("%02x", advert.data[i]);
        }

        // Make up a beacon record
        local beacon = {};
        beacon.uuid <- payload.slice(18, 50);
        beacon.majorString <- payload.slice(50, 54);
        beacon.minorString <- payload.slice(54, 58);

        if (beacons.len() > 0) {
            local got = false;

            foreach (aBeacon in beacons) {
                if (aBeacon.uuid == beacon.uuid && aBeacon.majorString == beacon.majorString && aBeacon.minorString == beacon.minorString) {
                    got = true;
                    break;
                }
            }

            if (!got) addBeacon(beacon);
        } else {
            addBeacon(beacon);
        }
    }
});

This code can be easily expanded to approximate beacon distance (using the advert table’s rssi key). This replicates the functionality of the Radius Networks Locate app mentioned in the previous example, which you can do if you have at more than one imp006 or imp004m Breakout Boards.

Scans can be suspended by calling stopscan().

Scan Responses

In response to the scan request, the advertiser will transmit a scan response. The imp006 will do this if you have made use of startadvertise()’s fourth parameter, scanResponse. This takes up to 31 bytes of string or blob data. Again, like the startadvertise() advert parameter’s argument, it is up to you to format your scanResponse correctly — the imp API cannot do this for you because the data is so highly application specific.

As an example, you can send the imp’s device ID as the standard ‘local name’, which we include in its complete form, indicated by the Bluetooth data type value 0x09. We also prefix the response with the standard flags field (type 0x01, see above) and the beacon’s major and minor values, supplied as 16-bit UUIDs (data type 0x03). This code is included in the advertiser code, not the scanner code:

// Set scan response
scanResponse <- "\x02\x01\x06\x04\x03\x00\x01\x00\x02";
local name = hardware.getdeviceid();
local nameDataLength = name.len() + 1;
// NOTE '\x09' is the data type: 'complete local name'
scanResponse += (nameDataLength.tochar() + "\x09" + name);

Finally, to advertise with a response, include scanResponse in the startadvertise() call:

bt.startadvertise(iBeacon, 100, 100, scanResponse);

You can now update the scanner code (from Example 2, above) as follows:

  • Change the scan parameters to use active scanning.
  • Update the scan filter to look for response-providing beacons (type 2).
  • Update the scan filter to include scan responses (type 4) with the correct signature.
bt.setscanparams(true, 100, 100);
bt.setscanfilter([{ "type" : 2, "data" : "\x02\x01\x06\x1A\xFF\x4C\x00" },
                  { "type" : 4, "data" : "\x02\x01\x06\x04\x03" }]);
  • Include code in the callback registered using startscan() to process scan responses (type 4).
bt.startscan(function(adverts) {
    foreach (advert in adverts) {
        if (advert.type == 4) {
            // Get the major and minor beacon values embedded in the response
            local aMajor = format("%02x%02x", advert.data[5], advert.data[6]);
            local aMinor = format("%02x%02x", advert.data[7], advert.data[8]);
            local got = false;
            foreach (aBeacon in beacons) {
                if (aBeacon.majorString == aMajor && aBeacon.minorString == aMinor && aBeacon.devID.len() == 0) {
                    // We have a beacon at major, minor, but no ID for it yet, so extract and record that now
                    advert.data.seek(11, 'b');
                    aBeacon.devID = advert.data.readstring(99);
                    server.log("Response received (device ID: " + aBeacon.devID + ")");
                    break;
                }
            }

            return;
        }

        try {
            // Convert the advert's data payload to hex string
            local payload = "";
            for (local i = 0 ; i < advert.data.len() ; i++) {
                payload += format("%02x", advert.data[i]);
            }

            // This is a beacon so record it
            local beacon = {};
            beacon.uuid <- payload.slice(18, 50);
            beacon.majorString <- payload.slice(50, 54);
            beacon.minorString <- payload.slice(54, 58);

            if (beacons.len() > 0) {
                local got = false;

                foreach (aBeacon in beacons) {
                    if (aBeacon.uuid == beacon.uuid && aBeacon.majorString == beacon.majorString && aBeacon.minorString == beacon.minorString) {
                        got = true;
                        break;
                    }
                }

                if (!got) addBeacon(beacon);
            } else {
                addBeacon(beacon);
            }
        } catch (err) {
            // Ignore errors - they're from packets we don't care about
        }
    }
});
  • Update the addBeacon() function to add a device ID field if the beacon is one of ours (it may not be). We use the presence of this field in the response-parsing code above to check whether we have seen a response from this beacon already:
function addBeacon(beacon) {
    // Check for OUR beacons -- add extra fields for them
    if (beacon.uuid == "9277830ab2eb490fa1dd7fe38c492ede") {
        beacon.devID <- "";
    }

    beacons.append(beacon);
    reportBeacon(beacon);
}

Device-to-Device Communications

The Bluetooth GAP specification also covers the establishment of one-to-one connections between nearby Bluetooth devices. Typically, one device will issue a connection request to another.

Making Connections

If the imp006 is to respond to such a request — it currently can only respond to requests, not initiate them — you must register a function that will be called when a connection request is received. You do this by calling the imp API method onconnect().

The supplied function needs a parameter of its own into which a new btconnection instance is passed. This btconnection instance provides a means for you to manage the connection now established between the two devices. btconnection has the following methods:

  • address() — Returns the Bluetooth IP address of the remote device as a 12-character hexadecimal string.
  • onclose() — Registers a function that will be called when the connection is closed by any means outside of the application’s (or impOS’) control, eg. the remote device shuts down or goes out of range.
  • close() — The application’s means to close the connection.

Closed btconnection instances, or those that go out of scope, cannot be used or re-used. If this happens while the devices are connected and the remote device subsequently breaks the connection, your application will not be notified (eg. via onclose()).

If you want the imp006 to stop handling connection attempts, call onconnect() again, but null in place of the handler function.

The Bluetooth Generic Attribute Profile (GATT)

How do two Bluetooth-enabled devices exchange information? To do so, both devices make use of the standard’s Generic Attribute Profile, or GATT. This profile defines a client-server relationship that can be established between two devices.

The client typically sends a request to the GATT server and is able to read data (an ‘attribute’) from the server and write values back to that attribute. The server makes its attributes available to the client when the client connects to it. Clients can also operate as servers, and vice versa.

The imp006 can become a GATT server by calling the imp API method servergatt() and passing in an array of tables. Each table defines a ‘service’ provided by the server: a UUID to identify the service and a subsidiary array of tables, each of which defines a ‘characteristic’ of that service, ie. a data point. A full list of the keys that may be included in the definition of a characteristic can be found in the servergatt() documentation.

Many characteristics are predefined by the Bluetooth standards, but you can make use of your own by supplying a non-reserved UUID.

When configured using the imp API, each characteristic can include references to getter (read) and setter (write) functions. These functions must have at least one parameter: a btconnection object which provides information about the connected device. The read function will return the value of the characteristic; the write function has a second parameter, a string or blob value from which it updates the value of the characteristic. The functions can return zero, or no value at all to indicate success; returning a non-zero value indicates failure — the value is typically an application-specific error code.

Note If you plan to advertise the services offered by the GATT server by including service UUIDs in the BLE advertisement payload, you should ensure your code calls servegatt() before it calls startadvertise().

GATT Security

impOS supports GATT connection security. This is enabled by default, but can be disabled by calling bt.setsecurity(1);, where bt is your Bluetooth instance.

Example: Refreshing An imp006’s WiFi Credentials

Using what we have learned so far, we can build an application that is able to receive new WiFi credentials via Bluetooth and, if requested, apply them.

The first thing we do is establish a data structure to hold the imp006’s WiFi credentials and manage updates, data, and a record of the connection connection:

connection <- null;

// Set up a table to hold WiFi data
data <- {};
data.ssid <- "";
data.pwd <- "";
data.updated <- false;
data.update <- function() {
    local bs = "############################".slice(0, this.pwd.len());
    server.log("Switching to SSID: " + this.ssid + ", PSK: " + bs);
    imp.setwificonfiguration(this.ssid, this.pwd);
    if (incoming != null) incoming.close();
    imp.onidle(function() {
        imp.reset();
    });
};

The next step is to set up the service that the imp006 will provide. This has three characteristics: one each for writing the network name (SSID) and the password to data, and a third to trigger the application of those credentials:

server.log("Setting up services...");

local service = {};
// WiFi Credential Refresh Service
service.uuid <- "FADA47BEC45548C9A5F2AF7CF368D719";
service.chars <- [];

local chara = {};
// Set SSID characteristic
chara.uuid <- "5EBA195632D347C681A6A7E59F18DAC0";
chara.write <- function(conn, value) {
    data.ssid = value.tostring();
    server.log("Written SSID");
    data.updated = true;
};
service.chars.append(chara);

// Set PWD characteristic
chara = {};
chara.uuid <- "ED694AB947564528AA3A799A4FD11117";
chara.write <- function(conn, value) {
    data.pwd = value.tostring();
    server.log("Written PWD");
    data.updated = true;
};
service.chars.append(chara);

// Set dummy characteristic to trigger the WiFi update
chara = {};
chara.uuid <- "F299C3428A8A4544AC4208C841737B1B";
chara.write <- function(conn, value) {
    if (data.updated) data.update();
};
service.chars.append(chara);

bt.setsecurity(1);
bt.servegatt([service]);
server.log("Serving GATT...");

The code sets UUIDs for the service and each of the characteristics, for which it provides write functions. The first two update data’s ssid and pwd properties, and record the fact by setting its updated flag. This is checked in the third characteristic’s write function. This doesn’t actually write any data — any value passed in from the connected Bluetooth device is ignored — but uses this as a proxy to trigger the application of the new credentials — if there are any; this is why data’s updated flag is checked. Finally, the service is placed in an array and passed into servergatt().

The characteristics defined above can’t be read or written until a remote device connects to the imp006. We make this possible by now adding the following code:

bt.onconnect(function(conn) {
    connection = conn;
    server.log(conn.address() + " connected");

    conn.onclose(function() {
        server.log(connection.address() + " disconnected");
    });
});

server.log("Awaiting connections...");

Here we define a function that will be called on an attempt to connect. The connection is referenced by the global variable connection (set earlier) to keep it in scope and available for future use: ie. in the function registered with the onclose() method.

Finally, we need to signal to other devices that the im006 can be contacted, and for this we use the GAP advertising process described earlier in this guide:

bt.startadvertise("\x11\x07\x19\xD7\x68\xF3\x7C\xAF\xF2\xA5\xC9\x48\x55\xC4\xBE\x47\xDA\xFA", 100, 100);
server.log("Advertising services...");

What does the first of these lines do? As we saw earlier, it sets the advertising data and the minimum and maximum advertising intervals — the latter two are both set to 100ms.

The advertising data makes use of the Bluetooth specification as follows: the first byte is the data size (17 bytes); it’s followed by a data-type indicator: 0x07 indicates that what follows is a complete list of 128-bit service UUIDs provided by the device. You can find a list of pre-defined advertising data types here. After this come the UUIDs themselves. In this case, there is only one, the service UUID we set in the code at the start of this example. You should note that though the imp API takes UUIDs in little-endian form, the specification requires them to be transmitted in big-endian form. This is why the UUID bytes are reversed in the advertising data.

With this code in place, it’s possible to write a mobile app which can scan for Bluetooth devices offering a service with the UUID set above — this is made possible by the advertising signal. Detecting the device may then result in further discovery operations as the app interrogates the imp006 for the characteristics of that service. The app may then connect to the imp006 and this allows it to write data to the module’s GATT server and, as we’ve seen, trigger the device to apply new WiFi credentials, disconnect from the network, restart and reconnect, this time to the new network.

  • Check out our Bluetooth BlinkUp sample code for complete Squirrel code that incorporates the example above and includes an iOS app able to communicate with the imp via Bluetooth.

Monitoring Your imp006’s GATT Server

You can make use of Punch Through’s mobile app LightBlue Explorer (on iOS and Android) to scan for your imp006 and its GATT services. For example, you might set up the imp006 to serve standard Bluetooth LE device information:

// Device information service
local infoService = { "uuid": 0x180A,
                      "chars": [
                        { "uuid": 0x2A29, "value": "Electric Imp" },           // Manufacturer name
                        { "uuid": 0x2A25, "value": hardware.getdeviceid() },   // Serial number (the device ID)
                        { "uuid": 0x2A24, "value": imp.info().type },          // Model number (the imp type)
                        { "uuid": 0x2A26, "value": imp.getsoftwareversion() }] // Firmware version (impOS version)
};

bt.servegatt([infoService]);

This will be detected and presented by LightBlue Explorer:

Mobile OS Bluetooth Attribute Caching

By default, iOS and Android cache the attribute information they discover about devices. This is done to ensures that future discovery requests need not use the radio, conserving power. This makes sense because Bluetooth peripherals generally do not change the services they offer, or the characteristics of those services. However, it also means that if you change your Squirrel app’s served attributes during development, the changes will not be detected by the app. For example, if you replace the service listed above with a different one, apps like LightBlue will continue to show the Device Information details. This is because the host OS is providing that information from its cache.

The easiest approach to dealing with this is to disable then re-enable Bluetooth on your mobile device. You may also need to power-cycle the device. Both actions cause the device’s Bluetooth peripheral cache to be cleared, forcing the OS to go to the device for service information.

Security

impOS allows you to set the minimum connection security level the imp will then require for all GAP connections made to its current bluetooth instance. You make your choice with the imp API method setsecurity(). Using the scheme used in the Bluetooth 4.2 standard, it provides three security levels:

securityLevel Standard Name imp IO Capabilities
1 LE Security Mode 1 Level 1: No security NoInputNoOutput
3 LE Security Mode 1 Level 3: Authenticated pairing with encryption KeyboardOnly
4 LE Security Mode 1 Level 4: Authenticated LE Secure Connections pairing with encryption KeyboardOnly

Note Other values are illegal and will cause a Squirrel error.

Security levels 3 and 4 require a six-digit pairing code which is passed into bluetooth.setsecurity() as its second argument. Security level 4 causes any connection which only achieves level 3 or lower to be automatically closed after pairing. Squirrel will not be notified when this happens.

The default setting is level 4. A random pairing code is generated when bluetooth.open() is called. This code can be retrieved using bluetooth.getsecuritycode(). It will be changed by calling bluetooth.setsecurity().

The imp initiates the pairing procedure when a new connection is made, and will not answer GATT queries until pairing completes successfully. And only then will the Squirrel GAP connection callback registered using bluetooth.onconnect() be called.

Choosing Pairing Code Values

Bluetooth GAP connections are authenticated by pairing code. Unfortunately, this method can leak one new bit of the code to an active adversary on each run of the protocol. Because the code is fixed at six decimal digits, only 20 failed runs are needed to recover the whole code. For this reason, you should choose your code carefully.

There are four possible levels of pairing code security:

Mode Lifetime Scope Security Strength Notes
1 Forever Global None Effectively opts out of security
2 Forever Per-imp Limited Allows the pairing code to be etched on the product or printed on the packaging
3 Squirrel Bluetooth session Squirrel Bluetooth session Good This is the default
4 Time-limited Single connection Strong

For mode 2, you generate a random pairing code and both store it in the device under test (DUT) and either relay it to the label printing or case-etching station so the end-user can read it and enter it into the mobile app that is being used to activate the device, or store it in a database from which it can be retrieved by the mobile app.

For mode 3, impOS chooses a random pairing code when a bluetooth object is instantiated. This is the default behavior and provides good security, but requires your product to incorporate a means of relaying the code to the end-user, such as a display.

Mode 4 provides the best security. The imp chooses a new random pairing code after every successful connection and every time a fixed period of time has elapsed. This period must be long enough to allow pairing to take place, but short enough to limit exposure, ie. minutes rather than seconds (too short) or hours (too long). Again, your product must incorporate a means of relaying the latest code to the end-user.