Advanced Examples
Connecting to Bluetooth Sensors
Example - The following example demonstrates how to connect to a Calypso Ultrasonic Wind instrument via Bluetooth. The process includes scanning for nearby sensors, discovering characteristics and reading data.
load("senquip.js");
load("api_events.js");
load("api_bt_gap.js");
load("api_bt_gattc.js");
load("api_timer.js");
load("api_math.js");
load("api_serial.js");
load("api_endpoint.js");
let current_state = "RESET";
let state_entry_time = 0;
let SCAN_TIME_MS = 4000;
let conn = null;
let sensors = [];
let sensor_index = 0;
function log(s) {
UDP.send(s);
}
function change_state(new_state) {
log("State change: " + current_state + " --> " + new_state);
current_state = new_state;
state_entry_time = Sys.uptime();
}
function time_in_state_s() {
return Sys.uptime() - state_entry_time;
}
function process_state() {
log("State: " + current_state + " Time: " + JSON.stringify(time_in_state_s()));
let state_functions = {
RESET: function () {
if (time_in_state_s() > 4) {
change_state("SCAN");
}
},
SCAN: function () {
sensor_index = 0;
// We now scan for the bluetooth advertising data of nearby sensors
// This will trigger the GAP.EV_SCAN_RESULT event for each advertisement found.
// To limit the scan to only the sensors we are interested in, we MUST filter using
// the name of the sensor (in this case, it starts with "ULTRA")
// Without this filter, there can be many advertisements, and the device will not be able to handle them all.
GAP.scan(SCAN_TIME_MS, false, "ULTRA");
change_state("WAIT");
},
WAIT: function () {
// Wait for an async event, and eventually timeout
if (time_in_state_s() > 60) {
change_state("RESET");
}
},
CONNECT: function () {
// Connect to the next sensor in the list
if (sensors.length === 0) {
change_state("SCAN");
return;
}
if (sensor_index >= sensors.length) {
sensor_index = 0; // Reset index for next cycle
}
log("CONNECT");
// Connect to the sensor at the current index
// We then wait for the GATTC.EV_CONNECT event to be triggered
GATTC.connect(sensors[sensor_index].addr);
// Wait for connection and discovery events
change_state("WAIT");
},
CONNECTED: function () {
// Connected to the sensor
},
DISCONNECT: function () {
// Disconnect from the current sensor
// This state is not used in this example, but is shown for completeness
if (!conn) {
return;
}
log("Disconnecting from", JSON.stringify(conn));
GATTC.disconnect(conn.connId);
change_state("WAIT");
},
};
let state_function = state_functions[current_state];
if (typeof state_function === "function") {
state_function();
}
}
Timer.set(1000, Timer.REPEAT, process_state, null);
function sensor_in_list(sensor_addr) {
for (let i = 0; i < sensors.length; i++) {
if (sensors[i].addr === sensor_addr) {
return true;
}
}
return false;
}
Event.on(
GAP.EV_SCAN_RESULT,
function (ev, evdata) {
let sr = GAP.getScanResultArg(evdata);
let name = GAP.parseName(sr.advData);
log("EV_SCAN_RESULT" + JSON.stringify(sr));
if (sensor_in_list(sr.addr) === false) {
// Found a new sensor, add to our sensor list
sensors.push({ name: name, addr: sr.addr });
log("NEW SENSOR:" + JSON.stringify(name) + "addr " + JSON.stringify(sr.addr));
}
},
null
);
Event.on(
GAP.EV_SCAN_STOP,
function () {
log("SCAN_STOP");
// If we found no sensors, keep scanning
if (sensors.length === 0) {
change_state("SCAN");
} else {
change_state("CONNECT");
}
},
null
);
function discover() {
if (!conn) {
return;
}
// Discover characteristics on connected device
// This will trigger the GATTC.EV_DISCOVERY_RESULT event for each characteristic found.
if (!GATTC.discover(conn.connId)) {
change_state("RESET");
}
}
Event.on(
GATTC.EV_CONNECT,
function (ev, evdata) {
log("EV_CONNECT " + sensors[sensor_index].name);
change_state("CONNECTED");
conn = GATTC.getConnectArg(evdata);
discover();
},
null
);
Event.on(
GATTC.EV_DISCONNECT,
function () {
log("EV_DISCONNECT");
conn = null;
// NOTE: EV_DISCONNECT may occur twice for each disconnect
// please handle accordingly
change_state("RESET");
},
null
);
Event.on(
GATTC.EV_DISCOVERY_RESULT,
function (ev, evdata) {
let dr = GATTC.getDiscoveryResultArg(evdata);
// log(JSON.stringify(dr));
/* You will see something like this:
{"conn":{"addr":"E46303091846","connId":0,"mtu":247},"svc":"1800","chr":"2a00","handle":3,"prop":10}
{"conn":{"addr":"E46303091846","connId":0,"mtu":247},"svc":"fe59","chr":"8ec90003-f315-4f60-9fb8-838830daea50","handle":13,"prop":40}
{"conn":{"addr":"E46303091846","connId":0,"mtu":247},"svc":"180d","chr":"2a39","handle":49,"prop":18}
*/
if (dr.chr === "a001") {
// Example of how to read a characteristic
if (!GATTC.read(dr.conn.connId, dr.handle)) {
log("Failed to read characteristic");
}
}
if (dr.chr === "a003") {
// Turn on the compass by writing to the characteristic
let newValue = "\x01";
if (!GATTC.write(dr.conn.connId, dr.handle, newValue)) {
log("Failed to write characteristic");
}
}
if (dr.chr === "2a39") {
// Subscribe to the data measurements
if (!GATTC.subscribe(dr.conn.connId, dr.handle)) {
log("Failed to subscribe to characteristic");
}
}
},
null
);
Event.on(
GATTC.EV_NOTIFY,
function (ev, evdata) {
// This event is triggered when a subscribed characteristic sends a notification (new data)
let rd = GATTC.getNotifyArg(evdata);
// log("getNotifyArg " + JSON.stringify(rd));
let data = rd.data;
// Parse the data and apply scaling or offset
let result = {
wind_speed: SQ.parse(data, 0, 2, 0, -SQ.U16) / 100,
wind_dir: SQ.parse(data, 2, 2, 0, -SQ.U16),
battery: data.at(4) * 10,
time: Timer.now(),
};
sensors[sensor_index].data = result;
log('[' + JSON.stringify(data) + '] ' + JSON.stringify(data.length));
log(JSON.stringify(result));
},
null
);
Event.on(
GATTC.EV_READ_RESULT,
function (ev, evdata) {
log("EV_READ_RESULT");
let rd = GATTC.getReadResult(evdata);
log(JSON.stringify(rd));
},
null
);
SQ.set_data_handler(function () {
if (sensors.length > 0) {
let i = 0;
SQ.dispatch(1, JSON.stringify(sensors[i]));
if (isdef(sensors[i].data) && (sensors[i].data.time + 10) > Timer.now()) {
SQ.dispatch(2, sensors[i].data.wind_speed);
SQ.dispatch(3, sensors[i].data.wind_dir);
SQ.dispatch(4, sensors[i].data.battery);
}
}
}, null);