changed layout to work as a repository in hassio

This commit is contained in:
Marcus Westin 2019-12-13 17:59:48 +01:00
parent acb3cc1ad3
commit 471ec7023e
12 changed files with 47 additions and 14 deletions

67
plejd/Dockerfile Normal file
View file

@ -0,0 +1,67 @@
ARG BUILD_FROM=hassioaddons/base:5.0.2
FROM $BUILD_FROM
ENV LANG C.UTF-8
# Set shell
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Copy data for add-on
COPY ./api.js /plejd/
COPY ./config.json /plejd/
COPY ./main.js /plejd/
COPY ./mqtt.js /plejd/
COPY ./package.json /plejd/
COPY ./plejd.js /plejd/
ARG BUILD_ARCH
# Install Node
RUN apk add --no-cache jq
RUN \
apk add --no-cache --virtual .build-dependencies \
g++=8.3.0-r0 \
gcc=8.3.0-r0 \
libc-dev=0.7.1-r0 \
linux-headers=4.19.36-r0 \
make=4.2.1-r2 \
python=2.7.16-r1 \
bluez=5.50-r3 \
eudev-dev=3.2.8-r0 \
\
&& apk add --no-cache \
git \
nodejs=10.16.3-r0 \
npm=10.16.3-r0 \
\
&& npm config set unsafe-perm true
WORKDIR /plejd
RUN npm install \
--no-audit \
--no-update-notifier \
--unsafe-perm
# Copy root filesystem
COPY rootfs /
# Build arguments
ARG BUILD_DATE
ARG BUILD_REF
ARG BUILD_VERSION
# Labels
LABEL \
io.hass.name="Plejd" \
io.hass.description="Adds support for the Swedish home automation devices from Plejd." \
io.hass.arch="${BUILD_ARCH}" \
io.hass.type="addon" \
io.hass.version=${BUILD_VERSION} \
maintainer="Marcus Westin <marcus@sekurbit.se>" \
org.label-schema.description="Adds support for the Swedish home automation devices from Plejd." \
org.label-schema.build-date=${BUILD_DATE} \
org.label-schema.name="Plejd" \
org.label-schema.schema-version="1.0" \
org.label-schema.usage="https://github.com/icanos/hassio-plejd/tree/master/README.md" \
org.label-schema.vcs-ref=${BUILD_REF} \
org.label-schema.vcs-url="https://github.com/icanos/hassio-plejd"

205
plejd/api.js Normal file
View file

@ -0,0 +1,205 @@
const axios = require('axios');
const EventEmitter = require('events');
const _ = require('lodash');
API_APP_ID = 'zHtVqXt8k4yFyk2QGmgp48D9xZr2G94xWYnF4dak';
API_BASE_URL = 'https://cloud.plejd.com/parse/';
API_LOGIN_URL = 'login';
API_SITES_URL = 'functions/getSites';
// #region logging
const debug = '';
const getLogger = () => {
const consoleLogger = msg => console.log('plejd-api', msg);
if (debug === 'console') {
return consoleLogger;
}
return _.noop;
};
const logger = getLogger();
// #endregion
class PlejdApi extends EventEmitter {
constructor(siteName, username, password, includeRoomsAsLights) {
super();
this.includeRoomsAsLights = includeRoomsAsLights;
this.siteName = siteName;
this.username = username;
this.password = password;
this.sessionToken = '';
this.site = null;
}
login() {
const self = this;
const instance = axios.create({
baseURL: API_BASE_URL,
headers: {
'X-Parse-Application-Id': API_APP_ID,
'Content-Type': 'application/json'
}
});
instance.post(
API_LOGIN_URL,
{
'username': this.username,
'password': this.password
})
.then((response) => {
self.sessionToken = response.data.sessionToken;
self.emit('loggedIn');
});
}
getCryptoKey(callback) {
const self = this;
const instance = axios.create({
baseURL: API_BASE_URL,
headers: {
'X-Parse-Application-Id': API_APP_ID,
'X-Parse-Session-Token': this.sessionToken,
'Content-Type': 'application/json'
}
});
instance.post(API_SITES_URL)
.then((response) => {
self.site = response.data.result.find(x => x.site.title == self.siteName);
self.cryptoKey = self.site.plejdMesh.cryptoKey;
callback(self.cryptoKey);
})
.catch((error) => {
logger('unable to retrieve the crypto key. error: ' + error);
return Promise.reject('unable to retrieve the crypto key. error: ' + error);
});
}
getDevices() {
let devices = [];
// Just log the devices if debug logging enabled
if (debug) {
logger(JSON.stringify(this.site));
}
const roomDevices = {};
for (let i = 0; i < this.site.devices.length; i++) {
const device = this.site.devices[i];
const deviceId = device.deviceId;
const settings = this.site.outputSettings.find(x => x.deviceParseId == device.objectId);
let deviceNum = this.site.deviceAddress[deviceId];
if (settings) {
const outputs = this.site.outputAddress[deviceId];
deviceNum = outputs[settings.output];
}
// check if device is dimmable
const plejdDevice = this.site.plejdDevices.find(x => x.deviceId == deviceId);
let { name, type, dimmable } = this._getDeviceType(plejdDevice.hardwareId);
if (settings) {
dimmable = settings.dimCurve != 'NonDimmable';
}
const newDevice = {
id: deviceNum,
name: device.title,
type: type,
typeName: name,
dimmable: dimmable
};
logger(JSON.stringify(newDevice));
if (roomDevices[device.roomId]) {
roomDevices[device.roomId].push(newDevice);
}
else {
roomDevices[device.roomId] = [newDevice];
}
devices.push(newDevice);
}
if (this.includeRoomsAsLights) {
logger('includeRoomsAsLights is set to true, adding rooms too.');
for (let i = 0; i < this.site.rooms.length; i++) {
const room = this.site.rooms[i];
const roomId = room.roomId;
const roomAddress = this.site.roomAddress[roomId];
const newDevice = {
id: roomAddress,
name: room.title,
type: 'light',
typeName: 'Room',
dimmable: roomDevices[roomId].find(x => x.dimmable).length > 0
};
logger(JSON.stringify(newDevice));
devices.push(newDevice);
}
}
return devices;
}
_getDeviceType(hardwareId) {
switch (parseInt(hardwareId)) {
case 1:
case 11:
return { name: "DIM-01", type: 'light', dimmable: true };
case 2:
return { name: "DIM-02", type: 'light', dimmable: true };
case 3:
return { name: "CTR-01", type: 'light', dimmable: false };
case 4:
return { name: "GWY-01", type: 'sensor', dimmable: false };
case 5:
return { name: "LED-10", type: 'light', dimmable: true };
case 6:
return { name: "WPH-01", type: 'switch', dimmable: false };
case 7:
return { name: "REL-01", type: 'switch', dimmable: false };
case 8:
case 9:
// Unknown
return { name: "-unknown-", type: 'light', dimmable: false };
case 10:
return { name: "-unknown-", type: 'light', dimmable: false };
case 12:
// Unknown
return { name: "-unknown-", type: 'light', dimmable: false };
case 13:
return { name: "Generic", type: 'light', dimmable: false };
case 14:
case 15:
case 16:
// Unknown
return { name: "-unknown-", type: 'light', dimmable: false };
case 17:
return { name: "REL-01", type: 'switch', dimmable: false };
case 18:
return { name: "REL-02", type: 'switch', dimmable: false };
case 19:
// Unknown
return { name: "-unknown-", type: 'light', dimmable: false };
case 20:
return { name: "SPR-01", type: 'switch', dimmable: false };
}
}
}
module.exports = { PlejdApi };

11
plejd/build.json Normal file
View file

@ -0,0 +1,11 @@
{
"squash": false,
"build_from": {
"aarch64": "hassioaddons/base-aarch64:5.0.2",
"amd64": "hassioaddons/base-amd64:5.0.2",
"armhf": "hassioaddons/base-armhf:5.0.2",
"armv7": "hassioaddons/base-armv7:5.0.2",
"i386": "hassioaddons/base-i386:5.0.2"
},
"args": {}
}

35
plejd/config.json Normal file
View file

@ -0,0 +1,35 @@
{
"name": "Plejd",
"version": "4",
"slug": "plejd",
"description": "Adds support for the Swedish home automation devices from Plejd.",
"url": "https://github.com/icanos/hassio-plejd/",
"arch": [
"armhf",
"armv7",
"aarch64",
"amd64",
"i386"
],
"startup": "application",
"boot": "auto",
"host_network": true,
"options": {
"site": "Default Site",
"username": "",
"password": "",
"mqttBroker": "mqtt://",
"mqttUsername": "",
"mqttPassword": "",
"includeRoomsAsLights": false
},
"schema": {
"site": "str",
"username": "str",
"password": "str",
"mqttBroker": "str",
"mqttUsername": "str",
"mqttPassword": "str",
"includeRoomsAsLights": "bool"
}
}

68
plejd/main.js Normal file
View file

@ -0,0 +1,68 @@
const plejd = require('./plejd');
const api = require('./api');
const mqtt = require('./mqtt');
const fs = require('fs');
async function main() {
const rawData = fs.readFileSync('/data/plejd.json');
const config = JSON.parse(rawData);
const plejdApi = new api.PlejdApi(config.site, config.username, config.password);
const client = new mqtt.MqttClient(config.mqttBroker, config.mqttUsername, config.mqttPassword);
plejdApi.once('loggedIn', () => {
plejdApi.getCryptoKey((cryptoKey) => {
const devices = plejdApi.getDevices();
client.on('connected', () => {
console.log('plejd-mqtt: connected to mqtt.');
client.discover(devices);
});
client.init();
// init the BLE interface
const controller = new plejd.Controller(cryptoKey, true);
controller.on('scanComplete', async (peripherals) => {
await controller.connect();
});
controller.on('connected', () => {
console.log('plejd: connected via bluetooth.');
});
// subscribe to changes from Plejd
controller.on('stateChanged', (deviceId, state) => {
client.updateState(deviceId, state);
});
controller.on('dimChanged', (deviceId, state, dim) => {
client.updateState(deviceId, state);
client.updateBrightness(deviceId, dim);
});
// subscribe to changes from HA
client.on('stateChanged', async (deviceId, state) => {
if (state) {
await controller.turnOn(deviceId);
}
else {
await controller.turnOff(deviceId);
}
});
client.on('brightnessChanged', async (deviceId, brightness) => {
if (brightness > 0) {
await controller.turnOn(deviceId, brightness);
}
else {
await controller.turnOff(deviceId);
}
});
controller.init();
});
});
plejdApi.login();
}
main();

190
plejd/mqtt.js Normal file
View file

@ -0,0 +1,190 @@
const EventEmitter = require('events');
const mqtt = require('mqtt');
const _ = require('lodash');
const startTopic = 'hass/status';
// #region logging
const debug = '';
const getLogger = () => {
const consoleLogger = msg => console.log('plejd-mqtt', msg);
if (debug === 'console') {
return consoleLogger;
}
return _.noop;
};
const logger = getLogger();
// #endregion
// #region discovery
const discoveryPrefix = 'homeassistant';
const nodeId = 'plejd';
const getSubscribePath = () => `${discoveryPrefix}/+/${nodeId}/#`;
const getPath = ({ id, type }) =>
`${discoveryPrefix}/${type}/${nodeId}/${id}`;
const getConfigPath = plug => `${getPath(plug)}/config`;
const getAvailabilityTopic = plug => `${getPath(plug)}/availability`;
const getStateTopic = plug => `${getPath(plug)}/state`;
const getBrightnessCommandTopic = plug => `${getPath(plug)}/setBrightness`;
const getBrightnessTopic = plug => `${getPath(plug)}/brightness`;
const getCommandTopic = plug => `${getPath(plug)}/set`;
const getDiscoveryDimmablePayload = device => ({
name: device.name,
unique_id: `light.plejd.${device.name.toLowerCase().replace(/ /g, '')}`,
state_topic: getStateTopic(device),
command_topic: getCommandTopic(device),
brightness_command_topic: getBrightnessCommandTopic(device),
brightness_state_topic: getBrightnessTopic(device),
payload_on: 1,
payload_off: 0,
optimistic: false
});
const getDiscoveryPayload = device => ({
name: device.name,
unique_id: `light.plejd.${device.name.toLowerCase().replace(/ /g, '')}`,
state_topic: getStateTopic(device),
command_topic: getCommandTopic(device),
payload_on: 1,
payload_off: 0,
optimistic: false
});
// #endregion
class MqttClient extends EventEmitter {
constructor(mqttBroker, username, password) {
super();
this.mqttBroker = mqttBroker;
this.username = username;
this.password = password;
this.deviceMap = {};
this.devices = [];
}
init() {
const self = this;
this.client = mqtt.connect(this.mqttBroker, {
username: this.username,
password: this.password
});
this.client.on('connect', () => {
logger('connected to MQTT.');
this.client.subscribe(startTopic, (err) => {
if (err) {
logger('error: unable to subscribe to ' + startTopic);
}
self.emit('connected');
});
this.client.subscribe(getSubscribePath(), (err) => {
if (err) {
logger('error: unable to subscribe to control topics');
}
});
});
this.client.on('close', () => {
self.reconnect();
});
this.client.on('message', (topic, message) => {
const command = message.toString();
if (topic === startTopic) {
logger('home assistant has started. lets do discovery.');
self.emit('connected');
}
if (_.includes(topic, 'setBrightness')) {
const device = self.devices.find(x => getBrightnessCommandTopic(x) === topic);
logger('got brightness update for ' + device.name + ' with brightness: ' + command);
self.emit('brightnessChanged', device.id, parseInt(command));
}
else if (_.includes(topic, 'set') && _.includes(['0', '1'], command)) {
const device = self.devices.find(x => getCommandTopic(x) === topic);
logger('got state update for ' + device.name + ' with state: ' + command);
self.emit('stateChanged', device.id, parseInt(command));
}
});
}
reconnect() {
this.client.reconnect();
}
discover(devices) {
this.devices = devices;
const self = this;
logger('sending discovery of ' + devices.length + ' device(s).');
devices.forEach((device) => {
logger(`sending discovery for ${device.name}`);
let payload = null;
if (device.type === 'switch') {
payload = getDiscoveryPayload(device);
}
else {
payload = device.dimmable ? getDiscoveryDimmablePayload(device) : getDiscoveryPayload(device);
}
console.log(`discovered ${device.name} with Plejd ID ${device.id}.`);
self.deviceMap[device.id] = payload.unique_id;
self.client.publish(
getConfigPath(device),
JSON.stringify(payload)
);
});
}
updateState(deviceId, state) {
const device = this.devices.find(x => x.id === deviceId);
if (!device) {
logger('error: ' + deviceId + ' is not handled by us.');
return;
}
logger('updating state for ' + device.name + ': ' + state);
this.client.publish(
getStateTopic(device),
state.toString()
);
}
updateBrightness(deviceId, brightness) {
const device = this.devices.find(x => x.id === deviceId);
if (!device) {
logger('error: ' + deviceId + ' is not handled by us.');
return;
}
logger('updating brightness for ' + device.name + ': ' + brightness);
this.client.publish(
getBrightnessTopic(device),
brightness.toString()
);
}
}
module.exports = { MqttClient };

12
plejd/package.json Normal file
View file

@ -0,0 +1,12 @@
{
"dependencies": {
"@abandonware/noble": "^1.9.2-5",
"@abandonware/bluetooth-hci-socket": "0.5.3-3",
"axios": "^0.19.0",
"buffer-xor": "^2.0.2",
"fs": "0.0.1-security",
"jspack": "0.0.4",
"lodash": "^4.17.15",
"mqtt": "^3.0.0"
}
}

460
plejd/plejd.js Normal file
View file

@ -0,0 +1,460 @@
const noble = require('@abandonware/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"
class Controller extends EventEmitter {
constructor(cryptoKey, keepAlive = false) {
super();
this.cryptoKey = Buffer.from(cryptoKey.replace(/-/g, ''), 'hex');
this.peripheral = null;
this.peripheral_address = null;
this.isScanning = false;
this.isConnecting = false;
this.isConnected = false;
this.keepAlive = keepAlive;
this.writeQueue = [];
this.peripherals = [];
// Holds a reference to the connected peripheral from the peripheral list.
// In case the peripheral we're connecting to, disconnects us, we can then reinitiate the connection
// by increasing the connectedIndex and by that, connect to the next in line.
this.connectedIndex = 0;
}
async init() {
const self = this;
noble.on('stateChange', async (state) => {
logger('ble state changed: ' + state);
if (state === 'poweredOn') {
await this.scan();
}
});
noble.on('discover', (peripheral) => {
logger('found ' + peripheral.advertisement.localName + ' with addr ' + peripheral.address);
if (peripheral.advertisement.localName === 'P mesh') {
self.peripherals.push(peripheral);
}
});
}
async scan() {
const self = this;
this.isScanning = true;
noble.startScanning([PLEJD_SERVICE]);
setTimeout(() => {
noble.stopScanning();
this.isScanning = false;
self.peripherals.sort((a, b) => a.rssi > b.rssi);
this.emit('scanComplete', self.peripherals);
}, 5000);
}
async connect() {
const self = this;
if (this.isScanning) {
logger('already scanning, waiting.');
setTimeout(this.connect(), 1000);
return Promise.resolve(false);
}
if (!this.peripherals.length) {
await this.scan();
}
this.isConnecting = true;
return await this._internalConnect(this.connectedIndex);
}
async _internalConnect(idx) {
const self = this;
if (idx >= this.peripherals.length) {
logger('reached end of list.');
return Promise.resolve(false);
}
logger('connecting to Plejd device');
try {
this.peripherals[idx].connect(async (err) => {
if (err) {
console.log('error: failed to connect to Plejd device: ' + err);
return await self._internalConnect(idx + 1);
}
self.peripheral = self.peripherals[idx];
console.log('connected to Plejd device with addr ' + self.peripheral.address);
self.peripheral_address = self._reverseBuffer(Buffer.from(String(self.peripheral.address).replace(/\-/g, '').replace(/\:/g, ''), 'hex'));
logger('discovering services and characteristics');
await self.peripheral.discoverSomeServicesAndCharacteristics([PLEJD_SERVICE], [], async (err, services, characteristics) => {
if (err) {
console.log('error: failed to discover services: ' + err);
return;
}
characteristics.forEach((ch) => {
if (DATA_UUID == ch.uuid) {
logger('found DATA characteristic.');
self.dataCharacteristic = ch;
}
else if (LAST_DATA_UUID == ch.uuid) {
logger('found LAST_DATA characteristic.');
self.lastDataCharacteristic = ch;
}
else if (AUTH_UUID == ch.uuid) {
logger('found AUTH characteristic.');
self.authCharacteristic = ch;
}
else if (PING_UUID == ch.uuid) {
logger('found PING characteristic.');
self.pingCharacteristic = ch;
}
});
if (this.dataCharacteristic
&& this.lastDataCharacteristic
&& this.authCharacteristic
&& this.pingCharacteristic) {
this.on('authenticated', () => {
logger('Plejd is connected and authenticated.');
this.connectedIndex = idx;
if (self.keepAlive) {
self.startPing();
}
self.subscribe();
self.emit('connected');
});
try {
await this.authenticate();
}
catch (error) {
this.isConnecting = false;
console.log('error: failed to authenticate: ' + error);
return Promise.resolve(false);
}
this.isConnected = true;
this.isConnecting = false;
}
return Promise.resolve(true);
//});
});
});
}
catch (error) {
this.isConnecting = false;
console.log('error: failed to connect to Plejd device: ' + error);
}
return Promise.resolve(true);
}
subscribe() {
const self = this;
self.lastDataCharacteristic.subscribe((err) => {
if (err) {
console.log('error: couldnt subscribe to notification characteristic.');
return;
}
// subscribe to last data event
self.lastDataCharacteristic.on('data', (data, isNotification) => {
const decoded = self._encryptDecrypt(self.cryptoKey, self.peripheral_address, 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);
}
});
});
}
async disconnect() {
logger('disconnecting from Plejd');
if (this.isConnected) {
clearInterval(this.pingRef);
if (this.peripheral) {
try {
await this.peripheral.disconnect();
}
catch (error) {
console.log('error: unable to disconnect from Plejd: ' + error);
return Promise.resolve(false);
}
this.isConnected = false;
logger('disconnected from Plejd');
return Promise.resolve(true);
}
}
else {
clearInterval(this.pingRef);
this.isConnected = false;
logger('disconnected from Plejd');
return Promise.resolve(true);
}
}
async turnOn(id, brightness) {
if (!this.isConnected) {
console.log('warning: not connected, will connect. might take a few seconds.');
await this.connect();
}
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);
}
async turnOff(id) {
if (!this.isConnected) {
console.log('warning: not connected, will connect. might take a few seconds.');
await this.connect();
}
logger('turning off ' + id);
var payload = Buffer.from((id).toString(16).padStart(2, '0') + '0110009700', 'hex');
this.write(payload);
}
startPing() {
const self = this;
clearInterval(this.pingRef);
logger('starting ping');
this.pingRef = setInterval(async () => {
logger('ping');
if (self.isConnected) {
await self.plejdPing(async (pingOk) => {
if (!pingOk) {
logger('ping failed');
await self.disconnect();
await self.connect();
}
else {
logger('pong');
}
});
}
else {
await self.disconnect();
await self.connect();
}
}, 3000);
}
async plejdPing(callback) {
var ping = crypto.randomBytes(1);
try {
// make sure we're connected, otherwise, return false and reconnect
if (this.peripheral.state !== 'connected') {
callback(false);
return;
}
this.pingCharacteristic.write(ping, false, (err) => {
if (err) {
console.log('error: unable to send ping: ' + err);
callback(false);
}
this.pingCharacteristic.read((err, data) => {
if (err) {
console.log('error: unable to read ping: ' + err);
callback(false);
}
if (((ping[0] + 1) & 0xff) !== data[0]) {
callback(false);
}
else {
callback(true);
}
});
});
}
catch (error) {
console.log('error: writing to plejd: ' + error);
await self.disconnect();
await self.connect();
}
}
async authenticate() {
const self = this;
logger('authenticating connection');
this.authCharacteristic.write(Buffer.from([0]), false, (err) => {
if (err) {
console.log('error: failed to authenticate: ' + err);
return;
}
this.authCharacteristic.read(async (err2, data) => {
if (err2) {
console.log('error: challenge request failed: ' + err2);
return;
}
var resp = self._challengeResponse(self.cryptoKey, data);
this.authCharacteristic.write(resp, false, (err3) => {
if (err3) {
console.log('error: challenge failed: ' + err2);
return;
}
this.emit('authenticated');
});
});
});
}
async write(data) {
const self = this;
try {
if (this.isConnecting) {
logger('adding message to queue.');
this.writeQueue.push(data);
return Promise.resolve(true);
}
if (!this.keepAlive) {
logger('not connected to Plejd. reconnecting.');
await this.connect();
}
this.dataCharacteristic.write(this._encryptDecrypt(this.cryptoKey, this.peripheral_address, data), false);
let writeData;
while ((writeData = this.writeQueue.shift()) !== undefined) {
this.dataCharacteristic.write(this._encryptDecrypt(this.cryptoKey, this.peripheral_address, writeData), false);
}
if (!this.keepAlive) {
clearTimeout(this.disconnectIntervalRef);
this.disconnectIntervalRef = setTimeout(async () => {
await self.disconnect();
}, 5000);
}
}
catch (error) {
console.log('error: writing to plejd: ' + error);
await self.disconnect();
await self.connect();
}
}
_challengeResponse(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 = { Controller };

View file

@ -0,0 +1,8 @@
#!/usr/bin/execlineb -S0
# ==============================================================================
# Community Hass.io Add-ons: Plejd
# ==============================================================================
if -n { s6-test $# -ne 0 }
if -n { s6-test ${1} -eq 256 }
s6-svscanctl -t /var/run/s6/services

View file

@ -0,0 +1,14 @@
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Community Hass.io Add-ons: Plejd
# Runs the Plejd service
# ==============================================================================
bashio::log.info 'Starting the Plejd service...'
# Change working directory
cd /plejd || bashio::exit.nok 'Unable to change working directory'
# Run the Plejd service
chmod +x /usr/bin/plejd.sh
exec /usr/bin/plejd.sh

View file

@ -0,0 +1,27 @@
#!/usr/bin/with-contenv bashio
CONFIG_PATH=/data/options.json
SITE=$(jq --raw-output ".site" $CONFIG_PATH)
USERNAME=$(jq --raw-output ".username" $CONFIG_PATH)
PASSWORD=$(jq --raw-output ".password" $CONFIG_PATH)
MQTTBROKER=$(jq --raw-output ".mqttBroker" $CONFIG_PATH)
MQTTUSERNAME=$(jq --raw-output ".mqttUsername" $CONFIG_PATH)
MQTTPASSWORD=$(jq --raw-output ".mqttPassword" $CONFIG_PATH)
INCLUDEROOMSASLIGHTS=$(jq --raw-output ".includeRoomsAsLights" $CONFIG_PATH)
PLEJD_PATH=/data/plejd.json
PLEJD_CONFIG="{
\"site\": \"$SITE\",
\"username\": \"$USERNAME\",
\"password\": \"$PASSWORD\",
\"mqttBroker\": \"$MQTTBROKER\",
\"mqttUsername\": \"$MQTTUSERNAME\",
\"mqttPassword\": \"$MQTTPASSWORD\",
\"includeRoomsAsLights\": \"$INCLUDEROOMSASLIGHTS\"
}
"
echo "$PLEJD_CONFIG" > $PLEJD_PATH
exec node /plejd/main.js