Merge pull request #12 from icanos/dev

rewritten the ble communication layer
This commit is contained in:
Marcus Westin 2019-12-19 21:29:01 +01:00 committed by GitHub
commit e864a783a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 617 additions and 24 deletions

View file

@ -12,7 +12,7 @@ COPY ./config.json /plejd/
COPY ./main.js /plejd/ COPY ./main.js /plejd/
COPY ./mqtt.js /plejd/ COPY ./mqtt.js /plejd/
COPY ./package.json /plejd/ COPY ./package.json /plejd/
COPY ./plejd.js /plejd/ COPY ./ble.js /plejd/
ARG BUILD_ARCH ARG BUILD_ARCH

530
plejd/ble.js Normal file
View file

@ -0,0 +1,530 @@
const noble = require('@icanos/noble');
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';
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 = [];
// Holds a reference to all characteristics
this.characteristicState = STATE_UNINITIALIZED;
this.characteristics = {
data: null,
lastData: null,
auth: null,
ping: null
};
logger('wiring events and waiting for BLE interface to power up.');
this.wireEvents();
}
turnOn(id, brightness) {
logger('turning on ' + id + ' at brightness ' + brightness);
var payload;
if (!brightness) {
payload = Buffer.from((id).toString(16).padStart(2, '0') + '0110009701', 'hex');
} else {
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) {
logger('turning off ' + id);
var payload = Buffer.from((id).toString(16).padStart(2, '0') + '0110009700', 'hex');
this.write(payload);
}
scan() {
logger('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;
logger('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'
)
);
logger('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. trying next.');
self.deviceIdx++;
self.connect();
}
}
}, 10 * 1000);
this.state = STATE_CONNECTING;
this.device.connect((err) => {
self.onDeviceConnected(err);
});
}
reset() {
logger('reset()');
this.state = STATE_IDLE;
}
disconnect() {
logger('disconnect()');
if (this.state !== STATE_CONNECTED) {
return;
}
clearInterval(this.pingRef);
this.unsubscribeCharacteristics();
this.device.disconnect();
this.state = STATE_DISCONNECTED;
}
authenticate() {
logger('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;
}
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);
}
}
onAuthenticated() {
// Start ping
logger('onAuthenticated()');
this.startPing();
}
startPing() {
logger('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('stopping ping and reconnecting.');
clearInterval(this.pingRef);
this.unsubscribeCharacteristics();
this.state = STATE_DISCONNECTED;
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) {
logger('onDeviceConnected()');
const self = this;
if (err) {
console.log('error: failed to connect to device: ' + err + '. picking next.');
this.deviceIdx++;
this.reset();
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()');
if (!this.device) {
console.log('warning: reconnect will not be performed.');
return;
}
// we just want to reconnect
this.connect(this.device.id);
}
onDeviceScanComplete() {
logger('onDeviceScanComplete()');
console.log('trying to connect to the mesh network.');
this.connect();
}
onInterfaceStateChanged(state) {
logger('onInterfaceStateChanged(' + state + ')');
if (state === 'poweredOn') {
this.scan();
}
}
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.toString('hex', 3, 5) === '00c8' || decoded.toString('hex', 3, 5) === '0098') {
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('dimChanged', device, state, dim);
}
else if (decoded.toString('hex', 3, 5) === '0097') {
state = parseInt(decoded.toString('hex', 5, 6), 10);
logger('d: ' + device + ' got state update: ' + state);
this.emit('stateChanged', device, state);
}
}
wireEvents() {
logger('wireEvents()');
const self = this;
noble.on('stateChange', this.onInterfaceStateChanged.bind(self));
//noble.on('scanStop', this.onDeviceScanComplete.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) {
this.characteristics.lastData.unsubscribe((err) => {
if (err) {
console.log('error: could not unsubscribe from event.');
}
});
}
}
_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;

View file

@ -1,6 +1,6 @@
{ {
"name": "Plejd", "name": "Plejd",
"version": "0.0.7", "version": "0.1.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/",

View file

@ -2,6 +2,7 @@ const plejd = require('./plejd');
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');
async function main() { async function main() {
const rawData = fs.readFileSync('/data/plejd.json'); const rawData = fs.readFileSync('/data/plejd.json');
@ -22,43 +23,37 @@ async function main() {
client.init(); client.init();
// init the BLE interface // init the BLE interface
const controller = new plejd.Controller(cryptoKey, true); const plejd = new PlejdService(cryptoKey, true);
controller.on('scanComplete', async (peripherals) => { plejd.on('authenticated', () => {
await controller.connect();
});
controller.on('connected', () => {
console.log('plejd: connected via bluetooth.'); console.log('plejd: connected via bluetooth.');
}); });
// subscribe to changes from Plejd // subscribe to changes from Plejd
controller.on('stateChanged', (deviceId, state) => { plejd.on('stateChanged', (deviceId, state) => {
client.updateState(deviceId, state); client.updateState(deviceId, state);
}); });
controller.on('dimChanged', (deviceId, state, dim) => { plejd.on('dimChanged', (deviceId, state, dim) => {
client.updateState(deviceId, state); client.updateState(deviceId, state);
client.updateBrightness(deviceId, dim); client.updateBrightness(deviceId, dim);
}); });
// subscribe to changes from HA // subscribe to changes from HA
client.on('stateChanged', async (deviceId, state) => { client.on('stateChanged', (deviceId, state) => {
if (state) { if (state) {
await controller.turnOn(deviceId); plejd.turnOn(deviceId);
} }
else { else {
await controller.turnOff(deviceId); plejd.turnOff(deviceId);
} }
}); });
client.on('brightnessChanged', async (deviceId, brightness) => { client.on('brightnessChanged', (deviceId, brightness) => {
if (brightness > 0) { if (brightness > 0) {
await controller.turnOn(deviceId, brightness); plejd.turnOn(deviceId, brightness);
} }
else { else {
await controller.turnOff(deviceId); plejd.turnOff(deviceId);
} }
}); });
controller.init();
}); });
}); });

View file

@ -13,10 +13,10 @@
"usb": "^1.6.0" "usb": "^1.6.0"
} }
}, },
"@abandonware/noble": { "@icanos/noble": {
"version": "1.9.2-5", "version": "1.9.2-6",
"resolved": "https://registry.npmjs.org/@abandonware/noble/-/noble-1.9.2-5.tgz", "resolved": "https://registry.npmjs.org/@icanos/noble/-/noble-1.9.2-6.tgz",
"integrity": "sha512-Y1eyxDoA9kvKeAgd6mQ9c4qDbqQbqlPR56LkbtlAqptGB4HT/8KQweqqyTsj4CtdhbvCAt1G+J+2nE35WU9fBg==", "integrity": "sha512-+NxEW7nNEueqX8MknTdno3AZeFode56tzN+dMDW0TJjW96XG822DhoHmHeQZRylTd74r/8M5c4Sb9x/m45yiPw==",
"requires": { "requires": {
"@abandonware/bluetooth-hci-socket": "^0.5.3-3", "@abandonware/bluetooth-hci-socket": "^0.5.3-3",
"debug": "^4.1.1", "debug": "^4.1.1",
@ -24,6 +24,11 @@
"node-addon-api": "^1.1.0" "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": { "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",
@ -134,6 +139,11 @@
"readable-stream": "> 1.0.0 < 3.0.0" "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": { "chownr": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
@ -311,6 +321,11 @@
"ext": "^1.1.2" "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": { "event-emitter": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
@ -550,6 +565,14 @@
"resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz",
"integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=" "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": { "is-relative": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz",
@ -818,6 +841,11 @@
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" "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": { "once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -1090,6 +1118,11 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" "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": { "tar": {
"version": "4.4.13", "version": "4.4.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
@ -1142,6 +1175,20 @@
} }
} }
}, },
"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": { "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",
@ -1160,6 +1207,14 @@
"xtend": "~4.0.0" "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": { "to-absolute-glob": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz",
@ -1282,6 +1337,11 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" "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=="
} }
} }
} }

View file

@ -1,7 +1,7 @@
{ {
"dependencies": { "dependencies": {
"@abandonware/bluetooth-hci-socket": "0.5.3-3", "@abandonware/bluetooth-hci-socket": "0.5.3-3",
"@abandonware/noble": "^1.9.2-5", "@icanos/noble": "^1.9.2-6",
"axios": "^0.19.0", "axios": "^0.19.0",
"buffer-xor": "^2.0.2", "buffer-xor": "^2.0.2",
"fs": "0.0.1-security", "fs": "0.0.1-security",
@ -10,4 +10,4 @@
"mqtt": "^3.0.0", "mqtt": "^3.0.0",
"sleep": "^6.1.0" "sleep": "^6.1.0"
} }
} }

8
plejd/test/test.ble.js Normal file
View file

@ -0,0 +1,8 @@
const PlejdService = require('../ble');
const plejd = new PlejdService('todo-insert-crypto-key', true);
plejd.on('authenticated', () => {
plejd.disconnect();
console.log('ok, done! disconnected.');
});
plejd.scan();