commit
2e4c8540f5
10 changed files with 760 additions and 33 deletions
|
|
@ -22,6 +22,7 @@ RUN apt-get update \
|
||||||
#
|
#
|
||||||
# Verify git and needed tools are installed
|
# Verify git and needed tools are installed
|
||||||
&& apt-get -y install git iproute2 procps \
|
&& 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
|
# Remove outdated yarn from /opt and install via package
|
||||||
# so it can be easily updated via apt-get upgrade yarn
|
# so it can be easily updated via apt-get upgrade yarn
|
||||||
|
|
@ -53,5 +54,7 @@ RUN apt-get update \
|
||||||
&& apt-get clean -y \
|
&& apt-get clean -y \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN npm install -g node-gyp
|
||||||
|
|
||||||
# Switch back to dialog for any ad-hoc use of apt-get
|
# Switch back to dialog for any ad-hoc use of apt-get
|
||||||
ENV DEBIAN_FRONTEND=dialog
|
ENV DEBIAN_FRONTEND=dialog
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ COPY ./main.js /plejd/
|
||||||
COPY ./mqtt.js /plejd/
|
COPY ./mqtt.js /plejd/
|
||||||
COPY ./package.json /plejd/
|
COPY ./package.json /plejd/
|
||||||
COPY ./ble.js /plejd/
|
COPY ./ble.js /plejd/
|
||||||
|
COPY ./ble.bluez.js /plejd/
|
||||||
|
|
||||||
ARG BUILD_ARCH
|
ARG BUILD_ARCH
|
||||||
|
|
||||||
|
|
@ -33,6 +34,8 @@ RUN \
|
||||||
git \
|
git \
|
||||||
nodejs=10.16.3-r0 \
|
nodejs=10.16.3-r0 \
|
||||||
npm=10.16.3-r0 \
|
npm=10.16.3-r0 \
|
||||||
|
dbus-dev \
|
||||||
|
glib-dev \
|
||||||
\
|
\
|
||||||
&& npm config set unsafe-perm true
|
&& npm config set unsafe-perm true
|
||||||
|
|
||||||
|
|
|
||||||
16
plejd/api.js
16
plejd/api.js
|
|
@ -71,7 +71,7 @@ class PlejdApi extends EventEmitter {
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (error.response.status === 400) {
|
if (error.response.status === 400) {
|
||||||
console.log('error: server returned status 400. probably invalid credentials, please verify.');
|
console.log('error: server returned status 400. probably invalid credentials, please verify.');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log('error: unable to retrieve session token response: ' + error);
|
console.log('error: unable to retrieve session token response: ' + error);
|
||||||
|
|
@ -100,11 +100,11 @@ class PlejdApi extends EventEmitter {
|
||||||
self.site = response.data.result.find(x => x.site.title == self.siteName);
|
self.site = response.data.result.find(x => x.site.title == self.siteName);
|
||||||
self.cryptoKey = self.site.plejdMesh.cryptoKey;
|
self.cryptoKey = self.site.plejdMesh.cryptoKey;
|
||||||
|
|
||||||
this.emit('ready', self.cryptoKey);
|
|
||||||
//callback(self.cryptoKey);
|
//callback(self.cryptoKey);
|
||||||
|
this.emit('ready', self.cryptoKey);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log('error: unable to retrieve the crypto key. error: ' + error + ' (code: ' + error.response.status + ')');
|
console.log('error: unable to retrieve the crypto key. error: ' + error);
|
||||||
return Promise.reject('unable to retrieve the crypto key. error: ' + error);
|
return Promise.reject('unable to retrieve the crypto key. error: ' + error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +144,9 @@ class PlejdApi extends EventEmitter {
|
||||||
name: device.title,
|
name: device.title,
|
||||||
type: type,
|
type: type,
|
||||||
typeName: name,
|
typeName: name,
|
||||||
dimmable: dimmable
|
dimmable: dimmable,
|
||||||
|
version: plejdDevice.firmware.version,
|
||||||
|
serialNumber: plejdDevice.deviceId
|
||||||
};
|
};
|
||||||
|
|
||||||
logger(JSON.stringify(newDevice));
|
logger(JSON.stringify(newDevice));
|
||||||
|
|
@ -173,9 +175,9 @@ class PlejdApi extends EventEmitter {
|
||||||
typeName: 'Room',
|
typeName: 'Room',
|
||||||
dimmable: roomDevices[roomId].find(x => x.dimmable).length > 0
|
dimmable: roomDevices[roomId].find(x => x.dimmable).length > 0
|
||||||
};
|
};
|
||||||
|
|
||||||
logger(JSON.stringify(newDevice));
|
logger(JSON.stringify(newDevice));
|
||||||
|
|
||||||
devices.push(newDevice);
|
devices.push(newDevice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +207,7 @@ class PlejdApi extends EventEmitter {
|
||||||
// Unknown
|
// Unknown
|
||||||
return { name: "-unknown-", type: 'light', dimmable: false };
|
return { name: "-unknown-", type: 'light', dimmable: false };
|
||||||
case 10:
|
case 10:
|
||||||
return { name: "-unknown-", type: 'light', dimmable: false };
|
return { name: "-unknown-", type: 'light', dimmable: false };
|
||||||
case 12:
|
case 12:
|
||||||
// Unknown
|
// Unknown
|
||||||
return { name: "-unknown-", type: 'light', dimmable: false };
|
return { name: "-unknown-", type: 'light', dimmable: false };
|
||||||
|
|
|
||||||
601
plejd/ble.bluez.js
Normal file
601
plejd/ble.bluez.js
Normal file
|
|
@ -0,0 +1,601 @@
|
||||||
|
const dbus = require('dbus-next');
|
||||||
|
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-ble', msg);
|
||||||
|
if (debug === 'console') {
|
||||||
|
return consoleLogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
// > /dev/null
|
||||||
|
return _.noop;
|
||||||
|
};
|
||||||
|
|
||||||
|
const logger = getLogger();
|
||||||
|
|
||||||
|
// UUIDs
|
||||||
|
const PLEJD_SERVICE = '31ba0001-6085-4726-be45-040c957391b5';
|
||||||
|
const DATA_UUID = '31ba0004-6085-4726-be45-040c957391b5';
|
||||||
|
const LAST_DATA_UUID = '31ba0005-6085-4726-be45-040c957391b5';
|
||||||
|
const AUTH_UUID = '31ba0009-6085-4726-be45-040c957391b5';
|
||||||
|
const PING_UUID = '31ba000a-6085-4726-be45-040c957391b5';
|
||||||
|
|
||||||
|
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_OM_INTERFACE = 'org.freedesktop.DBus.ObjectManager';
|
||||||
|
const DBUS_PROP_INTERFACE = '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');
|
||||||
|
|
||||||
|
this.plejdService = null;
|
||||||
|
this.bleDevices = [];
|
||||||
|
this.plejdDevices = {};
|
||||||
|
this.connectEventHooked = false;
|
||||||
|
|
||||||
|
// Holds a reference to all characteristics
|
||||||
|
this.characteristics = {
|
||||||
|
data: null,
|
||||||
|
lastData: null,
|
||||||
|
lastDataProperties: null,
|
||||||
|
auth: null,
|
||||||
|
ping: null
|
||||||
|
};
|
||||||
|
|
||||||
|
this.bus = dbus.systemBus();
|
||||||
|
this.adapter = null;
|
||||||
|
|
||||||
|
logger('wiring events and waiting for BLE interface to power up.');
|
||||||
|
this.wireEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (this.objectManager) {
|
||||||
|
this.objectManager.removeAllListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bleDevices = [];
|
||||||
|
this.characteristics = {
|
||||||
|
data: null,
|
||||||
|
lastData: null,
|
||||||
|
lastDataProperties: null,
|
||||||
|
auth: null,
|
||||||
|
ping: null
|
||||||
|
};
|
||||||
|
clearInterval(this.pingRef);
|
||||||
|
|
||||||
|
const bluez = await this.bus.getProxyObject(BLUEZ_SERVICE_NAME, '/');
|
||||||
|
this.objectManager = await bluez.getInterface(DBUS_OM_INTERFACE);
|
||||||
|
|
||||||
|
// We need to find the ble interface which implements the Adapter1 interface
|
||||||
|
const managedObjects = await this.objectManager.GetManagedObjects();
|
||||||
|
let result = await this._getInterface(managedObjects, BLUEZ_ADAPTER_ID);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
this.adapter = result[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.adapter) {
|
||||||
|
console.log('plejd-ble: error: unable to find a bluetooth adapter that is compatible.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let path of Object.keys(managedObjects)) {
|
||||||
|
const interfaces = Object.keys(managedObjects[path]);
|
||||||
|
|
||||||
|
if (interfaces.indexOf(BLUEZ_DEVICE_ID) > -1) {
|
||||||
|
const proxyObject = await this.bus.getProxyObject(BLUEZ_SERVICE_NAME, path);
|
||||||
|
const device = await proxyObject.getInterface(BLUEZ_DEVICE_ID);
|
||||||
|
|
||||||
|
const connected = managedObjects[path][BLUEZ_DEVICE_ID].Connected.value;
|
||||||
|
|
||||||
|
if (connected) {
|
||||||
|
console.log('plejd-ble: disconnecting ' + path);
|
||||||
|
await device.Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.adapter.RemoveDevice(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.objectManager.on('InterfacesAdded', this.onInterfacesAdded.bind(this));
|
||||||
|
|
||||||
|
this.adapter.SetDiscoveryFilter({
|
||||||
|
'UUIDs': new dbus.Variant('as', [PLEJD_SERVICE]),
|
||||||
|
'Transport': new dbus.Variant('s', 'le')
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.adapter.StartDiscovery();
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
await this._internalInit();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _internalInit() {
|
||||||
|
logger('got ' + this.bleDevices.length + ' device(s).');
|
||||||
|
|
||||||
|
for (const plejd of this.bleDevices) {
|
||||||
|
logger('inspecting ' + plejd['path']);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const proxyObject = await this.bus.getProxyObject(BLUEZ_SERVICE_NAME, plejd['path']);
|
||||||
|
const device = await proxyObject.getInterface(BLUEZ_DEVICE_ID);
|
||||||
|
const properties = await proxyObject.getInterface(DBUS_PROP_INTERFACE);
|
||||||
|
|
||||||
|
plejd['rssi'] = (await properties.Get(BLUEZ_DEVICE_ID, 'RSSI')).value;
|
||||||
|
plejd['instance'] = device;
|
||||||
|
|
||||||
|
logger('discovered ' + plejd['path'] + ' with rssi ' + plejd['rssi']);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log('plejd-ble: failed inspecting ' + plejd['path'] + ' error: ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedDevices = this.bleDevices.sort((a, b) => b['rssi'] - a['rssi']);
|
||||||
|
let connectedDevice = null;
|
||||||
|
|
||||||
|
for (const plejd of sortedDevices) {
|
||||||
|
try {
|
||||||
|
console.log('plejd-ble: connecting to ' + plejd['path']);
|
||||||
|
await plejd['instance'].Connect();
|
||||||
|
connectedDevice = plejd;
|
||||||
|
break
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log('plejd-ble: failed connecting to plejd, error: ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(async () => {
|
||||||
|
await this.onDeviceConnected(connectedDevice);
|
||||||
|
await this.adapter.StopDiscovery();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _getInterface(managedObjects, iface) {
|
||||||
|
const managedPaths = Object.keys(managedObjects);
|
||||||
|
|
||||||
|
for (let path of managedPaths) {
|
||||||
|
const pathInterfaces = Object.keys(managedObjects[path]);
|
||||||
|
if (pathInterfaces.indexOf(iface) > -1) {
|
||||||
|
logger('found ble interface \'' + iface + '\' at ' + path);
|
||||||
|
try {
|
||||||
|
const adapterObject = await this.bus.getProxyObject(BLUEZ_SERVICE_NAME, path);
|
||||||
|
return [path, adapterObject.getInterface(iface), adapterObject];
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log('plejd-ble: error: failed to get interface \'' + iface + '\': ' + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onInterfacesAdded(path, interfaces) {
|
||||||
|
const [adapter, dev, service, characteristic] = path.split('/').slice(3);
|
||||||
|
const interfaceKeys = Object.keys(interfaces);
|
||||||
|
|
||||||
|
if (interfaceKeys.indexOf(BLUEZ_DEVICE_ID) > -1) {
|
||||||
|
if (interfaces[BLUEZ_DEVICE_ID]['UUIDs'].value.indexOf(PLEJD_SERVICE) > -1) {
|
||||||
|
logger('found Plejd service on ' + path);
|
||||||
|
this.bleDevices.push({ 'path': path });
|
||||||
|
} else {
|
||||||
|
console.log('uh oh, no Plejd device.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
async authenticate() {
|
||||||
|
console.log('authenticate()');
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//logger('sending challenge to device');
|
||||||
|
await this.characteristics.auth.WriteValue([0], {});
|
||||||
|
//logger('reading response from device');
|
||||||
|
const challenge = await this.characteristics.auth.ReadValue({});
|
||||||
|
const response = this._createChallengeResponse(this.cryptoKey, Buffer.from(challenge));
|
||||||
|
//logger('responding to authenticate');
|
||||||
|
await this.characteristics.auth.WriteValue([...response], {});
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log('plejd-ble: error: failed to authenticate: ' + err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// auth done, start ping
|
||||||
|
await this.startPing();
|
||||||
|
|
||||||
|
// After we've authenticated, we need to hook up the event listener
|
||||||
|
// for changes to lastData.
|
||||||
|
this.characteristics.lastDataProperties.on('PropertiesChanged', this.onLastDataUpdated.bind(this));
|
||||||
|
this.characteristics.lastData.StartNotify();
|
||||||
|
}
|
||||||
|
|
||||||
|
async write(data, retry = true) {
|
||||||
|
if (!this.plejdService || !this.characteristics.data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('plejd-ble: sending ' + data.length + ' byte(s) of data to Plejd');
|
||||||
|
const encryptedData = this._encryptDecrypt(this.cryptoKey, this.plejdService.addr, data);
|
||||||
|
await this.characteristics.data.WriteValue([...encryptedData], {});
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log('plejd-ble: write failed ' + err);
|
||||||
|
setTimeout(async () => {
|
||||||
|
await this.init();
|
||||||
|
|
||||||
|
if (retry) {
|
||||||
|
logger('reconnected and retrying to write');
|
||||||
|
await this.write(data, false);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async startPing() {
|
||||||
|
console.log('startPing()');
|
||||||
|
clearInterval(this.pingRef);
|
||||||
|
|
||||||
|
this.pingRef = setInterval(async () => {
|
||||||
|
logger('ping');
|
||||||
|
await this.ping();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPingSuccess(nr) {
|
||||||
|
logger('pong: ' + nr);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onPingFailed(error) {
|
||||||
|
logger('onPingFailed(' + error + ')');
|
||||||
|
console.log('plejd-ble: ping failed, reconnecting.');
|
||||||
|
|
||||||
|
clearInterval(this.pingRef);
|
||||||
|
await this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async ping() {
|
||||||
|
logger('ping()');
|
||||||
|
|
||||||
|
var ping = crypto.randomBytes(1);
|
||||||
|
let pong = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.characteristics.ping.WriteValue([...ping], {});
|
||||||
|
pong = await this.characteristics.ping.ReadValue({});
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log('error: writing to plejd: ' + err);
|
||||||
|
this.emit('pingFailed', 'write error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((ping[0] + 1) & 0xff) !== pong[0]) {
|
||||||
|
console.log('error: plejd ping failed');
|
||||||
|
this.emit('pingFailed', 'plejd ping failed ' + ping[0] + ' - ' + pong[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('pingSuccess', pong[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _processPlejdService(path, characteristics) {
|
||||||
|
const proxyObject = await this.bus.getProxyObject(BLUEZ_SERVICE_NAME, path);
|
||||||
|
const service = await proxyObject.getInterface(GATT_SERVICE_ID);
|
||||||
|
const properties = await proxyObject.getInterface(DBUS_PROP_INTERFACE);
|
||||||
|
|
||||||
|
const uuid = (await properties.Get(GATT_SERVICE_ID, 'UUID')).value;
|
||||||
|
if (uuid !== PLEJD_SERVICE) {
|
||||||
|
console.log('plejd-ble: not a Plejd device.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dev = (await properties.Get(GATT_SERVICE_ID, 'Device')).value;
|
||||||
|
const regex = /dev_([0-9A-F_]+)$/;
|
||||||
|
const dirtyAddr = regex.exec(dev);
|
||||||
|
const addr = this._reverseBuffer(
|
||||||
|
Buffer.from(
|
||||||
|
String(dirtyAddr[1])
|
||||||
|
.replace(/\-/g, '')
|
||||||
|
.replace(/\_/g, '')
|
||||||
|
.replace(/\:/g, ''), 'hex'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const chPath of characteristics) {
|
||||||
|
const chProxyObject = await this.bus.getProxyObject(BLUEZ_SERVICE_NAME, chPath);
|
||||||
|
const ch = await chProxyObject.getInterface(GATT_CHRC_ID);
|
||||||
|
const prop = await chProxyObject.getInterface(DBUS_PROP_INTERFACE);
|
||||||
|
|
||||||
|
const chUuid = (await prop.Get(GATT_CHRC_ID, 'UUID')).value;
|
||||||
|
|
||||||
|
if (chUuid === DATA_UUID) {
|
||||||
|
logger('found DATA characteristic.');
|
||||||
|
this.characteristics.data = ch;
|
||||||
|
}
|
||||||
|
else if (chUuid === LAST_DATA_UUID) {
|
||||||
|
logger('found LAST_DATA characteristic.');
|
||||||
|
this.characteristics.lastData = ch;
|
||||||
|
this.characteristics.lastDataProperties = prop;
|
||||||
|
}
|
||||||
|
else if (chUuid === AUTH_UUID) {
|
||||||
|
logger('found AUTH characteristic.');
|
||||||
|
this.characteristics.auth = ch;
|
||||||
|
}
|
||||||
|
else if (chUuid === PING_UUID) {
|
||||||
|
logger('found PING characteristic.');
|
||||||
|
this.characteristics.ping = ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
addr: addr
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async onDeviceConnected(device) {
|
||||||
|
console.log('onDeviceConnected()');
|
||||||
|
|
||||||
|
const objects = await this.objectManager.GetManagedObjects();
|
||||||
|
let characteristics = [];
|
||||||
|
|
||||||
|
for (const path of Object.keys(objects)) {
|
||||||
|
const interfaces = Object.keys(objects[path]);
|
||||||
|
if (interfaces.indexOf(GATT_CHRC_ID) > -1) {
|
||||||
|
characteristics.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const path of Object.keys(objects)) {
|
||||||
|
const interfaces = Object.keys(objects[path]);
|
||||||
|
if (interfaces.indexOf(GATT_SERVICE_ID) > -1) {
|
||||||
|
let chPaths = [];
|
||||||
|
for (const c of characteristics) {
|
||||||
|
if (c[0] === '/') {
|
||||||
|
chPaths.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('trying ' + chPaths.length + ' characteristics');
|
||||||
|
|
||||||
|
this.plejdService = await this._processPlejdService(path, chPaths);
|
||||||
|
if (this.plejdService) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.plejdService) {
|
||||||
|
console.log('plejd-ble: error: unable to connect to the Plejd mesh.');
|
||||||
|
this.emit('connectFailed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.characteristics.auth) {
|
||||||
|
console.log('plejd-ble: error: unable to connect to the Plejd mesh.');
|
||||||
|
this.emit('connectFailed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.authenticate();
|
||||||
|
}
|
||||||
|
|
||||||
|
async onLastDataUpdated(iface, properties, invalidated) {
|
||||||
|
if (iface !== GATT_CHRC_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changedKeys = Object.keys(properties);
|
||||||
|
if (changedKeys.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = await properties['Value'];
|
||||||
|
if (!value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = value.value;
|
||||||
|
const decoded = this._encryptDecrypt(this.cryptoKey, this.plejdService.addr, 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;
|
||||||
|
|
||||||
|
this.on('pingFailed', this.onPingFailed.bind(self));
|
||||||
|
this.on('pingSuccess', this.onPingSuccess.bind(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
_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;
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "Plejd",
|
"name": "Plejd",
|
||||||
"version": "0.2.10",
|
"version": "0.3.0",
|
||||||
"slug": "plejd",
|
"slug": "plejd",
|
||||||
"description": "Adds support for the Swedish home automation devices from Plejd.",
|
"description": "Adds support for the Swedish home automation devices from Plejd.",
|
||||||
"url": "https://github.com/icanos/hassio-plejd/",
|
"url": "https://github.com/icanos/hassio-plejd/",
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
const api = require('./api');
|
const api = require('./api');
|
||||||
const mqtt = require('./mqtt');
|
const mqtt = require('./mqtt');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const PlejdService = require('./ble');
|
const PlejdService = require('./ble.bluez');
|
||||||
|
|
||||||
const version = "0.2.10";
|
const version = "0.3.0";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
console.log('starting Plejd add-on v. ' + version);
|
console.log('starting Plejd add-on v. ' + version);
|
||||||
|
|
@ -27,6 +27,15 @@ async function main() {
|
||||||
|
|
||||||
// init the BLE interface
|
// init the BLE interface
|
||||||
const plejd = new PlejdService(cryptoKey, true);
|
const plejd = new PlejdService(cryptoKey, true);
|
||||||
|
plejd.on('connectFailed', () => {
|
||||||
|
console.log('plejd-ble: were unable to connect, will retry connection in 10 seconds.');
|
||||||
|
setTimeout(() => {
|
||||||
|
plejd.init();
|
||||||
|
}, 10000);
|
||||||
|
});
|
||||||
|
|
||||||
|
plejd.init();
|
||||||
|
|
||||||
plejd.on('authenticated', () => {
|
plejd.on('authenticated', () => {
|
||||||
console.log('plejd: connected via bluetooth.');
|
console.log('plejd: connected via bluetooth.');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,18 @@ const getSettingsTopic = () => `plejd/settings`;
|
||||||
const getDiscoveryPayload = device => ({
|
const getDiscoveryPayload = device => ({
|
||||||
schema: 'json',
|
schema: 'json',
|
||||||
name: device.name,
|
name: device.name,
|
||||||
unique_id: `light.plejd.${device.name.toLowerCase().replace(/ /g, '')}`,
|
unique_id: device.serialNumber,
|
||||||
state_topic: getStateTopic(device),
|
state_topic: getStateTopic(device),
|
||||||
command_topic: getCommandTopic(device),
|
command_topic: getCommandTopic(device),
|
||||||
optimistic: false,
|
optimistic: false,
|
||||||
brightness: `${device.dimmable}`
|
brightness: `${device.dimmable}`,
|
||||||
|
device: {
|
||||||
|
identifiers: device.serialNumber,
|
||||||
|
manufacturer: 'Plejd',
|
||||||
|
model: device.typeName,
|
||||||
|
name: device.name,
|
||||||
|
sw_version: device.version
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
|
||||||
136
plejd/package-lock.json
generated
136
plejd/package-lock.json
generated
|
|
@ -13,22 +13,26 @@
|
||||||
"usb": "^1.6.0"
|
"usb": "^1.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@icanos/noble": {
|
"@nornagon/put": {
|
||||||
"version": "1.9.2-9",
|
"version": "0.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@icanos/noble/-/noble-1.9.2-9.tgz",
|
"resolved": "https://registry.npmjs.org/@nornagon/put/-/put-0.0.8.tgz",
|
||||||
"integrity": "sha512-u4zJS+Rl1cBJAd+Z0DiBCOqKhNOUor5fiT6Jtl3WAcF9QlAS+O22HC9yUaQB5DzcfpS5DpBdMpglUTG3xV/kQA==",
|
"integrity": "sha512-ugvXJjwF5ldtUpa7D95kruNJ41yFQDEKyF5CW4TgKJnh+W/zmlBzXXeKTyqIgwMFrkePN2JqOBqcF0M0oOunow=="
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"abbrev": {
|
"abbrev": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||||
},
|
},
|
||||||
|
"abstract-socket": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/abstract-socket/-/abstract-socket-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-YZJizsvS1aBua5Gd01woe4zuyYBGgSMeqDOB6/ChwdTI904KP6QGtJswXl4hcqWxbz86hQBe++HWV0hF1aGUtA==",
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"bindings": "^1.2.1",
|
||||||
|
"nan": "^2.12.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ansi-regex": {
|
"ansi-regex": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||||
|
|
@ -195,6 +199,21 @@
|
||||||
"type": "^1.0.1"
|
"type": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dbus-next": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dbus-next/-/dbus-next-0.8.1.tgz",
|
||||||
|
"integrity": "sha512-/sgwpcnCkLQuMOTF9I95x6qvJZCbTK2RAbHwh7C60VMroSU7rphydj3ujpqiSy5yq04aKc3meZIHpCOrZ2791g==",
|
||||||
|
"requires": {
|
||||||
|
"@nornagon/put": "0.0.8",
|
||||||
|
"abstract-socket": "^2.0.0",
|
||||||
|
"event-stream": "3.3.4",
|
||||||
|
"hexy": "^0.2.10",
|
||||||
|
"jsbi": "^2.0.5",
|
||||||
|
"long": "^4.0.0",
|
||||||
|
"safe-buffer": "^5.1.1",
|
||||||
|
"xml2js": "^0.4.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
|
|
@ -227,6 +246,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||||
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
||||||
},
|
},
|
||||||
|
"duplexer": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
|
||||||
|
"integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E="
|
||||||
|
},
|
||||||
"duplexify": {
|
"duplexify": {
|
||||||
"version": "3.7.1",
|
"version": "3.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
|
||||||
|
|
@ -320,6 +344,20 @@
|
||||||
"es5-ext": "~0.10.14"
|
"es5-ext": "~0.10.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"event-stream": {
|
||||||
|
"version": "3.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
|
||||||
|
"integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
|
||||||
|
"requires": {
|
||||||
|
"duplexer": "~0.1.1",
|
||||||
|
"from": "~0",
|
||||||
|
"map-stream": "~0.1.0",
|
||||||
|
"pause-stream": "0.0.11",
|
||||||
|
"split": "0.3",
|
||||||
|
"stream-combiner": "~0.0.4",
|
||||||
|
"through": "~2.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"expand-template": {
|
"expand-template": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||||
|
|
@ -375,6 +413,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"from": {
|
||||||
|
"version": "0.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
|
||||||
|
"integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4="
|
||||||
|
},
|
||||||
"fs": {
|
"fs": {
|
||||||
"version": "0.0.1-security",
|
"version": "0.0.1-security",
|
||||||
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
|
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
|
||||||
|
|
@ -475,6 +518,11 @@
|
||||||
"xtend": "^4.0.0"
|
"xtend": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hexy": {
|
||||||
|
"version": "0.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/hexy/-/hexy-0.2.11.tgz",
|
||||||
|
"integrity": "sha512-ciq6hFsSG/Bpt2DmrZJtv+56zpPdnq+NQ4ijEFrveKN0ZG1mhl/LdT1NQZ9se6ty1fACcI4d4vYqC9v8EYpH2A=="
|
||||||
|
},
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
|
@ -576,6 +624,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||||
},
|
},
|
||||||
|
"jsbi": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsbi/-/jsbi-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-TzO/62Hxeb26QMb4IGlI/5X+QLr9Uqp1FPkwp2+KOICW+Q+vSuFj61c8pkT6wAns4WcK56X7CmSHhJeDGWOqxQ=="
|
||||||
|
},
|
||||||
"json-stable-stringify-without-jsonify": {
|
"json-stable-stringify-without-jsonify": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
||||||
|
|
@ -596,6 +649,16 @@
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||||
},
|
},
|
||||||
|
"long": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
|
||||||
|
},
|
||||||
|
"map-stream": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
|
||||||
|
"integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ="
|
||||||
|
},
|
||||||
"mimic-response": {
|
"mimic-response": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.0.0.tgz",
|
||||||
|
|
@ -707,11 +770,6 @@
|
||||||
"integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==",
|
"integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==",
|
||||||
"optional": true
|
"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": {
|
"needle": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
|
||||||
|
|
@ -746,11 +804,6 @@
|
||||||
"semver": "^5.4.1"
|
"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": {
|
"node-pre-gyp": {
|
||||||
"version": "0.13.0",
|
"version": "0.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz",
|
||||||
|
|
@ -863,6 +916,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||||
},
|
},
|
||||||
|
"pause-stream": {
|
||||||
|
"version": "0.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
|
||||||
|
"integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
|
||||||
|
"requires": {
|
||||||
|
"through": "~2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"prebuild-install": {
|
"prebuild-install": {
|
||||||
"version": "5.3.3",
|
"version": "5.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.3.tgz",
|
||||||
|
|
@ -1034,6 +1095,14 @@
|
||||||
"nan": "^2.13.2"
|
"nan": "^2.13.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"split": {
|
||||||
|
"version": "0.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
|
||||||
|
"integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=",
|
||||||
|
"requires": {
|
||||||
|
"through": "2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"split2": {
|
"split2": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/split2/-/split2-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/split2/-/split2-3.1.1.tgz",
|
||||||
|
|
@ -1054,6 +1123,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"stream-combiner": {
|
||||||
|
"version": "0.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
|
||||||
|
"integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=",
|
||||||
|
"requires": {
|
||||||
|
"duplexer": "~0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"stream-shift": {
|
"stream-shift": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
|
||||||
|
|
@ -1142,6 +1219,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"through": {
|
||||||
|
"version": "2.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
|
||||||
|
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
|
||||||
|
},
|
||||||
"through2": {
|
"through2": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
|
||||||
|
|
@ -1273,6 +1355,20 @@
|
||||||
"ultron": "~1.1.0"
|
"ultron": "~1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"xml2js": {
|
||||||
|
"version": "0.4.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||||
|
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
|
||||||
|
"requires": {
|
||||||
|
"sax": ">=0.6.0",
|
||||||
|
"xmlbuilder": "~11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xmlbuilder": {
|
||||||
|
"version": "11.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||||
|
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
|
||||||
|
},
|
||||||
"xtend": {
|
"xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@abandonware/bluetooth-hci-socket": "0.5.3-3",
|
"@abandonware/bluetooth-hci-socket": "0.5.3-3",
|
||||||
"@icanos/noble": "^1.9.2-9",
|
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
"buffer-xor": "^2.0.2",
|
"buffer-xor": "^2.0.2",
|
||||||
|
"dbus-next": "^0.8.1",
|
||||||
"fs": "0.0.1-security",
|
"fs": "0.0.1-security",
|
||||||
"jspack": "0.0.4",
|
"jspack": "0.0.4",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
|
|
||||||
6
plejd/test/test.ble.bluez.js
Normal file
6
plejd/test/test.ble.bluez.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
const PlejdService = require('../ble.bluez');
|
||||||
|
|
||||||
|
const cryptoKey = '';
|
||||||
|
|
||||||
|
const plejd = new PlejdService(cryptoKey, true);
|
||||||
|
plejd.init();
|
||||||
Loading…
Add table
Add a link
Reference in a new issue