signalk-sonoff-ewelink
Version:
Signal K Plugin For Sonoff/eWeLink devices
1,097 lines (1,096 loc) • 49.7 kB
JavaScript
;
/*
* Copyright 2021 Scott Bender <scott@scottbender.net>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
var camelCase = require('camelcase');
var ewelink = require('ewelink-api-sbender9');
var Zeroconf = require('ewelink-api-sbender9/src/classes/Zeroconf');
var decryptionData = require('ewelink-api-sbender9/src/helpers/ewelink').decryptionData;
var path = require('path');
var dnssd = require('dnssd2');
var fs = require('fs');
var mdns;
try {
mdns = require('mdns');
}
catch (err) { }
//the registered one
var APP_ID = '6dgd7bXbf6pfrqEftpq3JkYeRn9yBQ6y';
var APP_SECRET = 'ouCUhN7oBkkohUrmFy6VlT6c8iP5CYd4';
//const APP_ID = '4s1FXKC9FaGfoqXhmXSJneb3qcm1gOak'
//const APP_SECRET = 'oKvCM06gvwkRbfetd6qWRrbC3rFrbIpV'
//const APP_ID = 'KOBxGJna5qkk3JLXw3LHLX3wSNiPjAVi'
//const APP_SECRET = '4v0sv6X5IM2ASIBiNDj6kGmSfxo40w7n'
//const APP_ID = 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq'
//const APP_SECRET = '6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM'
//const APP_ID_WS = 'YzfeftUVcZ6twZw1OoVKPRFYTrGEg01Q'
//const APP_SECRET_WS = '4G91qSoboqYO4Y0XJ0LPPKIsq8reHdfa'
var APP_ID_WS = APP_ID;
var APP_SECRET_WS = APP_SECRET;
var pingTime = 25000;
//const pingTime = 120000
//const pingTime = 10000
function default_1(app) {
var error = app.error;
var debug = app.debug;
var sentMetaDevices = {};
var lanConnection;
var cloudConnection;
var anyCloudOnlyDevices;
var cloudOnlyByDevice = {};
var socket;
var pollInterval;
var putsRegistred = {};
var devicesCache;
var arpTable = [];
var arpTablePath;
var props;
var browser;
var wsTimer;
var wsPingInterval;
var plugin = {
start: function (properties) {
props = properties;
if (!props.userName ||
props.userName.length === 0 ||
!props.password ||
props.password.length === 0) {
app.setPluginError('please configure username and password');
return;
}
openConnection();
},
stop: function () {
if (socket) {
socket.close();
socket = undefined;
}
if (browser) {
browser.stop();
browser = undefined;
}
if (wsTimer) {
clearTimeout(wsTimer);
wsTimer = undefined;
}
if (wsPingInterval) {
clearInterval(wsPingInterval);
wsPingInterval = undefined;
}
putsRegistred = {};
sentMetaDevices = {};
devicesCache = undefined;
cloudOnlyByDevice = {};
},
id: 'signalk-sonoff-ewelink',
name: 'Sonoff/eWeLink',
description: 'Signal K Plugin For Sonoff/eWeLink devices',
schema: function () {
var schema = {
type: 'object',
properties: {
userName: {
type: 'string',
title: 'User Name',
description: 'eWeLink User Name'
},
password: {
type: 'string',
title: 'Password',
description: 'eWeLink Password'
},
region: {
type: 'string',
title: 'Region',
description: 'eWeLink Region (us, eu or cn)',
default: 'us'
},
lanMode: {
type: 'boolean',
title: 'Use LAN Mode',
default: true
},
useMdns: {
type: 'boolean',
title: 'Use MDNS Package If available',
default: false
}
}
};
if (devicesCache) {
devicesCache.forEach(function (device) {
if (device.params.switches) {
var devSchema_1 = {
type: 'object',
properties: {
deviceName: {
type: 'string',
title: 'Name',
default: device.name,
readOnly: true
},
enabled: {
type: 'boolean',
title: 'Enabled',
default: true
},
bankPath: {
type: 'string',
title: 'Bank Path',
default: camelCase(device.name),
description: 'Used to generate the path name, ie. electrical.switches.${bankPath}.0.state'
},
forceCloudMode: {
type: 'boolean',
title: 'Force Cloud Mode',
default: false,
description: 'Use the cloud for this device even if LAN mode is on'
}
}
};
schema.properties["Device ID ".concat(device.deviceid)] = devSchema_1;
device.params.switches.forEach(function (sw) {
var _a;
var name = ((_a = device.tags) === null || _a === void 0 ? void 0 : _a.ck_channel_name)
? device.tags.ck_channel_name[sw.outlet.toString()]
: sw.outlet.toString();
devSchema_1.properties["Channel ".concat(sw.outlet)] = {
type: 'object',
title: "Channel ".concat(sw.outlet + 1),
properties: {
displayName: {
type: 'string',
title: 'Display Name (meta)',
default: name
},
abbrev: {
type: 'string',
title: 'Abbreviated Name (meta)'
},
enabled: {
type: 'boolean',
title: 'Enabled',
default: true
},
switchPath: {
type: 'string',
title: 'Switch Path',
default: camelCase(name),
description: 'Used to generate the path name, ie. electrical.switches.bank.${switchPath}.state'
}
}
};
});
}
else {
schema.properties["Device ID ".concat(device.deviceid)] = {
type: 'object',
properties: {
deviceName: {
type: 'string',
title: 'Name',
default: device.name,
readOnly: true
},
enabled: {
type: 'boolean',
title: 'Enabled',
default: true
},
displayName: {
type: 'string',
title: 'Display Name (meta)',
default: device.name
},
abbrev: {
type: 'string',
title: 'Abbreviated Name (meta)'
},
switchPath: {
type: 'string',
title: 'Switch Path',
default: camelCase(device.name),
description: 'Used to generate the path name, ie electrical.switches.${switchPath}.state'
},
forceCloudMode: {
type: 'boolean',
title: 'Force Cloud Mode',
default: false,
description: 'Use the cloud for this device even if LAN mode is on'
}
}
};
if (device.uiid == l1Light) {
schema.properties["Device ID ".concat(device.deviceid)].properties.includeDYIModes = {
title: 'Include DYI Modes in Presets',
type: 'boolean',
default: false
};
schema.properties["Device ID ".concat(device.deviceid)].properties.presets = {
title: 'Presets',
type: 'array',
items: {
type: 'object',
properties: {
name: {
type: 'string',
title: 'Name'
},
colorR: {
type: 'number',
title: 'Red',
default: 255
},
colorG: {
type: 'number',
title: 'Green',
default: 255
},
colorB: {
type: 'number',
title: 'Blue',
default: 255
},
bright: {
type: 'number',
title: 'Brightness',
description: 'Number between 1-100. Set to 0 to preserve current brightness',
default: 100
}
}
}
};
}
}
});
}
return schema;
},
uiSchema: function () {
var uiSchema = {
password: {
'ui:widget': 'password'
}
};
if (devicesCache) {
devicesCache.forEach(function (device) {
uiSchema["Device ID ".concat(device.deviceid)] = {
'ui:field': 'collapsible',
collapse: {
field: 'ObjectField',
wrapClassName: 'panel-group'
}
};
});
}
return uiSchema;
}
};
function startLanMode() {
return __awaiter(this, void 0, void 0, function () {
var deviceCachePath, err_1, err_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 7, , 8]);
deviceCachePath = path.join(app.getDataDirPath(), 'devices-cache.json');
arpTablePath = path.join(app.getDataDirPath(), 'arp-table.json');
debug('saving device cache...');
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, cloudConnection.saveDevicesCache(deviceCachePath)];
case 2:
_a.sent();
return [3 /*break*/, 4];
case 3:
err_1 = _a.sent();
error(err_1);
return [3 /*break*/, 4];
case 4:
if (!!devicesCache) return [3 /*break*/, 6];
return [4 /*yield*/, Zeroconf.loadCachedDevices(deviceCachePath)];
case 5:
devicesCache = _a.sent();
if (devicesCache.error) {
//error(devicesCache.error)
devicesCache = undefined;
return [2 /*return*/];
}
_a.label = 6;
case 6:
readArpTabel();
lanConnection = new ewelink({
devicesCache: devicesCache,
arpTable: arpTable,
APP_ID: APP_ID,
APP_SECRET: APP_SECRET
});
getDevices(devicesCache, false);
debug('starting dnsd browser...');
if (mdns && props.useMdns) {
browser = mdns.createBrowser(mdns.tcp('ewelink'));
}
else {
browser = dnssd.Browser(dnssd.tcp('ewelink'));
}
browser
.on('serviceUp', dnsdChanged)
.on('serviceChanged', dnsdChanged)
.start();
return [3 /*break*/, 8];
case 7:
err_2 = _a.sent();
error(err_2);
app.setPluginError(err_2.message);
return [3 /*break*/, 8];
case 8: return [2 /*return*/];
}
});
});
}
function openConnection() {
return __awaiter(this, void 0, void 0, function () {
var res, devices, err_3;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
cloudConnection = new ewelink({
email: props.userName,
password: props.password,
region: props.region,
APP_ID: APP_ID,
APP_SECRET: APP_SECRET
});
_a.label = 1;
case 1:
_a.trys.push([1, 6, , 7]);
return [4 /*yield*/, cloudConnection.getCredentials()];
case 2:
res = _a.sent();
if (!res.error) return [3 /*break*/, 3];
error('error logging in: ' + JSON.stringify(res));
app.setPluginError(res.msg);
return [3 /*break*/, 5];
case 3: return [4 /*yield*/, cloudConnection.getDevices()];
case 4:
devices = _a.sent();
debug('found devices: %j', devices);
devicesCache = devices;
getDevices(devices, true);
_a.label = 5;
case 5: return [3 /*break*/, 7];
case 6:
err_3 = _a.sent();
error(err_3);
app.setPluginError(err_3.message);
return [3 /*break*/, 7];
case 7:
if (!props.lanMode) return [3 /*break*/, 9];
return [4 /*yield*/, startLanMode()];
case 8:
_a.sent();
_a.label = 9;
case 9:
if (devicesCache) {
devicesCache.forEach(function (device) {
var switchProps = getDeviceProps(device);
var cloudOnly = (!switchProps ||
typeof switchProps.enabled === 'undefined' ||
switchProps.enabled) &&
(cloudOnlyHardware.indexOf(device.uiid) !== -1 ||
(switchProps === null || switchProps === void 0 ? void 0 : switchProps.forceCloudMode) === true);
cloudOnlyByDevice[device.deviceid] = cloudOnly;
});
anyCloudOnlyDevices = devicesCache.find(function (device) { return cloudOnlyByDevice[device.deviceid] === true; });
if (props.lanMode && !anyCloudOnlyDevices) {
app.setPluginStatus('Using LAN mode only');
}
if (!props.lanMode || anyCloudOnlyDevices) {
try {
openWebSocket();
}
catch (err) {
error(err);
}
}
}
return [2 /*return*/];
}
});
});
}
function openWebSocket() {
return __awaiter(this, void 0, void 0, function () {
var err_4;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
cloudConnection = new ewelink({
email: props.userName,
password: props.password,
region: props.region,
APP_ID_WS: APP_ID_WS,
APP_SECRET_WS: APP_SECRET_WS
});
debug('opening cloud web socket...');
_a.label = 1;
case 1:
_a.trys.push([1, 4, , 5]);
return [4 /*yield*/, cloudConnection.getCredentials()];
case 2:
_a.sent();
app.setPluginStatus('Connected to Cloud');
return [4 /*yield*/, cloudConnection
.openWebSocket(function (data) {
if (typeof data === 'string') {
debug('ws recv: ' + data);
return;
}
else {
debug('ws recv: %j', data);
}
if (data.params && data.deviceid) {
var device = getCachedDevice(data.deviceid);
if (device) {
var deviceProps = getDeviceProps(device);
if (!deviceProps ||
typeof deviceProps === 'undefined' ||
deviceProps.enabled) {
sendDeltas(device, data.params);
}
}
else {
error("unknown device: ".concat(data.deviceid));
}
}
})];
case 3:
socket = _a.sent();
socket.onClose.addListener(function (err) {
error('web socket closed: ' + err);
wsTimer = setTimeout(function () {
wsTimer = undefined;
clearInterval(wsPingInterval);
wsPingInterval = undefined;
openWebSocket();
}, 5000);
});
if (wsPingInterval) {
clearInterval(wsPingInterval);
}
wsPingInterval = setInterval(function () { return __awaiter(_this, void 0, void 0, function () {
var err_5;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
debug('sending ping...');
return [4 /*yield*/, socket.send('ping')];
case 1:
_a.sent();
return [3 /*break*/, 3];
case 2:
err_5 = _a.sent();
error(err_5);
return [3 /*break*/, 3];
case 3: return [2 /*return*/];
}
});
}); }, pingTime);
return [3 /*break*/, 5];
case 4:
err_4 = _a.sent();
error(err_4);
app.setPluginError(err_4.message);
wsTimer = setTimeout(function () {
wsTimer = undefined;
openWebSocket();
}, 5000);
return [3 /*break*/, 5];
case 5: return [2 /*return*/];
}
});
});
}
function propHandler(context, path, value, device, prop, cb, converter) {
var _a;
if (converter === void 0) { converter = null; }
var params = converter ? converter(value) : (_a = {}, _a[prop] = value, _a);
cloudConnection
.setWSDeviceParams(device.deviceid, params)
.then(function (status) {
cloudConnection.getWSDeviceStatus(device.deviceid);
debug('got status: %j', status);
cb({
state: 'COMPLETED',
statusCode: status.status === 'ok' ? 200 : 400,
message: status.message
});
})
.catch(function (err) {
error(err);
app.setPluginError(err.message);
cb({ state: 'COMPLETED', statusCode: 400, message: err.message });
});
return { state: 'PENDING' };
}
function registerPutProp(device, prop, converter) {
if (converter === void 0) { converter = null; }
var propPath = getSwitchPath(device, prop);
app.registerPutHandler('vessels.self', propPath, function (context, path, value, cb) {
return propHandler(context, path, value, device, prop, cb, converter);
});
}
function presetHandler(context, path, value, device, cb) {
var switchProps = getSwitchProps(device);
var preset = switchProps.presets.find(function (preset) { return preset.name == value; });
if (value === 'Unknown' || !preset) {
return {
state: 'COMPLETED',
statusCode: 400,
message: "invalid value ".concat(value)
};
}
else {
var params = {
colorR: preset.colorR,
colorG: preset.colorG,
colorB: preset.colorB,
switch: 'on'
};
if (preset.bright !== 0) {
params.bright = preset.bright;
}
cloudConnection
.setWSDeviceParams(device.deviceid, params)
.then(function (status) {
cloudConnection.getWSDeviceStatus(device.deviceid);
debug('got status: %j', status);
cb({
state: 'COMPLETED',
statusCode: status.status === 'ok' ? 200 : 400,
message: status.message
});
})
.catch(function (err) {
error(err);
app.setPluginError(err.message);
cb({ state: 'COMPLETED', statusCode: 400, message: err.message });
});
return { state: 'PENDING' };
}
}
function filterEnabledDevices(devices) {
return devices.filter(function (device) {
var deviceProps = getDeviceProps(device);
return (!deviceProps ||
typeof deviceProps.enabled === 'undefined' ||
deviceProps.enabled);
});
}
function getDevices(devices, doSendDeltas) {
filterEnabledDevices(devices).forEach(function (device) {
if (device.params && (device.params.switches || device.params.switch)) {
if (device.params.switches) {
device.params.switches.forEach(function (channel) {
var switchProps = getSwitchProps(device, channel.outlet);
if (switchProps === null || switchProps === void 0 ? void 0 : switchProps.enabled) {
var switchPath = getBankSwitchPath(device, channel.outlet);
if (!putsRegistred[switchPath]) {
app.registerPutHandler('vessels.self', switchPath, function (context, path, value, cb) {
return bankHandler(context, path, value, device, channel.outlet, cb);
});
putsRegistred[switchPath] = true;
}
}
});
}
else {
var switchProps = getSwitchProps(device);
if (switchProps === null || switchProps === void 0 ? void 0 : switchProps.enabled) {
var switchPath = getSwitchPath(device);
if (!putsRegistred[switchPath]) {
app.registerPutHandler('vessels.self', switchPath, function (context, path, value, cb) {
return switchHandler(context, path, value, device, cb);
});
if (device.uiid === l1Light) {
registerPutProp(device, 'mode', function (value) {
return { mode: Object.values(l1ModeMap).indexOf(value) + 1 };
});
registerPutProp(device, 'colorR');
registerPutProp(device, 'colorG');
registerPutProp(device, 'colorB');
registerPutProp(device, 'dimmingLevel', function (value) {
var val = Number((value * 100).toFixed(0));
if (val === 0) {
val = 1;
}
return { bright: val };
});
registerPutProp(device, 'speed');
registerPutProp(device, 'sensitive');
registerPutProp(device, 'light_type');
var switchProps_1 = getSwitchProps(device);
if (switchProps_1.presets) {
app.registerPutHandler('vessels.self', getSwitchPath(device, 'preset'), function (context, path, value, cb) {
return presetHandler(context, path, value, device, cb);
});
}
putsRegistred[switchPath] = true;
}
}
}
}
if (doSendDeltas) {
sendDeltas(device, device.params);
}
}
});
}
function setBankPowerState(device, state, outlet) {
return __awaiter(this, void 0, void 0, function () {
var stateStr, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
stateStr = state ? 'on' : 'off';
if (!(props.lanMode && !cloudOnlyByDevice[device.deviceid])) return [3 /*break*/, 1];
return [2 /*return*/, lanConnection.setDevicePowerState(device.deviceid, stateStr, outlet + 1)];
case 1: return [4 /*yield*/, cloudConnection.setWSDevicePowerState(device.deviceid, stateStr, {
channel: outlet + 1
})];
case 2:
res = _a.sent();
cloudConnection.getWSDeviceStatus(device.deviceid);
return [2 /*return*/, res];
}
});
});
}
function bankHandler(context, path, value, device, outlet, cb) {
var state = value === 1 || value === 'on' || value === 'true' || value === true;
setBankPowerState(device, state, outlet)
.then(function (status) {
debug('got status outlet %d %j: ', outlet, status);
cb({
state: 'COMPLETED',
statusCode: status.status === 'ok' ? 200 : 400,
message: status.message
});
})
.catch(function (err) {
error(err);
app.setPluginError(err.message);
cb({ state: 'COMPLETED', statusCode: 400, message: err.message });
});
return { state: 'PENDING' };
}
function setPowerState(device, state) {
return __awaiter(this, void 0, void 0, function () {
var stateStr, res;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
stateStr = state ? 'on' : 'off';
if (!(props.lanMode && !cloudOnlyByDevice[device.deviceid])) return [3 /*break*/, 1];
return [2 /*return*/, lanConnection.setDevicePowerState(device.deviceid, stateStr)];
case 1: return [4 /*yield*/, cloudConnection.setWSDevicePowerState(device.deviceid, stateStr)];
case 2:
res = _a.sent();
cloudConnection.getWSDeviceStatus(device.deviceid);
return [2 /*return*/, res];
}
});
});
}
function switchHandler(context, path, value, device, cb) {
var state = value === 1 || value === 'on' || value === 'true';
setPowerState(device, state)
.then(function (status) {
debug('set status: %j', status);
cb({
state: 'COMPLETED',
statusCode: status.status === 'ok' ? 200 : 400,
message: status.message
});
})
.catch(function (err) {
error(err);
app.setPluginError(err.message);
cb({ state: 'COMPLETED', statusCode: 400, message: err.message });
});
return { state: 'PENDING' };
}
function sendMeta(device) {
var meta = [];
if (device.params.switches) {
device.params.switches.forEach(function (channel) {
var switchProps = getSwitchProps(device, channel.outlet);
if (!switchProps ||
typeof switchProps.enabled === 'undefined' ||
switchProps.enabled) {
meta.push({
path: getBankSwitchPath(device, channel.outlet),
value: {
displayName: (switchProps === null || switchProps === void 0 ? void 0 : switchProps.displayName) || device.name,
abbrev: switchProps === null || switchProps === void 0 ? void 0 : switchProps.abbrev,
//units: 'bool',
order: channel.outlet
}
});
meta.push({
path: getBankSwitchPath(device, channel.outlet, null),
value: {
displayName: (switchProps === null || switchProps === void 0 ? void 0 : switchProps.displayName) || device.name,
abbrev: switchProps === null || switchProps === void 0 ? void 0 : switchProps.abbrev,
order: channel.outlet
}
});
}
});
}
else if (device.params.switch) {
var switchProps = getSwitchProps(device);
if (!switchProps ||
typeof switchProps.enabled === 'undefined' ||
switchProps.enabled) {
var extras = {};
if (device.uiid === l1Light) {
extras = {
type: 'dimmer',
canDimWhenOff: true
};
meta.push({
path: getSwitchPath(device, 'mode'),
value: {
enum: Object.values(l1ModeMap),
possibleValues: Object.values(l1ModeMap).map(function (mode) {
return {
title: mode,
value: mode
};
})
}
});
if (switchProps.presets) {
meta.push({
path: getSwitchPath(device, 'preset'),
value: {
displayName: (switchProps === null || switchProps === void 0 ? void 0 : switchProps.displayName) || device.name,
possibleValues: __spreadArray([], switchProps.presets.map(function (preset) {
return {
title: preset.name,
value: preset.name
};
}), true),
enum: __spreadArray([], switchProps.presets.map(function (preset) { return preset.name; }), true)
}
});
}
}
meta.push({
path: getSwitchPath(device),
value: __assign(__assign({}, extras), { displayName: (switchProps === null || switchProps === void 0 ? void 0 : switchProps.displayName) || device.name, abbrev: switchProps === null || switchProps === void 0 ? void 0 : switchProps.abbrev })
});
meta.push({
path: getSwitchPath(device, null),
value: __assign(__assign({}, extras), { displayName: (switchProps === null || switchProps === void 0 ? void 0 : switchProps.displayName) || device.name, abbrev: switchProps === null || switchProps === void 0 ? void 0 : switchProps.abbrev })
});
}
}
if (meta.length) {
debug('sending meta: %j', meta);
app.handleMessage(plugin.id, {
updates: [
{
meta: meta
}
]
});
}
}
function sendDeltas(device, params) {
var values;
if (!sentMetaDevices[device.deviceid]) {
sendMeta(device);
sentMetaDevices[device.deviceid] = true;
}
device.params = __assign(__assign({}, device.params), params);
if (params.switches) {
values = params.switches
.map(function (channel) {
var switchProps = getSwitchProps(device, channel.outlet);
if (!switchProps ||
typeof switchProps.enabled === 'undefined' ||
switchProps.enabled) {
return [
{
path: getBankSwitchPath(device, channel.outlet),
value: channel.switch === 'on' ? 1 : 0
},
{
path: getBankSwitchPath(device, channel.outlet, 'order'),
value: channel.outlet
}
];
}
else {
return null;
}
})
.filter(function (kp) { return kp != null; });
values = [].concat.apply([], values);
}
else {
var switchProps = getSwitchProps(device);
if (!switchProps ||
typeof switchProps.enabled === 'undefined' ||
switchProps.enabled) {
values = [];
if (params.switch) {
values.push({
path: getSwitchPath(device),
value: params.switch === 'on' ? 1 : 0
});
}
var addValue = function (key, v) {
var val = typeof v !== 'undefined' ? v : params[key];
if (typeof val !== 'undefined') {
values.push({
path: getSwitchPath(device, key),
value: val
});
}
};
if (device.uiid === l1Light) {
addValue('mode', l1ModeMap[params.mode]);
addValue('colorR');
addValue('colorG');
addValue('colorB');
if (typeof params.bright !== 'undefined') {
addValue('dimmingLevel', params.bright / 100.0);
}
addValue('speed');
addValue('sensitive');
addValue('light_type');
if (switchProps.presets) {
var preset = switchProps.presets.find(function (preset) {
return (device.params.colorR == preset.colorR &&
device.params.colorG == preset.colorG &&
device.params.colorB == preset.colorB &&
(preset.bright === 0 || device.params.bright == preset.bright));
});
values.push({
path: getSwitchPath(device, 'preset'),
value: (preset === null || preset === void 0 ? void 0 : preset.name) || 'Unknown'
});
}
}
}
}
if (values) {
app.handleMessage(plugin.id, {
updates: [
{
values: values
}
]
});
}
}
function getCachedDevice(deviceid) {
return devicesCache.find(function (device) { return device.deviceid == deviceid; });
}
function findArpTableEntry(mac) {
return arpTable.find(function (entry) { return entry.mac == mac; });
}
function updateIPAddress(deviceid, service) {
if (service.addresses && service.addresses.length > 0) {
var device = getCachedDevice(deviceid);
if (device) {
var mac = device.extra.extra.staMac.toLowerCase();
var arp = findArpTableEntry(mac);
if (arp) {
arp.ip = service.addresses[0];
}
else {
arpTable.push({ ip: service.addresses[0], mac: mac });
}
saveArpTable();
}
}
}
function readArpTabel() {
try {
var content = fs.readFileSync(arpTablePath);
arpTable = JSON.parse(content);
}
catch (err) {
error(err);
}
}
function saveArpTable() {
try {
fs.writeFileSync(arpTablePath, JSON.stringify(arpTable, null, 2));
}
catch (err) {
error(err);
}
}
function dnsdChanged(service) {
var txt = service.txt || service.txtRecord;
if (!txt || !txt.id || !txt.iv) {
error('invalid mdns record');
error(JSON.stringify(service, null, 2));
}
var deviceid = txt.id;
var iv = txt.iv;
debug('got dnsd for device %s (id:%s)', service.name, deviceid);
var device = getCachedDevice(deviceid);
if (!device) {
var msg = 'new device found, please restart the plugin';
error(msg);
app.setPluginError(msg);
return;
}
updateIPAddress(deviceid, service);
try {
var info = decryptMessage(txt, device.devicekey, iv);
sendDeltas(device, info);
}
catch (err) {
error(err);
app.setPluginError('unable to decrypt mdns data');
}
}
function decryptMessage(msg, deviceKey, iv) {
var encoded = '';
for (var i = 1; i < 6; i++) {
var part = msg["data".concat(i)];
if (part) {
encoded = encoded + part;
}
}
return JSON.parse(decryptionData(encoded, deviceKey, iv));
}
function getDeviceProps(device) {
return props["Device ID ".concat(device.deviceid)] || {};
}
function getSwitchProps(device, channel) {
if (channel === void 0) { channel = undefined; }
if (device.params.switches) {
var bankConfig = props["Device ID ".concat(device.deviceid)] || {};
return bankConfig["Channel ".concat(channel)];
}
else {
return props["Device ID ".concat(device.deviceid)] || {};
}
}
function getSwitchPath(device, key) {
if (key === void 0) { key = 'state'; }
var config = props["Device ID ".concat(device.deviceid)] || {};
return "electrical.switches.".concat(config.switchPath || camelCase(device.name)).concat(key ? '.' + key : '');
}
function getBankSwitchPath(device, channel, key) {
var _a, _b, _c;
if (key === void 0) { key = 'state'; }
var bankConfig = props["Device ID ".concat(device.deviceid)] || {};
var path = (_a = bankConfig["Channel ".concat(channel)]) === null || _a === void 0 ? void 0 : _a.switchPath;
var cloud = ((_b = device.tags) === null || _b === void 0 ? void 0 : _b.ck_channel_name)
? (_c = device.tags) === null || _c === void 0 ? void 0 : _c.ck_channel_name[channel.toString()]
: undefined;
if (!path && cloud) {
path = camelCase(cloud);
}
return "electrical.switches.".concat(bankConfig.bankPath ||
camelCase(device.name), ".").concat(path || channel).concat(key ? '.' + key : '');
}
return plugin;
}
exports.default = default_1;
var l1Light = 59;
var cloudOnlyHardware = [l1Light];
var l1ModeMap = {
1: 'Colorful',
2: 'Colorful Gradient',
3: 'Colorful Breath',
4: 'DIY Gradient',
5: 'DIY Pulse',
6: 'DIY Breath',
7: 'DIY Strobe',
8: 'RGB Gradient',
9: 'RGB Pulse',
10: 'RGB Breath',
11: 'RBG Strobe',
12: 'Sync to music'
};