node-red-contrib-smartnora
Version:
Google Smart Home integration via Smart Nora https://smart-nora.eu/
215 lines (214 loc) • 8.92 kB
JavaScript
;
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));
}));
}