Skip to content

Commit

Permalink
Bluetooth fixes & improvements (#921)
Browse files Browse the repository at this point in the history
* Fix read and logs + add getCharacteristics

* Bluetooth device creation/deletion

* Fix BLE load devices

Co-authored-by: Pierre-Gilles Leymarie <pierregilles.leymarie@gmail.com>
  • Loading branch information
atrovato and Pierre-Gilles authored Dec 3, 2020
1 parent a821cfa commit 6f92880
Show file tree
Hide file tree
Showing 27 changed files with 627 additions and 182 deletions.
29 changes: 2 additions & 27 deletions server/services/bluetooth/lib/commands/bluetooth.connectDevices.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,10 @@ const logger = require('../../../../utils/logger');
async function connectDevices() {
logger.debug(`Bluetooth: subscribing to existing devices...`);
const devices = await this.gladys.device.get({
service_id: this.serviceId,
service: 'bluetooth',
});

return Promise.map(
devices,
(device) => {
const [, peripheralUuid] = device.external_id.split(':');

const subscribe = (peripheral) => {
return Promise.map(
device.features,
(feature) => {
const [, , serviceUuid, characteristicUuid] = feature.external_id.split(':');
return this.subscribePeripheral(peripheral, serviceUuid, characteristicUuid, feature);
},
{ concurrency: 1 },
).catch((e) => {
logger.error(e.message);
return Promise.resolve();
});
};

return this.applyOnPeripheral(peripheralUuid, subscribe, true).catch((e) => {
logger.error(e.message);
return Promise.resolve();
});
},
{ concurrency: 1 },
);
return Promise.map(devices, (device) => this.postCreate(device), { concurrency: 1 });
}

module.exports = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const Promise = require('bluebird');

const { discoverServices } = require('./peripheral/bluetooth.discoverServices');
const { discoverCharacteristics } = require('./service/bluetooth.discoverCharacteristics');
const { discoverServices } = require('../utils/peripheral/bluetooth.discoverServices');
const { discoverCharacteristics } = require('../utils/service/bluetooth.discoverCharacteristics');

/**
* @description Connects to peripheral, discovers all needed, to applu action.
Expand Down
46 changes: 46 additions & 0 deletions server/services/bluetooth/lib/commands/bluetooth.poll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const Promise = require('bluebird');

const logger = require('../../../../utils/logger');
const { EVENTS } = require('../../../../utils/constants');

const { decodeValue } = require('../device/bluetooth.information');

/**
* @description Poll value of a Bluetooth device
* @param {Object} device - The device to control.
* @returns {Promise} Promise of all read values.
* @example
* await bluetooth.poll({ external_id: 'bluetooth:uuid'});
*/
async function poll(device) {
const [, peripheralUuid] = device.external_id.split(':');

const readFeature = (feature, peripheral) => {
const featureExternalId = feature.external_id;
const [, , serviceUuid, characteristicUuid] = featureExternalId.split(':');

return this.readDevice(peripheral, serviceUuid, characteristicUuid)
.then((value) => {
const state = decodeValue(serviceUuid, characteristicUuid, feature, value);
this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: featureExternalId,
state,
});
return state;
})
.catch((e) => {
logger.warn(e.message);
return Promise.resolve();
});
};

const readFeatures = (peripheral) => {
return Promise.map(device.features, (feature) => readFeature(feature, peripheral), { concurrency: 1 });
};

return this.applyOnPeripheral(peripheralUuid, readFeatures);
}

module.exports = {
poll,
};
47 changes: 47 additions & 0 deletions server/services/bluetooth/lib/commands/bluetooth.postCreate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const Promise = require('bluebird');

const logger = require('../../../../utils/logger');
const { EVENTS } = require('../../../../utils/constants');
const { decodeValue } = require('../device/bluetooth.information');

/**
* @description Subscribe to peripheral notification on device creation.
* @param {Object} device - Newly created Gladys device.
* @returns {Promise} All subscription promises.
* @example
* await bluetooth.postCreate(device);
*/
async function postCreate(device) {
const [, peripheralUuid] = device.external_id.split(':');

const subscribe = (peripheral) => {
return Promise.map(
device.features,
(feature) => {
const [, , serviceUuid, characteristicUuid] = feature.external_id.split(':');

const onNotify = (value) => {
this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: feature.external_id,
state: decodeValue(serviceUuid, characteristicUuid, feature, value),
});
};

return this.subscribeDevice(peripheral, serviceUuid, characteristicUuid, onNotify);
},
{ concurrency: 1 },
).catch((e) => {
logger.error(e.message);
return Promise.resolve();
});
};

return this.applyOnPeripheral(peripheralUuid, subscribe, true).catch((e) => {
logger.error(e.message);
return Promise.resolve();
});
}

module.exports = {
postCreate,
};
37 changes: 37 additions & 0 deletions server/services/bluetooth/lib/commands/bluetooth.postDelete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const Promise = require('bluebird');

const logger = require('../../../../utils/logger');

/**
* @description Unsubscribe to peripheral notification on device delete.
* @param {Object} device - Newly created Gladys device.
* @returns {Promise} All subscription promises.
* @example
* await bluetooth.postDelete(device);
*/
async function postDelete(device) {
const [, peripheralUuid] = device.external_id.split(':');

const unsubscribe = (peripheral) => {
return Promise.map(
device.features,
(feature) => {
const [, , serviceUuid, characteristicUuid] = feature.external_id.split(':');
return this.unsubscribeDevice(peripheral, serviceUuid, characteristicUuid);
},
{ concurrency: 1 },
).catch((e) => {
logger.error(e.message);
return Promise.resolve();
});
};

return this.applyOnPeripheral(peripheralUuid, unsubscribe, true).catch((e) => {
logger.error(e.message);
return Promise.resolve();
});
}

module.exports = {
postDelete,
};
45 changes: 8 additions & 37 deletions server/services/bluetooth/lib/commands/bluetooth.readDevice.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,18 @@
const Promise = require('bluebird');

const logger = require('../../../../utils/logger');
const { EVENTS } = require('../../../../utils/constants');

const { decodeValue } = require('../device/bluetooth.information');
const { read } = require('../utils/characteristic/bluetooth.read');
const { getCharacteristic } = require('../utils/bluetooth.getCharacteristic');

/**
* @description Poll value of a Bluetooth device
* @param {Object} device - The device to control.
* @description Read value of a Bluetooth device
* @param {string} peripheral - Connected Noble peripheral.
* @param {string} serviceUuid - Service UUID.
* @param {string} characteristicUuid - Characteristic UUID.
* @returns {Promise} Promise of all read values.
* @example
* await bluetooth.readDevice({ external_id: 'bluetooth:uuid'});
*/
async function readDevice(device) {
const [, peripheralUuid] = device.external_id.split(':');

const readFeature = (feature, peripheral) => {
const featureExternalId = feature.external_id;
const [, , serviceUuid, characteristicUuid] = featureExternalId.split(':');

return getCharacteristic(peripheral, serviceUuid, characteristicUuid)
.then((characteristic) => read(characteristic))
.then((value) => {
const state = decodeValue(serviceUuid, characteristicUuid, feature, value);
this.gladys.event.emit(EVENTS.DEVICE.NEW_STATE, {
device_feature_external_id: featureExternalId,
state,
});
return state;
})
.catch((e) => {
logger.warn(e.message);
return Promise.resolve();
});
};

const readFeatures = (peripheral) => {
return Promise.map(device.features, (feature) => readFeature(feature, peripheral), { concurrency: 1 });
};

return this.applyOnPeripheral(peripheralUuid, readFeatures);
async function readDevice(peripheral, serviceUuid, characteristicUuid) {
return this.getCharacteristic(peripheral, serviceUuid, characteristicUuid).then((characteristic) =>
read(characteristic),
);
}

module.exports = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const { setDeviceParam } = require('../../../../utils/setDeviceParam');
const { INFORMATION_SERVICES } = require('../device/bluetooth.information');
const { PARAMS } = require('../utils/bluetooth.constants');
const { read } = require('../utils/characteristic/bluetooth.read');
const { getCharacteristic } = require('../utils/bluetooth.getCharacteristic');

/**
* @description Look for peripheral details.
Expand All @@ -34,7 +33,7 @@ async function scanDevice(peripheralUuid) {
Promise.map(
Object.keys(INFORMATION_SERVICES[serviceUuid]),
(characteristicUuid) => {
return getCharacteristic(peripheral, serviceUuid, characteristicUuid)
return this.getCharacteristic(peripheral, serviceUuid, characteristicUuid)
.then((characteristic) => {
const actionMapper = INFORMATION_SERVICES[serviceUuid][characteristicUuid];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const Promise = require('bluebird');

const { subscribe } = require('../utils/characteristic/bluetooth.subscribe');
const { read } = require('../utils/characteristic/bluetooth.read');

/**
* @description Subscribes to peripheral characteristic.
* @param {Object} peripheral - Connected Noble peripheral.
* @param {string} serviceUuid - Service UUID.
* @param {string} characteristicUuid - Characteristic UUID.
* @param {Object} onNotify - Value callback.
* @returns {Promise<Object>} The write value.
* @example
* await subscribeDevice({ uuid: 'peripheral' }, 'service1', 'char1', () => console.log('done'))
*/
async function subscribeDevice(peripheral, serviceUuid, characteristicUuid, onNotify) {
return this.getCharacteristic(peripheral, serviceUuid, characteristicUuid).then((characteristic) =>
subscribe(characteristic, onNotify).then(() => read(characteristic).then((value) => onNotify(value))),
);
}

module.exports = {
subscribeDevice,
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const Promise = require('bluebird');

const { unsubscribe } = require('../utils/characteristic/bluetooth.unsubscribe');

/**
* @description Unsubscribes to peripheral characteristic.
* @param {Object} peripheral - Connected Noble peripheral.
* @param {string} serviceUuid - Service UUID.
* @param {string} characteristicUuid - Characteristic UUID.
* @returns {Promise<Object>} Unscription status.
* @example
* await subscribeDevice({ uuid: 'peripheral' }, 'service1', 'char1')
*/
async function unsubscribeDevice(peripheral, serviceUuid, characteristicUuid) {
return this.getCharacteristic(peripheral, serviceUuid, characteristicUuid).then((characteristic) =>
unsubscribe(characteristic),
);
}

module.exports = {
unsubscribeDevice,
};
10 changes: 4 additions & 6 deletions server/services/bluetooth/lib/commands/bluetooth.writeDevice.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
const Promise = require('bluebird');

const { write } = require('../utils/characteristic/bluetooth.write');
const { getCharacteristic } = require('../utils/bluetooth.getCharacteristic');

/**
* @description Write specific value to requested characteristic.
* @param {string} peripheral - Connected Noble peripheral.
* @param {string} serviceUuid - Service UUID.
* @param {string} characteristicUuid - Characteristic UUID.
* @param {Array | Buffer} value - Value to send to peripheral.
* @param {boolean} withoutResponse - Use "write without response" property (default false).
* @returns {Promise<Object>} The write value.
* @example
* await writeDevice({ uuid: 'peripheral' }, 'service1', 'char1')
*/
async function writeDevice(peripheral, serviceUuid, characteristicUuid, value) {
return getCharacteristic(peripheral, serviceUuid, characteristicUuid).then((characteristic) =>
write(characteristic, value),
async function writeDevice(peripheral, serviceUuid, characteristicUuid, value, withoutResponse = false) {
return this.getCharacteristic(peripheral, serviceUuid, characteristicUuid).then((characteristic) =>
write(characteristic, value, withoutResponse),
);
}

Expand Down
Loading

0 comments on commit 6f92880

Please sign in to comment.