@ha4us/hue.adapter
Version:
Adapter for the hue system to ha4us
377 lines (376 loc) • 20.1 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
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) : new P(function (resolve) { resolve(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 (_) 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 };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var adapter_1 = require("@ha4us/adapter");
var core_1 = require("@ha4us/core");
var node_hue_api_1 = require("node-hue-api");
var rxjs_1 = require("rxjs");
var operators_1 = require("rxjs/operators");
var models_1 = require("./models");
var rgb_1 = require("./rgb");
var ADAPTER_OPTIONS = {
name: 'hue',
path: __dirname + '/..',
args: {
ip: {
demandOption: false,
default: 'auto',
describe: 'id of the hue to connect to',
type: 'string',
},
user: {
demandOption: false,
describe: 'hue user - if not known, option to create on start',
type: 'string',
},
poll: {
demandOption: false,
default: 5,
describe: 'polling intervall in sec.',
type: 'number',
},
},
imports: ['$states', '$objects', '$media'],
logo: 'logos/hue_logo.png',
};
function Adapter($log, $args, $states, $objects, $media) {
var converterMap = new Map();
function createUser(ip) {
var client = new node_hue_api_1.HueApi();
$log.debug('Creating User for ip %s', ip);
return client
.createUser(ip, $args.name)
.then(function (user) {
$log.debug('User created', user);
return user;
})
.catch(function (error) {
$log.info('Please press link button and start over again', error);
throw new core_1.Ha4usError(200, 'please press link button before starting');
});
}
function connectHue() {
return __awaiter(this, void 0, void 0, function () {
var client, e_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
client = new node_hue_api_1.HueApi($args.hueIp, $args.hueUser);
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, client.getConfig()];
case 2:
_a.sent();
return [2 /*return*/, client];
case 3:
e_1 = _a.sent();
$log.error('Connect failed', e_1);
throw new core_1.Ha4usError(403, 'user not valid');
case 4: return [2 /*return*/];
}
});
});
}
function getObjects(client) {
return __awaiter(this, void 0, void 0, function () {
var objects, groups, light2room, lights;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
objects = [];
return [4 /*yield*/, client.groups()];
case 1:
groups = _a.sent();
$log.debug('Groups', groups);
light2room = {};
groups
.filter(function (group) { return group.type === 'Room' || group.type === 'LightGroup '; })
.forEach(function (group) {
group._topic = group.name;
group._type = 'group';
if (group.type === 'Room') {
group.lights.forEach(function (id) {
light2room[id] = group.name;
});
}
objects.push(group);
});
return [4 /*yield*/, client.lights()];
case 2:
lights = _a.sent();
$log.debug('Lights', lights);
lights.lights.forEach(function (light) {
light._type = 'light';
var roomName = light2room[light.id];
light._topic = roomName ? [roomName, light.name].join('/') : light.name;
light._roomName = roomName;
var cg = core_1.get(light, 'capabilities.control.colorgamut');
if (cg) {
$log.debug('Adding color property', cg);
var conv = new rgb_1.XY2RGBConverter(cg);
converterMap.set(light.id.toString(), conv);
light.converter = conv;
light.state.color = conv.toRGB(light.state.xy, light.state.bri);
}
objects.push(light);
});
$log.debug('Objects detected', objects);
rxjs_1.from(objects)
.pipe(operators_1.mergeMap(function (obj) {
return rxjs_1.combineLatest(rxjs_1.of(obj), obj._type === 'light' ? rxjs_1.from(Object.keys(obj.state)) : rxjs_1.from(['on'])).pipe(operators_1.startWith([obj, undefined]));
}), operators_1.mergeMap(function (_a) {
var obj = _a[0], state = _a[1];
var newObject = {
label: obj.name,
type: core_1.Ha4usObjectType.Mixed,
tags: obj._roomName ? ["@" + obj._roomName] : [],
can: {
read: true,
write: true,
trigger: true,
},
native: {},
};
switch (state) {
case 'on':
newObject.type = core_1.Ha4usObjectType.Boolean;
newObject.can.trigger = obj._type !== 'group';
newObject.can.read = obj._type !== 'group';
newObject.role = 'Toggle/PowerState';
break;
case 'bri':
newObject.type = core_1.Ha4usObjectType.Number;
newObject.min = 1;
newObject.max = 254;
newObject.role = 'Range/Brightness';
break;
case 'ct':
newObject.min = obj.capabilities.control.ct.min;
newObject.max = obj.capabilities.control.ct.max;
newObject.role = 'Range/Color/Temperature';
break;
case 'xy':
newObject.type = core_1.Ha4usObjectType.Mixed;
newObject.role = 'Range/Color/XY';
break;
case 'color':
$log.debug('Creating object for color');
newObject.type = core_1.Ha4usObjectType.String;
newObject.role = 'Input/Color/Rgb';
break;
case 'hue':
newObject.type = core_1.Ha4usObjectType.Number;
newObject.min = 0;
newObject.max = 65535;
newObject.role = 'Range/Color/Hue';
break;
case 'sat':
newObject.type = core_1.Ha4usObjectType.Number;
newObject.min = 0;
newObject.max = 254;
newObject.role = 'Range/Color/Saturation';
break;
case 'alert':
newObject.type = core_1.Ha4usObjectType.String;
newObject.role = 'Mode/Hue/Alert';
break;
case 'effect':
newObject.type = core_1.Ha4usObjectType.String;
newObject.role = 'Mode/Hue/Effect';
break;
case 'colormode':
newObject.type = core_1.Ha4usObjectType.String;
newObject.can.write = false;
newObject.role = 'Mode/Color';
break;
case 'mode':
newObject.type = core_1.Ha4usObjectType.String;
newObject.can.write = false;
newObject.role = 'Mode/Hue/Mode';
break;
case 'reachable':
newObject.type = core_1.Ha4usObjectType.Boolean;
newObject.can.write = false;
newObject.role = 'Indicator/System/Reachable';
break;
default:
newObject.can.read = false;
newObject.can.write = false;
newObject.can.trigger = false;
newObject.role = models_1.HUE_MODELS[obj.modelid]
? models_1.HUE_MODELS[obj.modelid].role
: 'Device/Hue/Unknown';
newObject.image = models_1.HUE_MODELS[obj.modelid]
? models_1.HUE_MODELS[obj.modelid].image
: undefined;
newObject.native = {
id: obj.id,
class: obj.class,
type: obj.type,
lights: obj.lights,
modelid: obj.modelid,
manufacturername: obj.manufacturername,
productname: obj.productname,
uniqueid: obj.uniqueid,
};
break;
}
var topic = !state ? obj._topic : core_1.MqttUtil.join(obj._topic, state);
return $objects.install(topic, newObject, adapter_1.CreateObjectMode.force);
}), operators_1.tap(function (obj) { return $log.debug("Object " + obj.topic + " created"); }), operators_1.count(function (val) { return !!val; }))
.subscribe(function (_count) {
$log.info('%d objects created', _count);
});
return [2 /*return*/, objects];
}
});
});
}
function $onInit() {
return __awaiter(this, void 0, void 0, function () {
var bridges, user, client, objects;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
$log.info('Starting hue');
if (!($args.hueIp === 'auto')) return [3 /*break*/, 2];
$log.info('Autodiscovering IP');
return [4 /*yield*/, node_hue_api_1.upnpSearch(10000)];
case 1:
bridges = _a.sent();
if (bridges.length === 1) {
$args.hueIp = bridges[0].ipaddress;
$log.info('Discovered IP: %s', $args.hueIp);
}
else {
$log.info('Multiple bridges identified', bridges);
throw new core_1.Ha4usError(500, 'single hue bridge cannot be identified');
}
_a.label = 2;
case 2:
$log.debug('Using hue IP', $args.hueIp);
if (!!$args.hueUser) return [3 /*break*/, 4];
return [4 /*yield*/, createUser($args.hueIp)];
case 3:
user = _a.sent();
$log.info('Created hue User: %s', user);
$args.hueUser = user;
_a.label = 4;
case 4:
$log.debug('Connecting');
return [4 /*yield*/, connectHue()
// now normally save configuration
];
case 5:
client = _a.sent();
// now normally save configuration
$log.debug('Connected to hue', client);
return [4 /*yield*/, getObjects(client)];
case 6:
objects = _a.sent();
rxjs_1.interval($args.huePoll * 1000)
.pipe(operators_1.startWith(0), operators_1.mergeMap(function () {
return rxjs_1.from(objects);
}), operators_1.filter(function (obj) { return obj._type === 'light'; }), operators_1.mergeMap(function (obj) { return rxjs_1.zip(rxjs_1.of(obj), client.lightStatus(obj.id)); }, 3))
.subscribe(function (_a) {
var target = _a[0], states = _a[1];
if (states.state) {
if (target.converter) {
states.state.color = target.converter.toRGB(states.state.xy, states.state.bri);
}
Object.keys(states.state).forEach(function (state) {
$states.status(core_1.MqttUtil.join(['$' + target._topic, state]), states.state[state], true);
});
}
}, function (e) {
$log.error('Error occurred! Stopping observe of hue system', e);
});
$states
.observe('/$set/#/+')
.pipe(operators_1.mergeMap(function (msg) {
var _a;
$log.debug('Setting %s to %s', msg.topic, msg.val);
var _b = msg.match.params, topic = _b[0], state = _b[1];
var target = objects.find(function (obj) { return obj._topic === topic; });
if (!target) {
$log.warn(topic + " does not exist");
return rxjs_1.of();
}
if (!target.state.hasOwnProperty(state) &&
!(target.action && !target.action.hasOwnProperty(state))) {
$log.warn(topic + " has no state or action " + state);
return rxjs_1.of();
}
if (state === 'color' && target.converter) {
return client
.setLightState(target.id, { xy: target.converter.toXY(msg.val) })
.catch(function (e) {
// just log error and swallow the exception
$log.error('Error occurred for color', e.message);
});
}
else {
return client
.setLightState(target.id, (_a = {}, _a[state] = msg.val, _a))
.catch(function (e) {
// just log error and swallow the exception
$log.error('Error occurred for normal set operation', e.message);
});
}
}))
.subscribe();
$states.connected = 2;
return [2 /*return*/, true];
}
});
});
}
function $onDestroy() {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
$log.info('Destroying hue');
return [2 /*return*/];
});
});
}
return {
$onInit: $onInit,
$onDestroy: $onDestroy,
};
}
adapter_1.ha4us(ADAPTER_OPTIONS, Adapter);