UNPKG

node-red-contrib-smartnora

Version:

Google Smart Home integration via Smart Nora https://smart-nora.eu/

215 lines (214 loc) 8.92 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.convertValueType = convertValueType; exports.getValue = getValue; exports.escapeFirebasePath = escapeFirebasePath; exports.getId = getId; exports.getNumberOrDefault = getNumberOrDefault; exports.registerNoraDevice = registerNoraDevice; exports.getClose = getClose; exports.handleNodeInput = handleNodeInput; exports.R = R; const nora_firebase_common_1 = require("@andrei-tatar/nora-firebase-common"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const __1 = require(".."); const connection_1 = require("../nora/connection"); const device_1 = require("../nora/device"); const device_context_1 = require("../nora/device-context"); const local_execution_1 = require("../nora/local-execution"); const safe_update_1 = require("../nora/safe-update"); function convertValueType(RED, value, type, { defaultType = 'bool', defaultValue = false } = {}) { if (type === 'flow' || type === 'global') { try { const parts = RED.util.normalisePropertyExpression(value); if (parts.length === 0) { throw new Error(); } } catch (_err) { value = defaultValue; type = defaultType; } } return { value, type }; } function getValue(RED, node, value, type) { if (type === 'date') { return Date.now(); } else { return RED.util.evaluateNodeProperty(value, type, node); } } function escapeFirebasePath(value) { return value.replace(/[.#$[\]]/g, ':'); } function getId({ id }) { return escapeFirebasePath(id); } function getNumberOrDefault(a, defaultValue = 0) { const nr = +a; if (isFinite(nr)) { return nr; } return defaultValue; } function registerNoraDevice(node, RED, nodeConfig, options) { var _a, _b, _c; const noraConfig = RED.nodes.getNode(nodeConfig.nora); if (!(noraConfig === null || noraConfig === void 0 ? void 0 : noraConfig.valid)) { return; } const close$ = getClose(node); const ctx = new device_context_1.DeviceContext(node); ctx.startUpdating(close$); const deviceConfig = noraConfig.setCommon(Object.assign({ id: getId(nodeConfig) }, options.deviceConfig), nodeConfig); const configureOutputMessage = (msg) => (Object.assign(Object.assign(Object.assign({}, msg), (nodeConfig.topic ? { topic: nodeConfig.topic, } : null)), (noraConfig.sendDeviceNameAndLocation ? { device: deviceConfig.name.name, location: deviceConfig.roomHint, } : null))); if (noraConfig.storeStateInContext) { const contextState = node.context().get('state'); const safeUpdate = {}; (0, safe_update_1.getSafeUpdate)({ update: contextState !== null && contextState !== void 0 ? contextState : {}, safeUpdateObject: safeUpdate, currentState: deviceConfig.state, isValid: () => (0, nora_firebase_common_1.validate)(deviceConfig.traits, 'state-update', safeUpdate).valid, warn: (propName) => node.warn(`ignoring property from stored context ${propName}`), }); const { state: safeState } = (0, nora_firebase_common_1.updateState)(safeUpdate, deviceConfig.state); deviceConfig.state = safeState; } const device$ = connection_1.FirebaseConnection .withLogger(RED.log) .fromConfig(noraConfig, ctx) .pipe((0, operators_1.switchMap)(connection => connection.withDevice(deviceConfig, { ctx, disableValidationErrors: noraConfig.disableValidationErrors, })), withLocalExecution(noraConfig), (0, __1.singleton)(), (0, operators_1.takeUntil)(close$)); let subscriptions = 0; if (options.updateStatus || noraConfig.storeStateInContext) { device$.pipe((0, operators_1.switchMap)(d => d.state$), (0, operators_1.takeUntil)(close$)).subscribe(state => { var _a; if (noraConfig.storeStateInContext) { node.context().set('state', state); } (_a = options.updateStatus) === null || _a === void 0 ? void 0 : _a.call(options, { state, update: msg => ctx.status$.next(msg), }); }); subscriptions++; } if (options.mapStateToOutput) { device$.pipe((0, operators_1.switchMap)(d => d.stateUpdates$), (0, operators_1.takeUntil)(close$)).subscribe(state => { var _a; const output = (_a = options === null || options === void 0 ? void 0 : options.mapStateToOutput) === null || _a === void 0 ? void 0 : _a.call(options, state); if (output) { node.send(configureOutputMessage(output)); } }); subscriptions++; } if (deviceConfig.noraSpecific.asyncCommandExecution === true || Array.isArray(deviceConfig.noraSpecific.asyncCommandExecution) && deviceConfig.noraSpecific.asyncCommandExecution.length) { const padding = new Array(nodeConfig.outputs - 1).fill(null); device$.pipe((0, operators_1.switchMap)(d => d.asyncCommands$), (0, operators_1.takeUntil)(close$)).subscribe(({ id, command }) => { node.send([ ...padding, { _asyncCommandId: id, payload: Object.assign({ command: command.command.substring(command.command.lastIndexOf('.') + 1) }, command.params), }, ]); }); subscriptions++; } if (options.handleNodeInput) { handleNodeInput({ node, nodeConfig, configure: msg => configureOutputMessage(msg), handler: msg => { var _a; return (_a = options === null || options === void 0 ? void 0 : options.handleNodeInput) === null || _a === void 0 ? void 0 : _a.call(options, { msg, updateState: async (...args) => { const device = await (0, rxjs_1.firstValueFrom)(device$); return await device.updateState(...args); }, device$, state$: device$.pipe((0, operators_1.switchMap)(d => d.state$)), }); }, }); } if (!subscriptions) { device$.subscribe(); } (_c = (_b = (_a = options === null || options === void 0 ? void 0 : options.customRegistration) === null || _a === void 0 ? void 0 : _a.call(options, device$)) === null || _b === void 0 ? void 0 : _b.pipe((0, operators_1.takeUntil)(close$), (0, operators_1.retry)({ delay: err => { node.warn(err); return (0, rxjs_1.of)(err); }, }))) === null || _c === void 0 ? void 0 : _c.subscribe(); } function getClose(node) { const close$ = new rxjs_1.Subject(); node.on('close', () => { close$.next(); close$.complete(); }); return close$.asObservable(); } function handleNodeInput(opts) { opts.node.on('input', async (msg, send, done) => { var _a, _b, _c, _d, _e, _f; if (((_a = opts.nodeConfig) === null || _a === void 0 ? void 0 : _a.filter) && ((_b = opts.nodeConfig) === null || _b === void 0 ? void 0 : _b.topic) && `${(_c = opts.nodeConfig) === null || _c === void 0 ? void 0 : _c.topic}` !== `${msg.topic}`) { done === null || done === void 0 ? void 0 : done(); return; } if ((_d = opts.nodeConfig) === null || _d === void 0 ? void 0 : _d.passthru) { const sendMessage = send !== null && send !== void 0 ? send : opts.node.send.bind(opts.node); const output = (_f = (_e = opts.configure) === null || _e === void 0 ? void 0 : _e.call(opts, msg)) !== null && _f !== void 0 ? _f : msg; sendMessage(output); } try { await opts.handler(msg); done === null || done === void 0 ? void 0 : done(); } catch (err) { if (done) { done(err); } else { opts.node.error(`${err}`); } } }); } function R(template, ...substiutions) { return String.raw(template, ...substiutions.map(v => { if (typeof v === 'number') { return Math.round(v * 10) / 10; } return v; })); } function withLocalExecution(config) { return source => source.pipe((0, operators_1.switchMap)(device => { if (!(device instanceof device_1.FirebaseDevice)) { throw new Error('device must derive FirebaseDevice'); } return (0, rxjs_1.merge)(config.localExecution ? local_execution_1.LocalExecution.instance.registerDeviceForLocalExecution(device) : rxjs_1.EMPTY, (0, rxjs_1.of)(device)); })); }