impl turnon/off and notifications

This commit is contained in:
icanos 2019-12-19 09:41:16 +00:00
parent 5d90bceb99
commit c21d7e1243
3 changed files with 167 additions and 3 deletions

View file

@ -3,7 +3,6 @@ const crypto = require('crypto');
const xor = require('buffer-xor'); const xor = require('buffer-xor');
const _ = require('lodash'); const _ = require('lodash');
const EventEmitter = require('events'); const EventEmitter = require('events');
const sleep = require('sleep');
let debug = 'console'; let debug = 'console';
@ -47,8 +46,11 @@ class PlejdService extends EventEmitter {
this.devices = {}; this.devices = {};
// Keeps track of the currently connected device // Keeps track of the currently connected device
this.device = null; this.device = null;
this.deviceAddress = null;
this.deviceIdx = 0; this.deviceIdx = 0;
this.writeQueue = [];
// Holds a reference to all characteristics // Holds a reference to all characteristics
this.characteristicState = STATE_UNINITIALIZED; this.characteristicState = STATE_UNINITIALIZED;
this.characteristics = { this.characteristics = {
@ -62,6 +64,27 @@ class PlejdService extends EventEmitter {
this.wireEvents(); 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() { scan() {
logger('scan()'); logger('scan()');
@ -112,6 +135,14 @@ class PlejdService extends EventEmitter {
return; 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); logger('connecting to ' + this.device.id + ' with addr ' + this.device.address + ' and rssi ' + this.device.rssi);
setTimeout(() => { setTimeout(() => {
if (self.state !== STATE_CONNECTED && self.state !== STATE_AUTHENTICATED) { if (self.state !== STATE_CONNECTED && self.state !== STATE_AUTHENTICATED) {
@ -141,7 +172,12 @@ class PlejdService extends EventEmitter {
return; return;
} }
clearInterval(this.pingRef);
this.unsubscribeCharacteristics();
this.device.disconnect(); this.device.disconnect();
this.state = STATE_DISCONNECTED;
} }
authenticate() { authenticate() {
@ -179,6 +215,22 @@ class PlejdService extends EventEmitter {
}); });
} }
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() { onAuthenticated() {
// Start ping // Start ping
logger('onAuthenticated()'); logger('onAuthenticated()');
@ -194,6 +246,9 @@ class PlejdService extends EventEmitter {
logger('ping'); logger('ping');
this.ping(); this.ping();
} }
else if (this.state === STATE_DISCONNECTED) {
console.log('warning: device disconnected, stop ping.');
}
else { else {
console.log('error: ping failed, not connected.'); console.log('error: ping failed, not connected.');
} }
@ -210,7 +265,9 @@ class PlejdService extends EventEmitter {
logger('stopping ping and reconnecting.'); logger('stopping ping and reconnecting.');
clearInterval(this.pingRef); clearInterval(this.pingRef);
this.unsubscribeCharacteristics();
this.state = STATE_DISCONNECTED; this.state = STATE_DISCONNECTED;
this.connect(this.device.id); this.connect(this.device.id);
} }
@ -322,6 +379,10 @@ class PlejdService extends EventEmitter {
&& self.characteristics.ping) { && self.characteristics.ping) {
self.characteristicState = STATE_INITIALIZED; self.characteristicState = STATE_INITIALIZED;
// subscribe to notifications
this.subscribeCharacteristics();
self.emit('deviceCharacteristicsComplete', self.device); self.emit('deviceCharacteristicsComplete', self.device);
} }
}); });
@ -367,6 +428,28 @@ class PlejdService extends EventEmitter {
} }
} }
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() { wireEvents() {
logger('wireEvents()'); logger('wireEvents()');
const self = this; const self = this;
@ -383,6 +466,27 @@ class PlejdService extends EventEmitter {
this.on('pingSuccess', this.onPingSuccess.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) { _createChallengeResponse(key, challenge) {
const intermediate = crypto.createHash('sha256').update(xor(key, challenge)).digest(); const intermediate = crypto.createHash('sha256').update(xor(key, challenge)).digest();
const part1 = intermediate.subarray(0, 16); const part1 = intermediate.subarray(0, 16);

View file

@ -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=="
} }
} }
} }