node-red-contrib-home-assistant-websocket
Version:
Node-RED integration with Home Assistant through websocket and REST API
261 lines (260 loc) • 11.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _UnidirectionalIntegration_instances, _UnidirectionalIntegration_isValidVersionforDevices, _UnidirectionalIntegration_removeDevice;
Object.defineProperty(exports, "__esModule", { value: true });
const compare_versions_1 = require("compare-versions");
const node_1 = require("../../helpers/node");
const Events_1 = require("../events/Events");
const helpers_1 = require("./helpers");
const Integration_1 = __importStar(require("./Integration"));
class UnidirectionalIntegration extends Integration_1.default {
constructor(props) {
super(props);
_UnidirectionalIntegration_instances.add(this);
this.deviceConfigNode = props.deviceConfigNode;
this.entityConfigNode = props.entityConfigNode;
}
async init() {
var _a;
this.entityConfigNode.on(Events_1.NodeEvent.Close, this.onEntityConfigNodeClose.bind(this));
(_a = this.deviceConfigNode) === null || _a === void 0 ? void 0 : _a.on(Events_1.NodeEvent.Close, this.onDeviceConfigNodeClose.bind(this));
super.init();
}
async onEntityConfigNodeClose(removed, done) {
if (this.registered && this.isIntegrationLoaded && removed) {
try {
await this.unregister();
}
catch (err) {
done(err);
}
}
done();
}
async onDeviceConfigNodeClose(removed, done) {
if (this.registered && this.isIntegrationLoaded && removed) {
try {
await __classPrivateFieldGet(this, _UnidirectionalIntegration_instances, "m", _UnidirectionalIntegration_removeDevice).call(this);
}
catch (err) {
done(err);
}
}
done();
}
getDeviceInfo() {
if (!this.deviceConfigNode) {
return undefined;
}
const config = this.deviceConfigNode.config;
return {
id: config.id,
hw_version: config.hwVersion,
name: config.name,
manufacturer: config.manufacturer,
model: config.model,
sw_version: config.swVersion,
};
}
getStateData(state) {
if (!state) {
return {};
}
let data = {};
if (this.entityConfigNode.config.resend && state) {
const lastPayload = state.getLastPayload();
if (lastPayload) {
data = { ...lastPayload };
}
}
return data;
}
getDiscoveryPayload({ config, remove, state, }) {
const deviceInfo = this.getDeviceInfo();
const message = {
...this.getStateData(state),
type: Integration_1.MessageType.Discovery,
server_id: this.entityConfigNode.config.server,
node_id: this.entityConfigNode.id,
config,
component: this.entityConfigNode.config.entityType,
remove,
device_info: deviceInfo,
};
return message;
}
getEntityPayload(state, attributes) {
return {
type: Integration_1.MessageType.Entity,
server_id: this.entityConfigNode.config.server,
node_id: this.entityConfigNode.id,
state,
attributes,
};
}
async register() {
if (!this.isIntegrationLoaded) {
this.entityConfigNode.error(this.notInstalledMessage);
this.status.forEach((status) => status.setFailed('home-assistant.status.error'));
return;
}
if (this.isRegistered)
return;
const config = (0, helpers_1.createHaConfig)(this.entityConfigNode.config.haConfig);
const payload = this.getDiscoveryPayload({
config,
state: this.state,
});
this.debugToClient('register', payload);
this.entityConfigNode.debug(`Registering ${this.entityConfigNode.config.entityType} with HA`);
try {
await this.homeAssistant.websocket.send(payload);
}
catch (err) {
this.status.forEach((status) => status.setFailed('home-assistant.status.error_registering'));
const message = err instanceof Error ? err.message : err;
this.entityConfigNode.error(`Error registering entity. Error Message: ${message}`);
return;
}
this.saveHaConfigToContext(config);
this.status.forEach((status) => status === null || status === void 0 ? void 0 : status.setSuccess('home-assistant.status.registered'));
this.registered = true;
}
setStatus(status) {
this.status.push(status);
}
async updateStateAndAttributes(state, attributes) {
const payload = this.getEntityPayload(state, attributes);
try {
await this.homeAssistant.websocket.send(payload);
}
catch (err) {
this.entityConfigNode.error(`Error updating entity. Error Message: ${err}`);
}
if (this.entityConfigNode.config.resend) {
const lastPayload = {
state,
attributes,
};
this.state.setLastPayload(lastPayload);
}
this.debugToClient('update state', payload);
return payload;
}
async unregister() {
var _a;
this.entityConfigNode.debug(`Unregistering ${this.entityConfigNode.config.entityType} node from HA`);
const payload = this.entityConfigNode.integration.getDiscoveryPayload({
remove: true,
});
this.debugToClient('unregister', payload);
try {
await ((_a = this.homeAssistant) === null || _a === void 0 ? void 0 : _a.websocket.send(payload));
}
catch (err) {
this.entityConfigNode.error(`Error unregistering entity. Error Message: ${err}`);
}
}
debugToClient(topic, message) {
(0, node_1.debugToClient)(this.entityConfigNode, message, topic);
}
getEntityConfigNode() {
return this.entityConfigNode;
}
/**
* This method retrieves the value of a specific key from the Home Assistant configuration for the entity.
* If the key does not exist in the configuration, it returns undefined.
*
* @param key - The key for which to retrieve the value.
* @returns The value of the key from the Home Assistant configuration, or undefined if the key does not exist.
*/
getEntityHomeAssistantConfigValue(key) {
var _a;
// Get the Home Assistant configuration from the context or the entity's config
const haConfig = (_a = this.getHaConfigFromContext()) !== null && _a !== void 0 ? _a : (0, helpers_1.createHaConfig)(this.entityConfigNode.config.haConfig);
return haConfig[key];
}
/**
* This method saves the Home Assistant (HA) configuration to the context.
* It first retrieves any existing HA configuration from the context.
* Then it removes any properties from the new HA configuration that have undefined values.
* Finally, it merges the existing and new HA configurations and saves the result back to the context.
*
* @param haConfig - The new HA configuration to save. Properties with undefined values will not be saved.
*/
saveHaConfigToContext(haConfig) {
var _a;
const existingHaConfig = (_a = this.getHaConfigFromContext()) !== null && _a !== void 0 ? _a : {};
// remove undefined values from haConfig
Object.keys(haConfig).forEach((key) => haConfig[key] === undefined && delete haConfig[key]);
const mergedHaConfig = {
...existingHaConfig,
...haConfig,
};
this.entityConfigNode.context().set('haConfig', mergedHaConfig);
}
/**
* This method retrieves the Home Assistant (HA) configuration from the context.
* If the HA configuration does not exist in the context, it returns undefined.
*
* @returns The HA configuration from the context, or undefined if it does not exist.
*/
getHaConfigFromContext() {
return this.entityConfigNode.context().get('haConfig');
}
}
_UnidirectionalIntegration_instances = new WeakSet(), _UnidirectionalIntegration_isValidVersionforDevices = function _UnidirectionalIntegration_isValidVersionforDevices() {
return ((0, compare_versions_1.compareVersions)(`${this.homeAssistant.websocket.integrationVersion}`, '1.1') >= 0);
}, _UnidirectionalIntegration_removeDevice = async function _UnidirectionalIntegration_removeDevice() {
var _a;
if (!this.deviceConfigNode || !__classPrivateFieldGet(this, _UnidirectionalIntegration_instances, "m", _UnidirectionalIntegration_isValidVersionforDevices).call(this))
return;
this.deviceConfigNode.debug(`Removing device from Home Assistant: ${this.deviceConfigNode.config.name}`);
try {
await ((_a = this.homeAssistant) === null || _a === void 0 ? void 0 : _a.websocket.send({
type: Integration_1.MessageType.RemoveDevice,
node_id: this.deviceConfigNode.id,
}));
}
catch (err) {
this.deviceConfigNode.error(`Error removing device. Error Message: ${err}`);
}
};
exports.default = UnidirectionalIntegration;