UNPKG

thing-it-device-btle-heart-rate-monitor

Version:
331 lines (280 loc) 10.6 kB
module.exports = { metadata: { family: "btleHeartRateMonitor", plugin: "btleHeartRateMonitor", label: "BTLE Heart Rate Monitor", manufacturer: "Generic", tangible: true, discoverable: true, state: [{ id: "heartRate", label: "Heart Rate", type: { id: "number" }, min: 0, max: 250 }, { id: "bodyLocation", label: "Body Location", type: { id: "string" } }, { id: "batteryLevel", label: "Battery Level", type: { id: "number" }, min: 0, max: 100 }], actorTypes: [], sensorTypes: [], services: [], configuration: [{ id: "criticalThresholdLow", label: "Critical Threshold (Low)", type: { id: "number" }, defaultValue: 40 }, { id: "warningThresholdLow", label: "Warning Threshold (Low)", type: { id: "number" }, defaultValue: 50 }, { id: "warningThresholdHigh", label: "Warning Threshold (High)", type: { id: "number" }, defaultValue: 170 }, { id: "criticalThresholdHigh", label: "Critical Threshold High", type: { id: "number" }, defaultValue: 180 }] }, create: function (device) { return new BtleHeartRateMonitor(); }, discovery: function (options) { var discovery = new BtleHeartRateMonitorDiscovery(); discovery.options = options; return discovery; } }; var q = require('q'); var noble; var GUID_PATTERN = /([a-f0-9]{8})-?([a-f0-9]{4})-?([a-f0-9]{4})-?([a-f0-9]{4})-?([a-f0-9]{12})/; var GUID_REPLACEMENT = "$1-$2-$3-$4-$5"; var HEART_RATE_SERVICE_UUID = "f000aa10-0451-4000-b000-000000000000"; // TODO Correct values var HEART_RATE_DATA_UUID = /^f000aa11-0451-4000-b000-000000000000$/; // TODO Correct values var HEART_RATE_CONFIGURATION_UUID = "f000aa12-0451-4000-b000-000000000000"; // TODO Correct values var HEART_RATE_PERIOD_UUID = "f000aa13-0451-4000-b000-000000000000"; // TODO Correct values var HR_CHARACTERISTIC_BODY_SENSOR_UUID = '2a38'; var HR_CHARACTERISTIC_HEART_RATE_UUID = '2a37'; var BATTERY_LEVEL_SERVICE_UUID = "f000aa10-0451-4000-b000-000000000000"; // TODO Correct values var BATTERY_LEVEL_DATA_UUID = /^f000aa11-0451-4000-b000-000000000000$/; // TODO Correct values var BATTERY_LEVEL_CONF_UUID = "f000aa12-0451-4000-b000-000000000000"; // TODO Correct values var BATTERY_LEVEL_PERIOD_UUID = "f000aa13-0451-4000-b000-000000000000"; // TODO Correct values var BATTERY_SERVICE_UUID = '180f'; var BATTERY_CHARACTERISTIC_LEVEL_UUID = '2a19'; var HEART_RATE_VALUE_FORMAT = 1; var BODY_LOCATION_STRINGS = ['Other', 'Chest', 'Wrist', 'Finger', 'Hand', 'Ear Lobe', 'Foot']; function BtleHeartRateMonitorDiscovery() { /** * * @param options */ BtleHeartRateMonitorDiscovery.prototype.start = function () { if (this.isSimulated()) { } else { if (!noble) { noble = require('noble'); } noble.on('discover', function (peripheral) { if (peripheral.advertisement.localName && peripheral.advertisement.localName.indexOf('@@@@') === 0) { console.log("Found Heart Rate Monitor " + peripheral.advertisement.localName); console.log("\tUUID " + peripheral.uuid); if (peripheral.uuid) { console.log("GUID " + peripheral.uuid.replace(GUID_PATTERN, GUID_REPLACEMENT)); } var heartRateMonitor = new BtleHeartRateMonitor(); heartRateMonitor.peripheral = peripheral; heartRateMonitor.uuid = peripheral.uuid; this.advertiseDevice(heartRateMonitor); } }.bind(this)); noble.startScanning(); } }; /** * * @param options */ BtleHeartRateMonitorDiscovery.prototype.stop = function () { if (this.isSimulated()) { } else { noble.stopScanning(); } }; } /** * */ function BtleHeartRateMonitor() { /** * */ BtleHeartRateMonitor.prototype.start = function () { var deferred = q.defer(); this.state = { heartRate: null, bodyLocation: null, batteryLevel: null }; if (this.isSimulated()) { setInterval(function () { this.state.heartRate = 120 + Math.floor((Math.random() * 20)); this.state.batteryLevel = 30; this.state.bodyLocation = "Wrist"; this.publishStateChange(); }.bind(this), 1000); deferred.resolve(); } else { if (!noble) { noble = require('noble'); } if (this.peripheral) { this.connect(); deferred.resolve(); } else { console.log("Start Heart Rate Monitor Discovery"); noble.on('discover', function (peripheral) { console.log(peripheral.advertisement.localName); if (peripheral.advertisement.localName && peripheral.uuid === this.uuid) { console.log("\tFound configured Heart Rate Monitor " + peripheral.advertisement.localName); console.log("\tUUID " + peripheral.uuid); if (peripheral.uuid) { console.log("\tGUID " + peripheral.uuid.replace(GUID_PATTERN, GUID_REPLACEMENT)); } this.peripheral = peripheral; noble.stopScanning(); this.connect(); } }.bind(this)); noble.startScanning(); console.log("\tScanning started"); deferred.resolve(); } } return deferred.promise; }; /** * */ BtleHeartRateMonitor.prototype.connect = function () { this.peripheral.connect(function (error) { if (error) { console.log(error); } else { peripheral.discoverAllServicesAndCharacteristics(function (error, services, characteristics) { for (var n = 0; n < services.length; ++n) { var guid = services[n].uuid.replace(Constants.GUID_PATTERN, Constants.GUID_REPLACEMENT); switch (guid) { case HEART_RATE_SERVICE_UUID: this.heartRateService = services[n]; break; case BATTERY_LEVEL_SERVICE_UUID: this.heartRateService = services[n]; break; } } for (var n = 0; n < characteristics.length; ++n) { var guid = characteristics[n].uuid.replace(Constants.GUID_PATTERN, Constants.GUID_REPLACEMENT); switch (guid) { case HEART_RATE_DATA_UUID: this.heartRateDataCharacteristic = characteristics[n]; break; case BATTERY_LEVEL_DATA_UUID: this.batteryLevelCharacteristic = characteristics[n]; break; case HR_CHARACTERISTIC_BODY_SENSOR_UUID: this.bodyLocationCharacteristic = characteristics[n]; break; } } this.heartRateDataCharacteristic.notify(true); this.heartRateDataCharacteristic.read(function (error, data) { if (error) { } var flags = data.readUInt8(0); if (!((flags & HEART_RATE_VALUE_FORMAT) != HEART_RATE_VALUE_FORMAT)) { return; } this.state.heartRate = data.readUInt8(1); this.getBodySensorData(function (error, bodyLocation) { if (error) { console.error(error); } else { this.state.bodyLocation = bodyLocation; this.publishStateChange(); } }.bind(this)); }); this.batteryLevelCharacteristic.notify(true) this.batteryLevelCharacteristic.read(function (error, data) { if (error) { console.error(error); } else { this.state.batteryLevel = data.readUInt8(0); this.publishStateChange(); } }); }.bind(this)); } }.bind(this)); }; BtleHeartRateMonitor.prototype.getBodySensorData = function (callback) { this.bodyLocationCharacteristic.read(function (error, data) { if (error) return callback(error); callback(null, this.bodyLocationToString(data.readUInt8(0))); }); }; BtleHeartRateMonitor.prototype.bodyLocationToString = function (location) { if (!BODY_LOCATION_STRINGS[location]) { return BODY_LOCATION_STRINGS[0]; } return BODY_LOCATION_STRINGS[location]; }; /** * */ BtleHeartRateMonitor.prototype.setState = function (state) { this.state = state; }; /** * */ BtleHeartRateMonitor.prototype.getState = function () { return this.state; }; }