Skip to main content

bluetooth.startscan(callback)

Causes the imp to begin scanning for GAP advertisements

Availability

Device
Only available on the imp004m and imp006 (impOS 42)

Parameters

Name Type Description
callback Function A function to process any detected advertising data

Returns

Nothing

Description

This method initiates scanning and registers a function that will be called when a compatible imp detects an advertisement signal.

The callback function has a single parameter of its own, adverts, into which an array of detected advertisements is passed. Each advert is a table with the following keys:

Key Data Type Description
type Integer The type of advertisement detected (see below)
rssi Integer The signal strength (-127 to 20) of the received data in dBm
address String The peer’s address as a 12-character hexadecimal string
addresstype Integer 0 if the address is public, 1 if the address is random
data Blob The raw data received. Up to 31 bytes in length
time Integer The time of receipt by impOS, as an integer millisecond time against the same clock as hardware.millis()
rule Integer The index (integer, zero-based) of the filter that allowed this advert. This is intended to both assist debugging of rules, and to allow Squirrel to fast-path adverts which match a certain rule (see bluetooth.setscanfilter() examples). This will be zero if the default accept-all filter is in use.

impOS™ will add as many advertisements to the adverts array as it can but will not add latency to achieve this.

impOS doesn’t interpret or validate the raw advertisement data (provided as data in the advert table) in any way other than to cap the length at the specified 31 bytes. Specifically, data is not guaranteed to contain syntactically valid advertising or scan data. Your application code should therefore take great care when parsing data.

Typically, a user will want to process advertising and scan response packets together; your application is responsible for mapping advertisement to scan response.

Advertisement Type Constants

Bluetooth Constant Value Description
ADV_IND 0 Connectable undirected advertisement
ADV_DIRECT_IND 1 Connectable directed advertisement
ADV_SCAN_IND 2 Scannable undirected advertisement
ADV_NONCONN_IND 3 Non-connectable undirected advertisement
SCAN_RSP 4 Scan response

Note These constants are not defined in Squirrel — please use the integer value in comparisons.

Example Code

Passive iBeacon Scanning

Scan for iBeacons and save each in a table as they are found.

bt <- null;

// Beacons
beacons <- [];

function makePrettyUUID(uuid) {
    return uuid.slice(0,9) + "-" + uuid.slice(8,12) + "-" + uuid.slice(12,16) + "-" + uuid.slice(16);
}

function reportBeacon(beacon) {
    server.log("Beacon added: " + makePrettyUUID(beacon.uuid) + " (" + beacon.majorString + "/" + beacon.minorString + ")");
}

function addBeacon(beacon) {
    beacons.append(beacon);
    reportBeacon(beacon);
}

try {
    // Instantiate BT on imp006 breakout
    bt = hardware.bluetooth.open();
} catch (err) {
    server.error(err);
    return;
}

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

// Filter out  advertisements whose 'data' contains the iBeacon preamble bytes
// and whose type is ADV_NONCONN_IND
bt.setscanfilter([{ "type" : 3, "data" : "\x02\x01\x06\x1A\xFF\x4C\x00\x02\x15" }]);

server.log("Scanning...");
bt.startscan(function(adverts) {
    foreach (advert in adverts) {
        // Convert 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.majorValue == beacon.majorValue && 
                    aBeacon.minorValue == beacon.minorValue) {
                    got = true;
                    break;
                }
            }

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

Active iBeacon Scanning

The following code updates the code shown above to support active scanning for beacon scan response packets. See bluetooth.startadvertise() for code creating and issuing the scan response.

bt <- null;

// Beacons
beacons <- [];
appBeaconUUID <- "9277830ab2eb490fa1dd7fe38c492ede";

function makePrettyUUID(uuid) {
    return uuid.slice(0,9) + "-" + uuid.slice(8,12) + "-" + uuid.slice(12,16) + "-" + uuid.slice(16);
}

function reportBeacon(beacon) {
    server.log("Beacon added: " + makePrettyUUID(beacon.uuid) + " (" + beacon.majorString + "/" + beacon.minorString + ")");
}

function addBeacon(beacon) {
    // Check for OUR beacons -- add extra fields for them
    if (beacon.uuid == appBeaconUUID) {
        beacon.appID <- 0x00FF;
        beacon.devID <- "";
    }

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

try {
    // Instantiate BT on imp006 breakout
    bt = hardware.bluetooth.open();
} catch (err) {
    server.error(err);
    return;
}

// Set scan parameters for active scanning (required for scan response detection)
bt.setscanparams(true, 100, 100);

// Filter out  advertisements whose 'data' contains the iBeacon preamble bytes
bt.setscanfilter([{ "data" : "\x02\x01\x06" }]);

server.log("Scanning...");
bt.startscan(function(adverts) {
    foreach (advert in adverts) {
        if (advert.type == 4) {
            local appID = (advert.data[6] << 8) + advert.data[5];
            local got = false;
            foreach (aBeacon in beacons) {
                if ("appID" in aBeacon) {
                    if (aBeacon.appID == appID && aBeacon.devID.len() == 0) {
                        aBeacon.devID = advert.data.tostring().slice(9);
                        server.log("Response received (device ID: " + aBeacon.devID + ")");
                        break;
                    }
                }
            }

            return;
        }

        // Convert 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.majorValue == beacon.majorValue && 
                    aBeacon.minorValue == beacon.minorValue) {
                    got = true;
                    break;
                }
            }

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