mirror of https://github.com/espruino/BangleApps
btadv: handle remaining sensors
parent
48f950f0b9
commit
c36a6a5fbb
|
@ -10,12 +10,17 @@ var __assign = (this && this.__assign) || function () {
|
|||
};
|
||||
return __assign.apply(this, arguments);
|
||||
};
|
||||
var _a, _b;
|
||||
var services = ["0x180d", "0x181a", "0x1819"];
|
||||
var acc;
|
||||
var bar;
|
||||
var gps;
|
||||
var hrm;
|
||||
var mag;
|
||||
var haveNewAcc = false;
|
||||
var haveNewBar = false;
|
||||
var haveNewGps = false;
|
||||
var haveNewHrm = false;
|
||||
var haveNewMag = false;
|
||||
var curMenu = "main";
|
||||
var mainMenuScroll = 0;
|
||||
var settings = {
|
||||
|
@ -49,7 +54,7 @@ var optionsCommon = {
|
|||
back: showMainMenu,
|
||||
};
|
||||
var accMenu = {
|
||||
"": __assign({ "title": "Acceleration -" }, optionsCommon),
|
||||
"": __assign({ "title": "Acceleration" }, optionsCommon),
|
||||
"Active": { value: "true (fixed)" },
|
||||
"x": { value: "" },
|
||||
"y": { value: "" },
|
||||
|
@ -78,7 +83,7 @@ var gpsMenu = {
|
|||
"HDOP": { value: "" },
|
||||
};
|
||||
var hrmMenu = {
|
||||
"": __assign({ "title": "- Heart Rate -" }, optionsCommon),
|
||||
"": __assign({ "title": "Heart Rate" }, optionsCommon),
|
||||
"Active": {
|
||||
value: settings.hrmEnabled,
|
||||
onchange: function (v) { return updateSetting('hrmEnabled', v); },
|
||||
|
@ -164,59 +169,39 @@ var updateMenu = function () {
|
|||
break;
|
||||
}
|
||||
};
|
||||
var updateBleAdvert = function () {
|
||||
var _a, _b;
|
||||
var bleAdvert;
|
||||
if (!(bleAdvert = Bangle.bleAdvert))
|
||||
bleAdvert = Bangle.bleAdvert = {};
|
||||
if (hrm) {
|
||||
bleAdvert["0x180d"] = undefined;
|
||||
if (NRF.getSecurityStatus().connected) {
|
||||
NRF.updateServices((_a = {},
|
||||
_a["0x180d"] = (_b = {},
|
||||
_b["0x2a37"] = {
|
||||
value: [0, hrm.bpm],
|
||||
notify: true,
|
||||
},
|
||||
_b),
|
||||
_a));
|
||||
return;
|
||||
}
|
||||
}
|
||||
NRF.setAdvertising(Bangle.bleAdvert);
|
||||
var encodeHrm = function (hrm) {
|
||||
return [0, hrm ? hrm.bpm : 0];
|
||||
};
|
||||
var encodeHrm = function () { return [0, hrm ? hrm.bpm : 0]; };
|
||||
var encodeBarServiceData = function (data) {
|
||||
var t = toByteArray(Math.round(data.temperature * 100), 2, true);
|
||||
var p = toByteArray(Math.round(data.pressure * 1000), 4, false);
|
||||
var e = toByteArray(Math.round(data.altitude * 100), 3, true);
|
||||
return [
|
||||
0x02, 0x01, 0x06,
|
||||
0x05, 0x16, 0x6e, 0x2a, t[0], t[1],
|
||||
0x07, 0x16, 0x6d, 0x2a, p[0], p[1], p[2], p[3],
|
||||
0x06, 0x16, 0x6c, 0x2a, e[0], e[1], e[2]
|
||||
];
|
||||
var encodePressure = function (data) {
|
||||
return toByteArray(Math.round(data.pressure * 1000), 4, false);
|
||||
};
|
||||
var encodeGpsServiceData = function (data) {
|
||||
var s = toByteArray(Math.round(1000 * data.speed / 36), 2, false);
|
||||
var encodeElevation = function (data) {
|
||||
return toByteArray(Math.round(data.altitude * 100), 3, true);
|
||||
};
|
||||
var encodeTemp = function (data) {
|
||||
return toByteArray(Math.round(data.temperature * 100), 2, true);
|
||||
};
|
||||
var encodeGps = function (data) {
|
||||
var speed = toByteArray(Math.round(1000 * data.speed / 36), 2, false);
|
||||
var lat = toByteArray(Math.round(data.lat * 10000000), 4, true);
|
||||
var lon = toByteArray(Math.round(data.lon * 10000000), 4, true);
|
||||
var e = toByteArray(Math.round(data.alt * 100), 3, true);
|
||||
var h = toByteArray(Math.round(data.course * 100), 2, false);
|
||||
var elevation = toByteArray(Math.round(data.alt * 100), 3, true);
|
||||
var heading = toByteArray(Math.round(data.course * 100), 2, false);
|
||||
return [
|
||||
0x02, 0x01, 0x06,
|
||||
0x14, 0x16, 0x67, 0x2a, 0x9d, 0x02, s[0], s[1], lat[0], lat[1], lat[2],
|
||||
lat[3], lon[0], lon[1], lon[2], lon[3], e[0], e[1], e[2], h[0], h[1]
|
||||
0x9d,
|
||||
0x2,
|
||||
speed[0], speed[1],
|
||||
lat[0], lat[1], lat[2], lat[3],
|
||||
lon[0], lon[1], lon[2], lon[3],
|
||||
elevation[0], elevation[1], elevation[2],
|
||||
heading[0], heading[1]
|
||||
];
|
||||
};
|
||||
var encodeMagServiceData = function (data) {
|
||||
var encodeMag = function (data) {
|
||||
var x = toByteArray(data.x, 2, true);
|
||||
var y = toByteArray(data.y, 2, true);
|
||||
var z = toByteArray(data.z, 2, true);
|
||||
return [
|
||||
0x02, 0x01, 0x06,
|
||||
0x09, 0x16, 0xa1, 0x2a, x[0], x[1], y[0], y[1], z[0], z[1]
|
||||
];
|
||||
return [x[0], x[1], y[0], y[1], z[0], z[1]];
|
||||
};
|
||||
var toByteArray = function (value, numberOfBytes, isSigned) {
|
||||
var byteArray = new Array(numberOfBytes);
|
||||
|
@ -241,54 +226,149 @@ var enableSensors = function () {
|
|||
Bangle.setCompassPower(settings.magEnabled, "btadv");
|
||||
if (!settings.magEnabled)
|
||||
mag = undefined;
|
||||
updateBleAdvert();
|
||||
updateServices();
|
||||
};
|
||||
var updateSetting = function (name, value) {
|
||||
settings[name] = value;
|
||||
enableSensors();
|
||||
};
|
||||
NRF.setServices((_a = {},
|
||||
_a["0x180d"] = (_b = {},
|
||||
_b["0x2a37"] = {
|
||||
value: encodeHrm(),
|
||||
readable: true,
|
||||
notify: true,
|
||||
},
|
||||
_b),
|
||||
_a), {
|
||||
advertise: [
|
||||
'180d',
|
||||
]
|
||||
});
|
||||
var updateServices = function () {
|
||||
var _a, _b;
|
||||
NRF.updateServices((_a = {},
|
||||
_a["0x180d"] = (_b = {},
|
||||
_b["0x2a37"] = {
|
||||
value: encodeHrm(),
|
||||
notify: true,
|
||||
},
|
||||
_b),
|
||||
_a));
|
||||
var haveNew = function () {
|
||||
return haveNewAcc || haveNewBar || haveNewGps || haveNewHrm || haveNewMag;
|
||||
};
|
||||
Bangle.on('accel', function (newAcc) { return acc = newAcc; });
|
||||
Bangle.on('pressure', function (newBar) { return bar = newBar; });
|
||||
Bangle.on('GPS', function (newGps) { return gps = newGps; });
|
||||
Bangle.on('HRM', function (newHrm) { return hrm = newHrm; });
|
||||
Bangle.on('mag', function (newMag) { return mag = newMag; });
|
||||
var serviceActive = function (serv) {
|
||||
switch (serv) {
|
||||
case "0x180d": return !!hrm;
|
||||
case "0x181a": return !!(bar || mag);
|
||||
case "0x1819": return !!(gps && gps.lat && gps.lon);
|
||||
}
|
||||
};
|
||||
var serviceToAdvert = function (serv) {
|
||||
var _a, _b;
|
||||
switch (serv) {
|
||||
case "0x180d":
|
||||
if (hrm) {
|
||||
return _a = {},
|
||||
_a["0x2a37"] = {
|
||||
value: encodeHrm(hrm),
|
||||
readable: true,
|
||||
notify: true,
|
||||
},
|
||||
_a;
|
||||
}
|
||||
return {};
|
||||
case "0x1819":
|
||||
if (gps) {
|
||||
return _b = {},
|
||||
_b["0x2a67"] = {
|
||||
value: encodeGps(gps),
|
||||
readable: true,
|
||||
notify: true,
|
||||
},
|
||||
_b;
|
||||
}
|
||||
return {};
|
||||
case "0x181a": {
|
||||
var o = {};
|
||||
if (bar) {
|
||||
o["0x2a6c"] = {
|
||||
value: encodeElevation(bar),
|
||||
readable: true,
|
||||
notify: true,
|
||||
};
|
||||
o["0x2A1F"] = {
|
||||
value: encodeTemp(bar),
|
||||
readable: true,
|
||||
notify: true,
|
||||
};
|
||||
o["0x2a6d"] = {
|
||||
value: encodePressure(bar),
|
||||
readable: true,
|
||||
notify: true,
|
||||
};
|
||||
}
|
||||
if (mag) {
|
||||
o["0x2aa1"] = {
|
||||
value: encodeMag(mag),
|
||||
readable: true,
|
||||
notify: true,
|
||||
};
|
||||
}
|
||||
;
|
||||
return o;
|
||||
}
|
||||
}
|
||||
};
|
||||
var getBleAdvert = function (map) {
|
||||
var advert = {};
|
||||
for (var _i = 0, services_1 = services; _i < services_1.length; _i++) {
|
||||
var serv = services_1[_i];
|
||||
if (serviceActive(serv)) {
|
||||
advert[serv] = map(serv);
|
||||
}
|
||||
}
|
||||
return advert;
|
||||
};
|
||||
var updateBleAdvert = function () {
|
||||
var bleAdvert;
|
||||
if (!(bleAdvert = Bangle.bleAdvert)) {
|
||||
bleAdvert = getBleAdvert(function (_) { return undefined; });
|
||||
Bangle.bleAdvert = bleAdvert;
|
||||
}
|
||||
try {
|
||||
NRF.setAdvertising(bleAdvert);
|
||||
}
|
||||
catch (e) {
|
||||
console.log("setAdvertising(): " + e);
|
||||
}
|
||||
};
|
||||
var updateServices = function () {
|
||||
var newAdvert = getBleAdvert(serviceToAdvert);
|
||||
if (NRF.getSecurityStatus().connected) {
|
||||
NRF.updateServices(newAdvert);
|
||||
}
|
||||
else {
|
||||
NRF.setServices(newAdvert, {
|
||||
advertise: Object
|
||||
.keys(newAdvert)
|
||||
.map(function (k) { return k.replace("0x", ""); })
|
||||
});
|
||||
}
|
||||
};
|
||||
Bangle.on('accel', function (newAcc) { acc = newAcc; haveNewAcc = true; });
|
||||
Bangle.on('pressure', function (newBar) { bar = newBar; haveNewBar = true; });
|
||||
Bangle.on('GPS', function (newGps) { gps = newGps; haveNewGps = true; });
|
||||
Bangle.on('HRM', function (newHrm) { hrm = newHrm; haveNewHrm = true; });
|
||||
Bangle.on('mag', function (newMag) { mag = newMag; haveNewMag = true; });
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
showMainMenu();
|
||||
enableSensors();
|
||||
setInterval(updateBleAdvert, 30000);
|
||||
var menuInterval = setInterval(updateMenu, 1000);
|
||||
Bangle.on("lock", function (locked) {
|
||||
changeInterval(menuInterval, locked ? 30000 : 1000);
|
||||
});
|
||||
var serviceInterval;
|
||||
enableSensors();
|
||||
var iv;
|
||||
var setIntervals = function (connected) {
|
||||
if (connected) {
|
||||
if (iv) {
|
||||
changeInterval(iv, 1000);
|
||||
}
|
||||
else {
|
||||
iv = setInterval(function () {
|
||||
if (haveNew())
|
||||
updateServices();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
else if (iv) {
|
||||
clearInterval(iv);
|
||||
}
|
||||
};
|
||||
setIntervals(NRF.getSecurityStatus().connected);
|
||||
NRF.on("connect", function () {
|
||||
serviceInterval = setInterval(updateServices, 1000);
|
||||
setIntervals(true);
|
||||
});
|
||||
NRF.on("disconnect", function () {
|
||||
clearInterval(serviceInterval);
|
||||
serviceInterval = undefined;
|
||||
setIntervals(false);
|
||||
});
|
||||
|
|
|
@ -1,17 +1,81 @@
|
|||
// TODO: emit other data beside HRM (via set/updateServices)
|
||||
|
||||
const enum Intervals {
|
||||
BLE_ADVERT = 30 * 1000,
|
||||
BLE_ADVERT = 60 * 1000,
|
||||
BLE = 1000,
|
||||
MENU_WAKE = 1000,
|
||||
MENU_SLEEP = 30 * 1000,
|
||||
}
|
||||
|
||||
type Hrm = { bpm: number, confidence: number };
|
||||
|
||||
// https://github.com/sputnikdev/bluetooth-gatt-parser/blob/master/src/main/resources/gatt/
|
||||
const enum BleServ {
|
||||
// org.bluetooth.service.heart_rate
|
||||
// contains: HRM
|
||||
HRM = "0x180d",
|
||||
|
||||
// org.bluetooth.service.environmental_sensing
|
||||
// contains: Elevation, Temp(Celsius), Pressure, Mag
|
||||
EnvSensing = "0x181a",
|
||||
|
||||
// org.bluetooth.service.location_and_navigation
|
||||
// contains: LocationAndSpeed
|
||||
LocationAndNavigation = "0x1819",
|
||||
|
||||
// Acc // none known for this
|
||||
}
|
||||
|
||||
const services = [BleServ.HRM, BleServ.EnvSensing, BleServ.LocationAndNavigation];
|
||||
|
||||
const enum BleChar {
|
||||
// org.bluetooth.characteristic.heart_rate_measurement
|
||||
// <see encode function>
|
||||
HRM = "0x2a37",
|
||||
|
||||
// org.bluetooth.characteristic.elevation
|
||||
// s24, meters 0.01
|
||||
Elevation = "0x2a6c",
|
||||
|
||||
// org.bluetooth.characteristic.temperature
|
||||
// s16 *10^2
|
||||
Temp = "0x2a6e",
|
||||
// org.bluetooth.characteristic.temperature_celsius
|
||||
// s16 *10^2
|
||||
TempCelsius = "0x2A1F",
|
||||
|
||||
// org.bluetooth.characteristic.pressure
|
||||
// u32 *10
|
||||
Pressure = "0x2a6d",
|
||||
|
||||
// org.bluetooth.characteristic.location_and_speed
|
||||
// <see encodeGps>
|
||||
LocationAndSpeed = "0x2a67",
|
||||
|
||||
// org.bluetooth.characteristic.magnetic_flux_density_3d
|
||||
// s16: x, y, z, tesla (10^-7)
|
||||
MagneticFlux3D = "0x2aa1",
|
||||
}
|
||||
|
||||
type BleCharAdvert = {
|
||||
value: Array<number>,
|
||||
readable?: true,
|
||||
notify?: true,
|
||||
indicate?: true, // notify + ACK
|
||||
};
|
||||
|
||||
type BleServAdvert = {
|
||||
[key in BleChar]?: BleCharAdvert;
|
||||
};
|
||||
|
||||
let acc: undefined | AccelData;
|
||||
let bar: undefined | PressureData;
|
||||
let gps: undefined | GPSFix;
|
||||
let hrm: undefined | { bpm: number, confidence: number };
|
||||
let hrm: undefined | Hrm;
|
||||
let mag: undefined | CompassData;
|
||||
let haveNewAcc = false;
|
||||
let haveNewBar = false;
|
||||
let haveNewGps = false;
|
||||
let haveNewHrm = false;
|
||||
let haveNewMag = false;
|
||||
|
||||
type BtAdvMenu = "acc" | "bar" | "gps" | "hrm" | "mag" | "main";
|
||||
let curMenu: BtAdvMenu = "main";
|
||||
|
@ -54,7 +118,7 @@ const optionsCommon = {
|
|||
};
|
||||
|
||||
const accMenu = {
|
||||
"": { "title" : "Acceleration -", ...optionsCommon },
|
||||
"": { "title" : "Acceleration", ...optionsCommon },
|
||||
"Active": { value: "true (fixed)" },
|
||||
"x": { value: "" },
|
||||
"y": { value: "" },
|
||||
|
@ -86,7 +150,7 @@ const gpsMenu = {
|
|||
};
|
||||
|
||||
const hrmMenu = {
|
||||
"": { "title" : "- Heart Rate -", ...optionsCommon },
|
||||
"": { "title" : "Heart Rate", ...optionsCommon },
|
||||
"Active": {
|
||||
value: settings.hrmEnabled,
|
||||
onchange: (v: boolean) => updateSetting('hrmEnabled', v),
|
||||
|
@ -174,109 +238,91 @@ const updateMenu = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const enum BleServ {
|
||||
HRM = "0x180d",
|
||||
}
|
||||
const enum BleChar {
|
||||
HRM = "0x2a37",
|
||||
}
|
||||
|
||||
const updateBleAdvert = () => {
|
||||
let bleAdvert: { [key: string]: undefined };
|
||||
|
||||
if (!(bleAdvert = (Bangle as any).bleAdvert))
|
||||
bleAdvert = (Bangle as any).bleAdvert = {};
|
||||
|
||||
// const data = [ APP_ADVERTISING_DATA ]; // Always advertise at least app name
|
||||
|
||||
// if (bar) {
|
||||
// data.push(encodeBarServiceData(bar));
|
||||
// bar = undefined;
|
||||
const encodeHrm = (hrm: Hrm) =>
|
||||
// {
|
||||
// flags: u8,
|
||||
// bytes: [u8...]
|
||||
// }
|
||||
|
||||
// if (gps && gps.lat && gps.lon) {
|
||||
// data.push(encodeGpsServiceData(gps));
|
||||
// gps = undefined;
|
||||
// flags {
|
||||
// 1 << 0: 16bit bpm
|
||||
// 1 << 1: sensor contact available
|
||||
// 1 << 2: sensor contact boolean
|
||||
// 1 << 3: energy expended, next 16 bits
|
||||
// 1 << 4: "rr" data available, u16s, intervals
|
||||
// }
|
||||
[0, hrm ? hrm.bpm : 0];
|
||||
|
||||
if (hrm) {
|
||||
bleAdvert[BleServ.HRM] = undefined; // Advertise HRM
|
||||
const encodePressure = (data: PressureData) =>
|
||||
toByteArray(Math.round(data.pressure * 1000), 4, false);
|
||||
|
||||
// hack
|
||||
if (NRF.getSecurityStatus().connected) {
|
||||
NRF.updateServices({
|
||||
[BleServ.HRM]: {
|
||||
[BleChar.HRM]: {
|
||||
value: [0, hrm.bpm],
|
||||
notify: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
return;
|
||||
}
|
||||
const encodeElevation = (data: PressureData) =>
|
||||
toByteArray(Math.round(data.altitude * 100), 3, true);
|
||||
|
||||
// data.push({ 0x2a37: [ 0, hrm.bpm ] });
|
||||
// hrm = undefined;
|
||||
}
|
||||
const encodeTemp = (data: PressureData) =>
|
||||
toByteArray(Math.round(data.temperature * 100), 2, true);
|
||||
|
||||
// if (mag) {
|
||||
// data.push(encodeMagServiceData(mag));
|
||||
// mag = undefined;
|
||||
// }
|
||||
const encodeGps = (data: GPSFix) => {
|
||||
// flags: 16 bits
|
||||
// bit 0: Instantaneous Speed Present
|
||||
// bit 1: Total Distance Present
|
||||
// bit 2: Location Present
|
||||
// bit 3: Elevation Present
|
||||
// bit 4: Heading Present
|
||||
// bit 5: Rolling Time Present
|
||||
// bit 6: UTC Time Present
|
||||
//
|
||||
// bit 7-8: position status
|
||||
// 0 (0b00): no position
|
||||
// 1 (0b01): position ok
|
||||
// 2 (0b10): estimated position
|
||||
// 3 (0b11): last known position
|
||||
//
|
||||
// bit 9: speed & distance format
|
||||
// 0: 2d
|
||||
// 1: 3d
|
||||
//
|
||||
// bit 10-11: elevation source
|
||||
// 0: Positioning System
|
||||
// 1: Barometric Air Pressure
|
||||
// 2: Database Service (or similiar)
|
||||
// 3: Other
|
||||
//
|
||||
// bit 12: Heading Source
|
||||
// 0: Heading based on movement
|
||||
// 1: Heading based on magnetic compass
|
||||
//
|
||||
// speed: u16 (m/s), 1/100
|
||||
// distance: u24, 1/10
|
||||
// lat: s32, 1/10^7
|
||||
// lon: s32, 1/10^7
|
||||
// elevation: s24, 1/100
|
||||
// heading: u16 (deg), 1/100
|
||||
// rolling time: u8 (s)
|
||||
// utc time: org.bluetooth.characteristic.date_time
|
||||
|
||||
NRF.setAdvertising((Bangle as any).bleAdvert);
|
||||
};
|
||||
|
||||
// {
|
||||
// flags: u8,
|
||||
// bytes: [u8...]
|
||||
// }
|
||||
// flags {
|
||||
// 1 << 0: 16bit bpm
|
||||
// 1 << 1: sensor contact available
|
||||
// 1 << 2: sensor contact boolean
|
||||
// 1 << 3: energy expended, next 16 bits
|
||||
// 1 << 4: "rr" data available, u16s, intervals
|
||||
// }
|
||||
const encodeHrm = () => [0, hrm ? hrm.bpm : 0];
|
||||
|
||||
const encodeBarServiceData = (data: PressureData) => {
|
||||
const t = toByteArray(Math.round(data.temperature * 100), 2, true);
|
||||
const p = toByteArray(Math.round(data.pressure * 1000), 4, false);
|
||||
const e = toByteArray(Math.round(data.altitude * 100), 3, true);
|
||||
|
||||
return [
|
||||
0x02, 0x01, 0x06, // Flags
|
||||
0x05, 0x16, 0x6e, 0x2a, t[0], t[1], // Temperature
|
||||
0x07, 0x16, 0x6d, 0x2a, p[0], p[1], p[2], p[3], // Pressure
|
||||
0x06, 0x16, 0x6c, 0x2a, e[0], e[1], e[2] // Elevation
|
||||
];
|
||||
};
|
||||
|
||||
const encodeGpsServiceData = (data: GPSFix) => {
|
||||
const s = toByteArray(Math.round(1000 * data.speed / 36), 2, false);
|
||||
const speed = toByteArray(Math.round(1000 * data.speed / 36), 2, false);
|
||||
const lat = toByteArray(Math.round(data.lat * 10000000), 4, true);
|
||||
const lon = toByteArray(Math.round(data.lon * 10000000), 4, true);
|
||||
const e = toByteArray(Math.round(data.alt * 100), 3, true);
|
||||
const h = toByteArray(Math.round(data.course * 100), 2, false);
|
||||
const elevation = toByteArray(Math.round(data.alt * 100), 3, true);
|
||||
const heading = toByteArray(Math.round(data.course * 100), 2, false);
|
||||
|
||||
return [
|
||||
0x02, 0x01, 0x06, // Flags
|
||||
0x14, 0x16, 0x67, 0x2a, 0x9d, 0x02, s[0], s[1], lat[0], lat[1], lat[2],
|
||||
lat[3], lon[0], lon[1], lon[2], lon[3], e[0], e[1], e[2], h[0], h[1]
|
||||
// Location and Speed
|
||||
0x9d, // 0b10011101: speed, location, elevation, heading [...]
|
||||
0x2, // 0b00000010: position ok, 3d speed/distance(?)
|
||||
speed[0]!, speed[1]!,
|
||||
lat[0]!, lat[1]!, lat[2]!, lat[3]!,
|
||||
lon[0]!, lon[1]!, lon[2]!, lon[3]!,
|
||||
elevation[0]!, elevation[1]!, elevation[2]!,
|
||||
heading[0]!, heading[1]!
|
||||
];
|
||||
};
|
||||
|
||||
const encodeMagServiceData = (data: CompassData) => {
|
||||
const encodeMag = (data: CompassData) => {
|
||||
const x = toByteArray(data.x, 2, true);
|
||||
const y = toByteArray(data.y, 2, true);
|
||||
const z = toByteArray(data.z, 2, true);
|
||||
|
||||
return [
|
||||
0x02, 0x01, 0x06, // Flags
|
||||
0x09, 0x16, 0xa1, 0x2a, x[0], x[1], y[0], y[1], z[0], z[1] // Mag 3D
|
||||
];
|
||||
return [ x[0]!, x[1]!, y[0]!, y[1]!, z[0]!, z[1]! ];
|
||||
};
|
||||
|
||||
const toByteArray = (value: number, numberOfBytes: number, isSigned: boolean) => {
|
||||
|
@ -309,6 +355,9 @@ const enableSensors = () => {
|
|||
Bangle.setCompassPower(settings.magEnabled, "btadv");
|
||||
if (!settings.magEnabled)
|
||||
mag = undefined;
|
||||
|
||||
updateBleAdvert();
|
||||
updateServices();
|
||||
};
|
||||
|
||||
const updateSetting = (
|
||||
|
@ -322,49 +371,136 @@ const updateSetting = (
|
|||
|
||||
// ----------------------------
|
||||
|
||||
NRF.setServices(
|
||||
{
|
||||
[BleServ.HRM]: {
|
||||
[BleChar.HRM]: {
|
||||
value: encodeHrm(),
|
||||
readable: true,
|
||||
notify: true,
|
||||
//indicate: true, // notify + ACK
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
advertise: [
|
||||
'180d',
|
||||
]
|
||||
},
|
||||
);
|
||||
const haveNew = () =>
|
||||
haveNewAcc || haveNewBar || haveNewGps || haveNewHrm || haveNewMag;
|
||||
|
||||
const updateServices = () => {
|
||||
NRF.updateServices({
|
||||
[BleServ.HRM]: {
|
||||
[BleChar.HRM]: {
|
||||
value: encodeHrm(),
|
||||
notify: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
const serviceActive = (serv: BleServ): boolean => {
|
||||
switch (serv) {
|
||||
case BleServ.HRM: return !!hrm;
|
||||
case BleServ.EnvSensing: return !!(bar || mag);
|
||||
case BleServ.LocationAndNavigation: return !!(gps && gps.lat && gps.lon);
|
||||
}
|
||||
};
|
||||
|
||||
Bangle.on('accel', newAcc => acc = newAcc);
|
||||
Bangle.on('pressure', newBar => bar = newBar);
|
||||
Bangle.on('GPS', newGps => gps = newGps);
|
||||
Bangle.on('HRM', newHrm => hrm = newHrm);
|
||||
Bangle.on('mag', newMag => mag = newMag);
|
||||
const serviceToAdvert = (serv: BleServ): BleServAdvert => {
|
||||
switch (serv) {
|
||||
case BleServ.HRM:
|
||||
if (hrm) {
|
||||
return {
|
||||
[BleChar.HRM]: {
|
||||
value: encodeHrm(hrm),
|
||||
readable: true,
|
||||
notify: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
|
||||
case BleServ.LocationAndNavigation:
|
||||
if (gps) {
|
||||
return {
|
||||
[BleChar.LocationAndSpeed]: {
|
||||
value: encodeGps(gps),
|
||||
readable: true,
|
||||
notify: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
|
||||
case BleServ.EnvSensing: {
|
||||
const o: BleServAdvert = {};
|
||||
|
||||
if (bar) {
|
||||
o[BleChar.Elevation] = {
|
||||
value: encodeElevation(bar),
|
||||
readable: true,
|
||||
notify: true,
|
||||
};
|
||||
o[BleChar.TempCelsius] = {
|
||||
value: encodeTemp(bar),
|
||||
readable: true,
|
||||
notify: true,
|
||||
};
|
||||
o[BleChar.Pressure] = {
|
||||
value: encodePressure(bar),
|
||||
readable: true,
|
||||
notify: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (mag) {
|
||||
o[BleChar.MagneticFlux3D] = {
|
||||
value: encodeMag(mag),
|
||||
readable: true,
|
||||
notify: true,
|
||||
};
|
||||
};
|
||||
|
||||
return o;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getBleAdvert = <T>(map: (s: BleServ) => T) => {
|
||||
const advert: { [key in BleServ]?: T } = {};
|
||||
|
||||
for (const serv of services) {
|
||||
if (serviceActive(serv)) {
|
||||
advert[serv] = map(serv);
|
||||
}
|
||||
}
|
||||
|
||||
return advert;
|
||||
};
|
||||
|
||||
// call this when settings changes
|
||||
const updateBleAdvert = () => {
|
||||
let bleAdvert: ReturnType<typeof getBleAdvert<undefined>>;
|
||||
|
||||
if (!(bleAdvert = (Bangle as any).bleAdvert)) {
|
||||
bleAdvert = getBleAdvert(_ => undefined);
|
||||
|
||||
(Bangle as any).bleAdvert = bleAdvert;
|
||||
}
|
||||
|
||||
try {
|
||||
NRF.setAdvertising(bleAdvert);
|
||||
} catch (e) {
|
||||
console.log("setAdvertising(): " + e);
|
||||
}
|
||||
};
|
||||
|
||||
// call this when settings changes, or when we have new data to send/serve
|
||||
const updateServices = () => {
|
||||
const newAdvert = getBleAdvert(serviceToAdvert);
|
||||
|
||||
if (NRF.getSecurityStatus().connected) {
|
||||
NRF.updateServices(newAdvert);
|
||||
} else {
|
||||
NRF.setServices(
|
||||
newAdvert,
|
||||
{
|
||||
advertise: Object
|
||||
.keys(newAdvert)
|
||||
.map((k: string) => k.replace("0x", ""))
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Bangle.on('accel', newAcc => { acc = newAcc; haveNewAcc = true; });
|
||||
Bangle.on('pressure', newBar => { bar = newBar; haveNewBar = true; });
|
||||
Bangle.on('GPS', newGps => { gps = newGps; haveNewGps = true; });
|
||||
Bangle.on('HRM', newHrm => { hrm = newHrm; haveNewHrm = true; });
|
||||
Bangle.on('mag', newMag => { mag = newMag; haveNewMag = true; });
|
||||
|
||||
// show menu first to have it reserve space for widgets (appRect)
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// show UI
|
||||
showMainMenu();
|
||||
enableSensors();
|
||||
|
||||
setInterval(updateBleAdvert, Intervals.BLE_ADVERT);
|
||||
|
||||
const menuInterval = setInterval(updateMenu, Intervals.MENU_WAKE);
|
||||
Bangle.on("lock", locked => {
|
||||
changeInterval(
|
||||
|
@ -373,11 +509,32 @@ Bangle.on("lock", locked => {
|
|||
);
|
||||
});
|
||||
|
||||
let serviceInterval: undefined | number;
|
||||
// turn things on
|
||||
enableSensors(); // calls updateBleAdvert
|
||||
|
||||
let iv: undefined | number;
|
||||
const setIntervals = (connected: boolean) => {
|
||||
if (connected) {
|
||||
if (iv) {
|
||||
changeInterval(iv, Intervals.BLE);
|
||||
} else {
|
||||
iv = setInterval(
|
||||
() => {
|
||||
if (haveNew())
|
||||
updateServices();
|
||||
},
|
||||
Intervals.BLE
|
||||
);
|
||||
}
|
||||
} else if (iv) {
|
||||
clearInterval(iv);
|
||||
}
|
||||
};
|
||||
|
||||
setIntervals(NRF.getSecurityStatus().connected);
|
||||
NRF.on("connect", () => {
|
||||
serviceInterval = setInterval(updateServices, Intervals.BLE);
|
||||
setIntervals(true);
|
||||
});
|
||||
NRF.on("disconnect", () => {
|
||||
clearInterval(serviceInterval);
|
||||
serviceInterval = undefined;
|
||||
setIntervals(false);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue