start converting ble to bluez
This commit is contained in:
parent
a3b1d1d5d9
commit
7a1ee5a504
4 changed files with 779 additions and 83 deletions
|
|
@ -22,6 +22,7 @@ RUN apt-get update \
|
|||
#
|
||||
# Verify git and needed tools are installed
|
||||
&& apt-get -y install git iproute2 procps \
|
||||
&& apt-get -y install libdbus-1-dev libglib2.0-dev \
|
||||
#
|
||||
# Remove outdated yarn from /opt and install via package
|
||||
# so it can be easily updated via apt-get upgrade yarn
|
||||
|
|
@ -53,5 +54,7 @@ RUN apt-get update \
|
|||
&& apt-get clean -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN npm install -g node-gyp
|
||||
|
||||
# Switch back to dialog for any ad-hoc use of apt-get
|
||||
ENV DEBIAN_FRONTEND=dialog
|
||||
|
|
|
|||
766
plejd/ble.bluez.js
Normal file
766
plejd/ble.bluez.js
Normal file
|
|
@ -0,0 +1,766 @@
|
|||
const dbus = require('dbus');
|
||||
const crypto = require('crypto');
|
||||
const xor = require('buffer-xor');
|
||||
const _ = require('lodash');
|
||||
const EventEmitter = require('events');
|
||||
|
||||
let debug = '';
|
||||
|
||||
const getLogger = () => {
|
||||
const consoleLogger = msg => console.log('plejd', msg);
|
||||
if (debug === 'console') {
|
||||
return consoleLogger;
|
||||
}
|
||||
|
||||
// > /dev/null
|
||||
return _.noop;
|
||||
};
|
||||
|
||||
const logger = getLogger();
|
||||
|
||||
// UUIDs
|
||||
const PLEJD_SERVICE = "31ba000160854726be45040c957391b5"
|
||||
const DATA_UUID = "31ba000460854726be45040c957391b5"
|
||||
const LAST_DATA_UUID = "31ba000560854726be45040c957391b5"
|
||||
const AUTH_UUID = "31ba000960854726be45040c957391b5"
|
||||
const PING_UUID = "31ba000a60854726be45040c957391b5"
|
||||
|
||||
const STATE_IDLE = 'idle';
|
||||
const STATE_SCANNING = 'scanning';
|
||||
const STATE_CONNECTING = 'connecting';
|
||||
const STATE_CONNECTED = 'connected';
|
||||
const STATE_AUTHENTICATED = 'authenticated';
|
||||
const STATE_DISCONNECTED = 'disconnected';
|
||||
const STATE_UNINITIALIZED = 'uninitialized';
|
||||
const STATE_INITIALIZED = 'initialized';
|
||||
|
||||
const BLE_CMD_DIM_CHANGE = '00c8';
|
||||
const BLE_CMD_DIM2_CHANGE = '0098';
|
||||
const BLE_CMD_STATE_CHANGE = '0097';
|
||||
const BLE_CMD_SCENE_TRIG = '0021';
|
||||
|
||||
const BLUEZ_SERVICE_NAME = 'org.bluez';
|
||||
const DBUS_OBJECT_MANAGER = 'org.freedesktop.DBus.ObjectManager';
|
||||
const DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties';
|
||||
|
||||
const BLUEZ_ADAPTER_ID = 'org.bluez.Adapter1';
|
||||
const BLUEZ_DEVICE_ID = 'org.bluez.Device1';
|
||||
const GATT_SERVICE_ID = 'org.bluez.GattService1';
|
||||
const GATT_CHRC_ID = 'org.bluez.GattCharacteristic1';
|
||||
|
||||
class PlejdService extends EventEmitter {
|
||||
constructor(cryptoKey, keepAlive = false) {
|
||||
super();
|
||||
|
||||
this.cryptoKey = Buffer.from(cryptoKey.replace(/-/g, ''), 'hex');
|
||||
|
||||
// Keeps track of the current state
|
||||
this.state = STATE_IDLE;
|
||||
// Keeps track of discovered devices
|
||||
this.devices = {};
|
||||
// Keeps track of the currently connected device
|
||||
this.device = null;
|
||||
this.deviceAddress = null;
|
||||
this.deviceIdx = 0;
|
||||
|
||||
this.writeQueue = [];
|
||||
|
||||
this.plejdDevices = {};
|
||||
this.connectEventHooked = false;
|
||||
|
||||
// Holds a reference to all characteristics
|
||||
this.characteristics = {
|
||||
data: null,
|
||||
lastData: null,
|
||||
auth: null,
|
||||
ping: null
|
||||
};
|
||||
|
||||
this.bus = dbus.registerService('system', null);
|
||||
|
||||
logger('wiring events and waiting for BLE interface to power up.');
|
||||
this.wireEvents();
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.objectManager) {
|
||||
this.objectManager.removeAllListeners();
|
||||
}
|
||||
|
||||
this.objectManager = await this.bus.getInterface(BLUEZ_SERVICE_NAME, '/', DBUS_OBJECT_MANAGER);
|
||||
|
||||
this.objectManager.on('InterfacesAdded', this.onInterfacesAdded.bind(this));
|
||||
this.objectManager.on('InterfacesRemoved', this.onInterfacesRemoved.bind(this));
|
||||
this.objectManager.GetManagedObjects((err, objs) => {
|
||||
Object.keys(objs).forEach((k) => this.onInterfacesAdded(k, objs[k]))
|
||||
});
|
||||
}
|
||||
|
||||
async onInterfacesAdded(path, interfaces) {
|
||||
const [adapter, dev, service, characteristic] = path.split('/').slice(3);
|
||||
|
||||
if ('org.bluez.Adapter1' in interfaces) {
|
||||
console.log('has org.bluez.Adapter1');
|
||||
|
||||
const props = interfaces['org.bluez.Adapter1'];
|
||||
console.log(props);
|
||||
this.adapter_props = props;
|
||||
}
|
||||
|
||||
if ('org.bluez.Device1' in interfaces) {
|
||||
console.log('has org.bluez.Device1');
|
||||
|
||||
const props = interfaces['org.bluez.Device1'];
|
||||
console.log(props);
|
||||
this.devices[props.Address] = path;
|
||||
this.emit('device', props.Address, props);
|
||||
}
|
||||
|
||||
if ('org.bluez.GattService1' in interfaces) {
|
||||
console.log('has org.bluez.GattService1');
|
||||
|
||||
const props = interfaces['org.bluez.GattService1'];
|
||||
console.log(props);
|
||||
|
||||
const iface = await this.getInterface('org.bluez', path, 'org.bluez.GattService1');
|
||||
const service = new Service(iface);
|
||||
|
||||
const dev = await this.getDevice(props.Device);
|
||||
|
||||
if (this._path[path]) {
|
||||
// Characteristic was registered before service, copy them over
|
||||
service.characteristics = this._path[path].characteristics || {};
|
||||
}
|
||||
|
||||
dev.services[props.UUID] = service;
|
||||
this._path[path] = service;
|
||||
}
|
||||
|
||||
if ('org.bluez.GattCharacteristic1' in interfaces) {
|
||||
console.log('has org.bluez.GattCharacteristic1');
|
||||
const props = interfaces['org.bluez.GattCharacteristic1'];
|
||||
console.log(props);
|
||||
|
||||
const iface = await this.getInterface('org.bluez', path, 'org.bluez.GattCharacteristic1');
|
||||
const changed = await this.getInterface('org.bluez', path, 'org.freedesktop.DBus.Properties');
|
||||
const characteristic = new Characteristic(iface, changed);
|
||||
|
||||
if (this._path[path]) {
|
||||
// Descriptor was registered before characteristic, copy them over
|
||||
characteristic.descriptors = this._path[path].descriptors || {};
|
||||
}
|
||||
|
||||
if (!this._path[props.Service]) {
|
||||
// service not jet created
|
||||
this._path[props.Service] = { characteristics: {} };
|
||||
}
|
||||
this._path[props.Service].characteristics[props.UUID] = characteristic;
|
||||
this._path[path] = characteristic;
|
||||
}
|
||||
}
|
||||
|
||||
async onInterfacesRemoved(path, interfaces) {
|
||||
const [adapter, dev, service, characteristic] = path.split('/').slice(3);
|
||||
}
|
||||
|
||||
updateSettings(settings) {
|
||||
if (settings.debug) {
|
||||
debug = 'console';
|
||||
}
|
||||
else {
|
||||
debug = '';
|
||||
}
|
||||
}
|
||||
|
||||
turnOn(id, command) {
|
||||
logger('turning on ' + id + ' at brightness ' + (!command.brightness ? 255 : command.brightness));
|
||||
const brightness = command.brightness ? command.brightness : 0;
|
||||
|
||||
if (command.transition) {
|
||||
// we have a transition time, split the target brightness
|
||||
// into pieces spread of the transition time
|
||||
const steps = command.transition * 2;
|
||||
const brightnessStep = brightness / steps;
|
||||
|
||||
let i = 0;
|
||||
const transitionRef = setInterval(() => {
|
||||
let currentBrightness = parseInt((brightnessStep * i) + 1);
|
||||
if (currentBrightness > 254) {
|
||||
currentBrightness = 254;
|
||||
}
|
||||
|
||||
this._turnOn(id, currentBrightness);
|
||||
|
||||
if (i >= steps) {
|
||||
clearInterval(transitionRef);
|
||||
}
|
||||
|
||||
i++;
|
||||
}, 500);
|
||||
}
|
||||
else {
|
||||
this._turnOn(id, brightness);
|
||||
}
|
||||
}
|
||||
|
||||
_turnOn(id, brightness) {
|
||||
var payload;
|
||||
if (!brightness || brightness === 0) {
|
||||
logger('no brightness specified, setting to previous known.');
|
||||
payload = Buffer.from((id).toString(16).padStart(2, '0') + '0110009701', 'hex');
|
||||
} else {
|
||||
logger('brightness is ' + brightness);
|
||||
brightness = brightness << 8 | brightness;
|
||||
payload = Buffer.from((id).toString(16).padStart(2, '0') + '0110009801' + (brightness).toString(16).padStart(4, '0'), 'hex');
|
||||
}
|
||||
|
||||
this.write(payload);
|
||||
}
|
||||
|
||||
turnOff(id, command) {
|
||||
logger('turning off ' + id);
|
||||
|
||||
if (command.transition) {
|
||||
// we have a transition time, split the target brightness (which will be 0)
|
||||
// into pieces spread of the transition time
|
||||
const initialBrightness = this.plejdDevices[id] ? this.plejdDevices[id].dim : 250;
|
||||
const steps = command.transition * 2;
|
||||
const brightnessStep = initialBrightness / steps;
|
||||
let currentBrightness = initialBrightness;
|
||||
|
||||
let i = 0;
|
||||
const transitionRef = setInterval(() => {
|
||||
currentBrightness = parseInt(initialBrightness - (brightnessStep * i));
|
||||
if (currentBrightness <= 0 || i >= steps) {
|
||||
clearInterval(transitionRef);
|
||||
|
||||
// finally, we turn it off
|
||||
this._turnOff(id);
|
||||
return;
|
||||
}
|
||||
|
||||
this._turnOn(id, currentBrightness);
|
||||
|
||||
i++;
|
||||
}, 500);
|
||||
}
|
||||
else {
|
||||
this._turnOff(id);
|
||||
}
|
||||
}
|
||||
|
||||
_turnOff(id) {
|
||||
var payload = Buffer.from((id).toString(16).padStart(2, '0') + '0110009700', 'hex');
|
||||
this.write(payload);
|
||||
}
|
||||
|
||||
scan() {
|
||||
console.log('scan()');
|
||||
|
||||
if (this.state === STATE_SCANNING) {
|
||||
console.log('error: already scanning, please wait.');
|
||||
return;
|
||||
}
|
||||
|
||||
// this.state = STATE_SCANNING;
|
||||
// noble.startScanning([PLEJD_SERVICE]);
|
||||
|
||||
// setTimeout(() => {
|
||||
// noble.stopScanning();
|
||||
// this.state = STATE_IDLE;
|
||||
|
||||
// const foundDeviceCount = Object.values(this.devices).length;
|
||||
// console.log('scan completed, found ' + foundDeviceCount + ' device(s).');
|
||||
|
||||
// if (foundDeviceCount == 0) {
|
||||
// console.log('warning: no devices found. will not do anything else.');
|
||||
// }
|
||||
// else {
|
||||
// this.emit('scanComplete', this.devices);
|
||||
// }
|
||||
// }, 5000);
|
||||
}
|
||||
|
||||
connect(uuid = null) {
|
||||
const self = this;
|
||||
if (this.state === STATE_CONNECTING) {
|
||||
console.log('warning: currently connecting to a device, please wait...');
|
||||
return;
|
||||
}
|
||||
|
||||
// if (!uuid) {
|
||||
// this.device = Object.values(this.devices)[this.deviceIdx];
|
||||
// }
|
||||
// else {
|
||||
// this.device = this.devices[uuid];
|
||||
// if (!this.device) {
|
||||
// console.log('error: could not find a device with uuid: ' + uuid);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!this.device) {
|
||||
// console.log('error: reached end of device list. cannot continue.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// this.deviceAddress = this._reverseBuffer(
|
||||
// Buffer.from(
|
||||
// String(this.device.address)
|
||||
// .replace(/\-/g, '')
|
||||
// .replace(/\:/g, ''), 'hex'
|
||||
// )
|
||||
// );
|
||||
|
||||
// console.log('connecting to ' + this.device.id + ' with addr ' + this.device.address + ' and rssi ' + this.device.rssi);
|
||||
// setTimeout(() => {
|
||||
// if (self.state !== STATE_CONNECTED && self.state !== STATE_AUTHENTICATED) {
|
||||
// if (self.deviceIdx < Object.keys(self.devices).length) {
|
||||
// logger('connection timed out after 10 s. cleaning up and trying next.');
|
||||
|
||||
// self.disconnect();
|
||||
|
||||
// self.deviceIdx++;
|
||||
// self.connect();
|
||||
// }
|
||||
// }
|
||||
// }, 10 * 1000);
|
||||
|
||||
// this.state = STATE_CONNECTING;
|
||||
|
||||
// if (!this.connectEventHooked) {
|
||||
// this.device.once('connect', (state) => {
|
||||
// self.onDeviceConnected(state);
|
||||
// this.connectEventHooked = false;
|
||||
// });
|
||||
// this.connectEventHooked = true;
|
||||
// }
|
||||
|
||||
// this.device.connect();
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
console.log('disconnect()');
|
||||
|
||||
clearInterval(this.pingRef);
|
||||
|
||||
// if (this.device) {
|
||||
// this.device.removeAllListeners('servicesDiscover');
|
||||
// this.device.removeAllListeners('connect');
|
||||
// this.device.removeAllListeners('disconnect');
|
||||
// }
|
||||
// if (this.characteristics.auth) {
|
||||
// this.characteristics.auth.removeAllListeners('read');
|
||||
// this.characteristics.auth.removeAllListeners('write');
|
||||
// }
|
||||
// if (this.characteristics.data) {
|
||||
// this.characteristics.data.removeAllListeners('read');
|
||||
// this.characteristics.data.removeAllListeners('write');
|
||||
// }
|
||||
// if (this.characteristics.lastData) {
|
||||
// this.characteristics.lastData.removeAllListeners('read');
|
||||
// this.characteristics.lastData.removeAllListeners('write');
|
||||
// }
|
||||
// if (this.characteristics.ping) {
|
||||
// this.characteristics.ping.removeAllListeners('read');
|
||||
// this.characteristics.ping.removeAllListeners('write');
|
||||
// }
|
||||
|
||||
|
||||
this.connectEventHooked = false;
|
||||
|
||||
// this.unsubscribeCharacteristics();
|
||||
// this.device.disconnect();
|
||||
|
||||
this.state = STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
authenticate() {
|
||||
console.log('authenticate()');
|
||||
const self = this;
|
||||
|
||||
if (this.state !== STATE_CONNECTED) {
|
||||
console.log('error: need to be connected and not previously authenticated (new connection).');
|
||||
return;
|
||||
}
|
||||
|
||||
// this.characteristics.auth.write(Buffer.from([0]), false, (err) => {
|
||||
// if (err) {
|
||||
// console.log('error: failed to authenticate: ' + err);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// self.characteristics.auth.read((err, data) => {
|
||||
// if (err) {
|
||||
// console.log('error: failed to read auth response: ' + err);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// var resp = self._createChallengeResponse(self.cryptoKey, data);
|
||||
// self.characteristics.auth.write(resp, false, (err) => {
|
||||
// if (err) {
|
||||
// console.log('error: failed to challenge: ' + err);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// self.state = STATE_AUTHENTICATED;
|
||||
// self.emit('authenticated');
|
||||
// });
|
||||
// })
|
||||
// });
|
||||
}
|
||||
|
||||
write(data) {
|
||||
if (this.state !== STATE_AUTHENTICATED) {
|
||||
logger('error: not connected.');
|
||||
this.writeQueue.push(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
// try {
|
||||
// const encryptedData = this._encryptDecrypt(this.cryptoKey, this.deviceAddress, data);
|
||||
// this.characteristics.data.write(encryptedData, false);
|
||||
|
||||
// let writeData;
|
||||
// while ((writeData = this.writeQueue.shift()) !== undefined) {
|
||||
// this.characteristics.data.write(this._encryptDecrypt(this.cryptoKey, this.deviceAddress, writeData), false);
|
||||
// }
|
||||
// }
|
||||
// catch (error) {
|
||||
// console.log('error: writing to plejd: ' + error);
|
||||
// console.log('will reconnect and try again.');
|
||||
// this.writeQueue.push(data);
|
||||
|
||||
// this.disconnect();
|
||||
// this.connect();
|
||||
// }
|
||||
}
|
||||
|
||||
onAuthenticated() {
|
||||
// Start ping
|
||||
logger('onAuthenticated()');
|
||||
this.startPing();
|
||||
}
|
||||
|
||||
startPing() {
|
||||
console.log('startPing()');
|
||||
clearInterval(this.pingRef);
|
||||
|
||||
this.pingRef = setInterval(async () => {
|
||||
if (this.state === STATE_AUTHENTICATED) {
|
||||
logger('ping');
|
||||
this.ping();
|
||||
}
|
||||
else if (this.state === STATE_DISCONNECTED) {
|
||||
console.log('warning: device disconnected, stop ping.');
|
||||
}
|
||||
else {
|
||||
console.log('error: ping failed, not connected.');
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
onPingSuccess(nr) {
|
||||
logger('pong: ' + nr);
|
||||
}
|
||||
|
||||
onPingFailed(error) {
|
||||
logger('onPingFailed(' + error + ')');
|
||||
|
||||
logger('ping failed, reconnecting.');
|
||||
this.disconnect();
|
||||
this.connect();
|
||||
// clearInterval(this.pingRef);
|
||||
|
||||
// this.unsubscribeCharacteristics();
|
||||
// this.state = STATE_DISCONNECTED;
|
||||
// this.characteristicState = STATE_UNINITIALIZED;
|
||||
|
||||
// this.connect(this.device.id);
|
||||
}
|
||||
|
||||
ping() {
|
||||
logger('ping()');
|
||||
|
||||
if (this.state !== STATE_AUTHENTICATED) {
|
||||
console.log('error: needs to be authenticated before pinging.');
|
||||
return;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
var ping = crypto.randomBytes(1);
|
||||
|
||||
// try {
|
||||
// this.characteristics.ping.write(ping, false, (err) => {
|
||||
// if (err) {
|
||||
// console.log('error: unable to send ping: ' + err);
|
||||
// self.emit('pingFailed');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// this.characteristics.ping.read((err, data) => {
|
||||
// if (err) {
|
||||
// console.log('error: unable to read ping: ' + err);
|
||||
// self.emit('pingFailed');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (((ping[0] + 1) & 0xff) !== data[0]) {
|
||||
// self.emit('pingFailed');
|
||||
// return;
|
||||
// }
|
||||
// else {
|
||||
// self.emit('pingSuccess', data[0]);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
// catch (error) {
|
||||
// console.log('error: writing to plejd: ' + error);
|
||||
// self.emit('pingFailed', error);
|
||||
// }
|
||||
}
|
||||
|
||||
onDeviceConnected(err) {
|
||||
console.log('onDeviceConnected()');
|
||||
const self = this;
|
||||
|
||||
// if (err) {
|
||||
// console.log('error: failed to connect to device: ' + err + '. picking next.');
|
||||
// this.deviceIdx++;
|
||||
// this.disconnect();
|
||||
// this.connect();
|
||||
// return;
|
||||
// }
|
||||
|
||||
// this.state = STATE_CONNECTED;
|
||||
|
||||
// if (this.characteristicState === STATE_UNINITIALIZED) {
|
||||
// // We need to discover the characteristics
|
||||
// logger('discovering services and characteristics');
|
||||
|
||||
// setTimeout(() => {
|
||||
// if (this.characteristicState === STATE_UNINITIALIZED) {
|
||||
// console.log('error: discovering characteristics timed out. trying next device.');
|
||||
// self.deviceIdx++;
|
||||
// self.disconnect();
|
||||
// self.connect();
|
||||
// }
|
||||
// }, 5000);
|
||||
|
||||
// this.device.discoverSomeServicesAndCharacteristics([PLEJD_SERVICE], [], async (err, services, characteristics) => {
|
||||
// if (err) {
|
||||
// console.log('error: failed to discover services: ' + err);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (self.state !== STATE_CONNECTED || self.characteristicState !== STATE_UNINITIALIZED) {
|
||||
// // in case our time out triggered before we got here.
|
||||
// console.log('warning: found characteristics in invalid state. ignoring.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// logger('found ' + characteristics.length + ' characteristic(s).');
|
||||
|
||||
// characteristics.forEach((ch) => {
|
||||
// if (DATA_UUID == ch.uuid) {
|
||||
// logger('found DATA characteristic.');
|
||||
// self.characteristics.data = ch;
|
||||
// }
|
||||
// else if (LAST_DATA_UUID == ch.uuid) {
|
||||
// logger('found LAST_DATA characteristic.');
|
||||
// self.characteristics.lastData = ch;
|
||||
// }
|
||||
// else if (AUTH_UUID == ch.uuid) {
|
||||
// logger('found AUTH characteristic.');
|
||||
// self.characteristics.auth = ch;
|
||||
// }
|
||||
// else if (PING_UUID == ch.uuid) {
|
||||
// logger('found PING characteristic.');
|
||||
// self.characteristics.ping = ch;
|
||||
// }
|
||||
// });
|
||||
|
||||
// if (self.characteristics.data
|
||||
// && self.characteristics.lastData
|
||||
// && self.characteristics.auth
|
||||
// && self.characteristics.ping) {
|
||||
|
||||
// self.characteristicState = STATE_INITIALIZED;
|
||||
|
||||
// // subscribe to notifications
|
||||
// this.subscribeCharacteristics();
|
||||
|
||||
// self.emit('deviceCharacteristicsComplete', self.device);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
onDeviceCharacteristicsComplete(device) {
|
||||
logger('onDeviceCharacteristicsComplete(' + device.id + ')');
|
||||
this.authenticate();
|
||||
}
|
||||
|
||||
onDeviceDiscovered(device) {
|
||||
logger('onDeviceDiscovered(' + device.id + ')');
|
||||
if (device.advertisement.localName === 'P mesh') {
|
||||
logger('device is P mesh');
|
||||
this.devices[device.id] = device;
|
||||
}
|
||||
}
|
||||
|
||||
onDeviceDisconnected() {
|
||||
logger('onDeviceDisconnected()');
|
||||
this.disconnect();
|
||||
|
||||
if (!this.device) {
|
||||
console.log('warning: reconnect will not be performed.');
|
||||
return;
|
||||
}
|
||||
|
||||
// we just want to reconnect
|
||||
// this.connect(this.device.id);
|
||||
}
|
||||
|
||||
onDeviceScanComplete() {
|
||||
console.log('onDeviceScanComplete()');
|
||||
console.log('trying to connect to the mesh network.');
|
||||
this.connect();
|
||||
}
|
||||
|
||||
// onInterfaceStateChanged(state) {
|
||||
// console.log('onInterfaceStateChanged(' + state + ')');
|
||||
|
||||
// if (state === 'poweredOn') {
|
||||
// this.scan();
|
||||
// }
|
||||
// else {
|
||||
// noble.stopScanning();
|
||||
// }
|
||||
// }
|
||||
|
||||
onLastDataUpdated(data, isNotification) {
|
||||
const decoded = this._encryptDecrypt(this.cryptoKey, this.deviceAddress, data);
|
||||
|
||||
let state = 0;
|
||||
let dim = 0;
|
||||
let device = parseInt(decoded[0], 10);
|
||||
|
||||
if (decoded.length < 5) {
|
||||
// ignore the notification since too small
|
||||
return;
|
||||
}
|
||||
|
||||
const cmd = decoded.toString('hex', 3, 5);
|
||||
|
||||
if (debug) {
|
||||
logger('raw event received: ' + decoded.toString('hex'));
|
||||
}
|
||||
|
||||
if (cmd === BLE_CMD_DIM_CHANGE || cmd === BLE_CMD_DIM2_CHANGE) {
|
||||
state = parseInt(decoded.toString('hex', 5, 6), 10);
|
||||
dim = parseInt(decoded.toString('hex', 6, 8), 16) >> 8;
|
||||
|
||||
logger('d: ' + device + ' got state+dim update: ' + state + ' - ' + dim);
|
||||
this.emit('stateChanged', device, { state: state, brightness: dim });
|
||||
}
|
||||
else if (cmd === BLE_CMD_STATE_CHANGE) {
|
||||
state = parseInt(decoded.toString('hex', 5, 6), 10);
|
||||
|
||||
logger('d: ' + device + ' got state update: ' + state);
|
||||
this.emit('stateChanged', device, { state: state });
|
||||
}
|
||||
else if (cmd === BLE_CMD_SCENE_TRIG) {
|
||||
const scene = parseInt(decoded.toString('hex', 5, 6), 10);
|
||||
this.emit('sceneTriggered', device, scene);
|
||||
}
|
||||
|
||||
this.plejdDevices[device] = {
|
||||
state: state,
|
||||
dim: dim
|
||||
};
|
||||
}
|
||||
|
||||
wireEvents() {
|
||||
console.log('wireEvents()');
|
||||
const self = this;
|
||||
|
||||
// noble.on('stateChange', this.onInterfaceStateChanged.bind(self));
|
||||
// noble.on('discover', this.onDeviceDiscovered.bind(self));
|
||||
// noble.on('disconnect', this.onDeviceDisconnected.bind(self));
|
||||
|
||||
this.on('scanComplete', this.onDeviceScanComplete.bind(this));
|
||||
this.on('deviceCharacteristicsComplete', this.onDeviceCharacteristicsComplete.bind(self));
|
||||
this.on('authenticated', this.onAuthenticated.bind(self));
|
||||
this.on('pingFailed', this.onPingFailed.bind(self));
|
||||
this.on('pingSuccess', this.onPingSuccess.bind(self));
|
||||
}
|
||||
|
||||
subscribeCharacteristics() {
|
||||
// if (this.characteristics.lastData) {
|
||||
// this.characteristics.lastData.subscribe((err) => {
|
||||
// if (err) {
|
||||
// console.log('error: could not subscribe to event.');
|
||||
// }
|
||||
// });
|
||||
// this.characteristics.lastData.on('data', this.onLastDataUpdated.bind(this));
|
||||
// }
|
||||
}
|
||||
|
||||
unsubscribeCharacteristics() {
|
||||
// if (this.characteristics.lastData) {
|
||||
// try {
|
||||
// this.characteristics.lastData.unsubscribe((err) => {
|
||||
// if (err) {
|
||||
// console.log('error: could not unsubscribe from event.');
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// catch (error) {
|
||||
// console.log('warning: could not unsubscribe from lastData, probably already disconnected: ' + error);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
_createChallengeResponse(key, challenge) {
|
||||
const intermediate = crypto.createHash('sha256').update(xor(key, challenge)).digest();
|
||||
const part1 = intermediate.subarray(0, 16);
|
||||
const part2 = intermediate.subarray(16);
|
||||
|
||||
const resp = xor(part1, part2);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
_encryptDecrypt(key, addr, data) {
|
||||
var buf = Buffer.concat([addr, addr, addr.subarray(0, 4)]);
|
||||
|
||||
var cipher = crypto.createCipheriv("aes-128-ecb", key, '');
|
||||
cipher.setAutoPadding(false);
|
||||
|
||||
var ct = cipher.update(buf).toString('hex');
|
||||
ct += cipher.final().toString('hex');
|
||||
ct = Buffer.from(ct, 'hex');
|
||||
|
||||
var output = "";
|
||||
for (var i = 0, length = data.length; i < length; i++) {
|
||||
output += String.fromCharCode(data[i] ^ ct[i % 16]);
|
||||
}
|
||||
|
||||
return Buffer.from(output, 'ascii');
|
||||
}
|
||||
|
||||
_reverseBuffer(src) {
|
||||
var buffer = Buffer.allocUnsafe(src.length)
|
||||
|
||||
for (var i = 0, j = src.length - 1; i <= j; ++i, --j) {
|
||||
buffer[i] = src[j]
|
||||
buffer[j] = src[i]
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PlejdService;
|
||||
89
plejd/package-lock.json
generated
89
plejd/package-lock.json
generated
|
|
@ -13,22 +13,6 @@
|
|||
"usb": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"@icanos/noble": {
|
||||
"version": "1.9.2-6",
|
||||
"resolved": "https://registry.npmjs.org/@icanos/noble/-/noble-1.9.2-6.tgz",
|
||||
"integrity": "sha512-+NxEW7nNEueqX8MknTdno3AZeFode56tzN+dMDW0TJjW96XG822DhoHmHeQZRylTd74r/8M5c4Sb9x/m45yiPw==",
|
||||
"requires": {
|
||||
"@abandonware/bluetooth-hci-socket": "^0.5.3-3",
|
||||
"debug": "^4.1.1",
|
||||
"napi-thread-safe-callback": "0.0.6",
|
||||
"node-addon-api": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"@types/zen-observable": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz",
|
||||
"integrity": "sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg=="
|
||||
},
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
|
|
@ -139,11 +123,6 @@
|
|||
"readable-stream": "> 1.0.0 < 3.0.0"
|
||||
}
|
||||
},
|
||||
"callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
|
||||
},
|
||||
"chownr": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
|
||||
|
|
@ -205,6 +184,14 @@
|
|||
"type": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"dbus": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/dbus/-/dbus-1.0.5.tgz",
|
||||
"integrity": "sha512-itMup/0lcjnwV4DULJtI8q37Nky713KE2b1QM4+W/0zlzAsMNJZURBtyENtnJc6BlHouqbur++PXWeEKDuGulg==",
|
||||
"requires": {
|
||||
"nan": "^2.13.2"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
|
|
@ -321,11 +308,6 @@
|
|||
"ext": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"esm": {
|
||||
"version": "3.2.25",
|
||||
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
|
||||
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
|
||||
},
|
||||
"event-emitter": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
|
||||
|
|
@ -565,14 +547,6 @@
|
|||
"resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz",
|
||||
"integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI="
|
||||
},
|
||||
"is-observable": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz",
|
||||
"integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==",
|
||||
"requires": {
|
||||
"symbol-observable": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"is-relative": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
|
||||
|
|
@ -730,11 +704,6 @@
|
|||
"integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==",
|
||||
"optional": true
|
||||
},
|
||||
"napi-thread-safe-callback": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/napi-thread-safe-callback/-/napi-thread-safe-callback-0.0.6.tgz",
|
||||
"integrity": "sha512-X7uHCOCdY4u0yamDxDrv3jF2NtYc8A1nvPzBQgvpoSX+WB3jAe2cVNsY448V1ucq7Whf9Wdy02HEUoLW5rJKWg=="
|
||||
},
|
||||
"needle": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
|
||||
|
|
@ -769,11 +738,6 @@
|
|||
"semver": "^5.4.1"
|
||||
}
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.1.tgz",
|
||||
"integrity": "sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ=="
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz",
|
||||
|
|
@ -841,11 +805,6 @@
|
|||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"observable-fns": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/observable-fns/-/observable-fns-0.4.0.tgz",
|
||||
"integrity": "sha512-2BFtEqza7sjLpgImAmagHK97mBwh3+bkwAZS/qF/4n2S8RzKsbdsdOczRBh+Piz7QgQZRAjTzI5vtxtOUgU+cQ=="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
|
|
@ -1118,11 +1077,6 @@
|
|||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
|
||||
},
|
||||
"symbol-observable": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
|
||||
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
|
||||
},
|
||||
"tar": {
|
||||
"version": "4.4.13",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
|
||||
|
|
@ -1175,20 +1129,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"threads": {
|
||||
"version": "1.0.0-beta.9",
|
||||
"resolved": "https://registry.npmjs.org/threads/-/threads-1.0.0-beta.9.tgz",
|
||||
"integrity": "sha512-ZjpQvqA78p+y4jtlhnQsKc8V9AwUvrWwOhy9FkFKWO24JHKte3oWllmjvUw896YqrZymsJvqJwlbUHV1CpVtKw==",
|
||||
"requires": {
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"callsites": "^3.1.0",
|
||||
"debug": "^4.1.1",
|
||||
"is-observable": "^1.1.0",
|
||||
"observable-fns": "^0.4.0",
|
||||
"tiny-worker": ">= 2",
|
||||
"zen-observable": "^0.8.14"
|
||||
}
|
||||
},
|
||||
"through2": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
|
||||
|
|
@ -1207,14 +1147,6 @@
|
|||
"xtend": "~4.0.0"
|
||||
}
|
||||
},
|
||||
"tiny-worker": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-worker/-/tiny-worker-2.3.0.tgz",
|
||||
"integrity": "sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==",
|
||||
"requires": {
|
||||
"esm": "^3.2.25"
|
||||
}
|
||||
},
|
||||
"to-absolute-glob": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz",
|
||||
|
|
@ -1337,11 +1269,6 @@
|
|||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
|
||||
},
|
||||
"zen-observable": {
|
||||
"version": "0.8.15",
|
||||
"resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz",
|
||||
"integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@abandonware/bluetooth-hci-socket": "0.5.3-3",
|
||||
"@icanos/noble": "^1.9.2-9",
|
||||
"dbus": "^1.0.5",
|
||||
"axios": "^0.19.0",
|
||||
"buffer-xor": "^2.0.2",
|
||||
"fs": "0.0.1-security",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue