@haydendonald/node-red-contrib-hass-stuff 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,330 @@
1
+ "use strict";
2
+ const baseConfigNode_1 = require("../baseConfigNode");
3
+ const connectionsTypes_1 = require("./connectionsTypes");
4
+ const utility_1 = require("../utility");
5
+ module.exports = function ConnectionsConfigNode(RED) {
6
+ const register = function (config) {
7
+ RED.nodes.createNode(this, config);
8
+ (0, baseConfigNode_1.assignBaseConfigNode)(this);
9
+ const self = this;
10
+ self.hassAPICallbacks = {};
11
+ self.hassActionCallbacks = {};
12
+ self.hassCurrentStateCallbacks = {};
13
+ self.hassGetEntitiesCallbacks = {};
14
+ self.hassEventReadyCallbacks = {};
15
+ self.hassEventCallbacks = {};
16
+ self.hassEventCallServiceCallbacks = {};
17
+ self.hassEventStateChangeCallbacks = {};
18
+ self.sendHASSAPI = function (protocol, method, path, callback, params, data, results) {
19
+ const callbackId = callback ? (0, utility_1.generateRandomId)(Object.keys(self.hassAPICallbacks)) : undefined;
20
+ const msg = {
21
+ topic: connectionsTypes_1.Topics.API,
22
+ callbackId,
23
+ payload: {
24
+ protocol,
25
+ method,
26
+ path,
27
+ data,
28
+ params,
29
+ results
30
+ }
31
+ };
32
+ self.sendMsg(msg);
33
+ //Add our callback for when a response comes back in
34
+ if (callbackId && callback) {
35
+ self.hassAPICallbacks[callbackId] = callback;
36
+ }
37
+ };
38
+ self.sendHASSAction = function (action, target, data, callback) {
39
+ const callbackId = callback ? (0, utility_1.generateRandomId)(Object.keys(self.hassActionCallbacks)) : undefined;
40
+ const msg = {
41
+ topic: connectionsTypes_1.Topics.ACTION,
42
+ callbackId,
43
+ payload: {
44
+ action,
45
+ target,
46
+ data
47
+ }
48
+ };
49
+ self.sendMsg(msg);
50
+ //Add our callback for when a response comes back in
51
+ if (callbackId && callback) {
52
+ self.hassActionCallbacks[callbackId] = callback;
53
+ }
54
+ };
55
+ self.getHASSEntityState = function (entityId, callback) {
56
+ const callbackId = callback ? (0, utility_1.generateRandomId)(Object.keys(self.hassCurrentStateCallbacks)) : undefined;
57
+ const msg = {
58
+ topic: connectionsTypes_1.Topics.STATE,
59
+ callbackId,
60
+ payload: {
61
+ entityId
62
+ }
63
+ };
64
+ self.sendMsg(msg);
65
+ //Add our callback for when a response comes back in
66
+ if (callbackId && callback) {
67
+ self.hassCurrentStateCallbacks[callbackId] = callback;
68
+ }
69
+ };
70
+ self.getHASSEntities = function (rules, callback) {
71
+ const callbackId = callback ? (0, utility_1.generateRandomId)(Object.keys(self.hassGetEntitiesCallbacks)) : undefined;
72
+ const msg = {
73
+ topic: connectionsTypes_1.Topics.GET_ENTITIES,
74
+ callbackId,
75
+ payload: {
76
+ rules
77
+ }
78
+ };
79
+ self.sendMsg(msg);
80
+ //Add our callback for when a response comes back in
81
+ if (callbackId && callback) {
82
+ self.hassGetEntitiesCallbacks[callbackId] = callback;
83
+ }
84
+ };
85
+ self.addHASSEntity = function (entityId, data, callback) {
86
+ self.sendHASSAPI("http", "post", "/api/states/" + entityId, callback, undefined, data);
87
+ };
88
+ self.addHASSInputBoolean = function (options) {
89
+ let currentState = options.state || "unknown";
90
+ const entityId = options.id ? options.id : (0, utility_1.getEntityId)("input_boolean", options.friendlyName);
91
+ const data = (state) => {
92
+ return {
93
+ state: state || "unknown",
94
+ attributes: {
95
+ friendly_name: options.friendlyName
96
+ }
97
+ };
98
+ };
99
+ //Add the input boolean to HASS
100
+ self.getHASSEntities([{ property: "entity_id", logic: "is", value: entityId }], (entities) => {
101
+ if (entities.length > 1) {
102
+ self.error(`Found more than 1 entity for ${entityId}`);
103
+ return;
104
+ }
105
+ const previousState = entities.length == 1 ? entities[0].state : "unknown";
106
+ self.addHASSEntity(entityId, data(options.state || previousState), options.creationCallback ? (response) => {
107
+ options.creationCallback(response.payload.state, response);
108
+ } : undefined);
109
+ });
110
+ //Assign the changed callback if set
111
+ if (options.changedCallback) {
112
+ self.hassEventCallServiceCallbacks[entityId] = function (domain, service, serviceData) {
113
+ if (domain == "input_boolean" && (serviceData === null || serviceData === void 0 ? void 0 : serviceData.entity_id) == entityId) {
114
+ let state = currentState;
115
+ if (service == "turn_on") {
116
+ state = "on";
117
+ }
118
+ else if (service == "turn_off") {
119
+ state = "off";
120
+ }
121
+ else if (service == "toggle") {
122
+ state = state === "on" ? "off" : "on";
123
+ }
124
+ currentState = state;
125
+ //Tell HASS that the button was pressed
126
+ self.sendHASSAPI("http", "post", "/api/states/" + entityId, undefined, undefined, data(currentState));
127
+ options.changedCallback(currentState, serviceData);
128
+ }
129
+ };
130
+ }
131
+ };
132
+ self.addHASSButton = function (options) {
133
+ var _a;
134
+ const entityId = (_a = options.id) !== null && _a !== void 0 ? _a : (0, utility_1.getEntityId)("button", options.friendlyName);
135
+ const data = (state) => {
136
+ return {
137
+ state: state || "unknown",
138
+ attributes: {
139
+ friendly_name: options.friendlyName
140
+ }
141
+ };
142
+ };
143
+ //Add the button to HASS
144
+ self.getHASSEntities([{ property: "entity_id", logic: "is", value: entityId }], (entities) => {
145
+ if (entities.length > 1) {
146
+ self.error(`Found more than 1 entity for ${entityId}`);
147
+ return;
148
+ }
149
+ const previousState = entities.length == 1 ? entities[0].state : "unknown";
150
+ self.addHASSEntity(entityId, data(options.state || previousState), options.creationCallback ? (response) => {
151
+ options.creationCallback(response.payload.state, response);
152
+ } : undefined);
153
+ });
154
+ //Assign the pressed callback if set
155
+ if (options.pressedCallback) {
156
+ self.hassEventCallServiceCallbacks[entityId] = function (domain, service, serviceData) {
157
+ if (domain == "button" && service == "press" && (serviceData === null || serviceData === void 0 ? void 0 : serviceData.entity_id) == entityId) {
158
+ const state = new Date();
159
+ //Tell HASS that the button was pressed
160
+ self.sendHASSAPI("http", "post", "/api/states/" + entityId, undefined, undefined, data(state));
161
+ options.pressedCallback(state, serviceData);
162
+ }
163
+ };
164
+ }
165
+ };
166
+ self.addHASSScene = function (options) {
167
+ const entityId = options.id || (0, utility_1.getEntityId)("scene", options.friendlyName);
168
+ const data = (state) => {
169
+ return {
170
+ state: state || "unknown",
171
+ attributes: {
172
+ friendly_name: options.friendlyName
173
+ }
174
+ };
175
+ };
176
+ //Add the scene to HASS
177
+ self.getHASSEntities([{ property: "entity_id", logic: "is", value: entityId }], (entities) => {
178
+ if (entities.length > 1) {
179
+ self.error(`Found more than 1 entity for ${entityId}`);
180
+ return;
181
+ }
182
+ const previousState = entities.length == 1 ? entities[0].state : "unknown";
183
+ self.addHASSEntity(entityId, data(options.state || previousState), options.creationCallback ? (response) => {
184
+ options.creationCallback(response.payload.state, response);
185
+ } : undefined);
186
+ });
187
+ //Assign the activated callback if set
188
+ if (options.activatedCallback) {
189
+ self.hassEventCallServiceCallbacks[entityId] = function (domain, service, serviceData) {
190
+ if (domain == "scene" && service == "turn_on") {
191
+ const entities = typeof serviceData.entity_id == "string" ? [serviceData.entity_id] : serviceData.entity_id;
192
+ if (!entities.includes(entityId)) {
193
+ return;
194
+ }
195
+ const state = new Date();
196
+ //Tell HASS that the scene was activated
197
+ self.sendHASSAPI("http", "post", "/api/states/" + entityId, undefined, undefined, data(state));
198
+ options.activatedCallback(state, serviceData);
199
+ }
200
+ };
201
+ }
202
+ };
203
+ self.addHASSSelect = function (options) {
204
+ const entityId = options.id || (0, utility_1.getEntityId)("select", options.friendlyName);
205
+ const data = (state) => {
206
+ return {
207
+ state: state || "unknown",
208
+ attributes: {
209
+ friendly_name: options.friendlyName,
210
+ options: options.options
211
+ }
212
+ };
213
+ };
214
+ //Add the select to HASS
215
+ self.getHASSEntities([{ property: "entity_id", logic: "is", value: entityId }], (entities) => {
216
+ if (entities.length > 1) {
217
+ self.error(`Found more than 1 entity for ${entityId}`);
218
+ return;
219
+ }
220
+ const previousState = entities.length == 1 ? entities[0].state : "unknown";
221
+ self.addHASSEntity(entityId, data(options.state || previousState), options.creationCallback ? (response) => {
222
+ options.creationCallback(response.payload.state, response);
223
+ } : undefined);
224
+ });
225
+ //Assign the activated callback if set
226
+ if (options.activatedCallback) {
227
+ self.hassEventCallServiceCallbacks[entityId] = function (domain, service, serviceData) {
228
+ if (domain == "select") {
229
+ const entities = typeof serviceData.entity_id == "string" ? [serviceData.entity_id] : serviceData.entity_id;
230
+ if (!entities.includes(entityId)) {
231
+ return;
232
+ }
233
+ if (service == "select_option") {
234
+ self.sendHASSAPI("http", "post", "/api/states/" + entityId, undefined, undefined, data(serviceData.option));
235
+ options.activatedCallback(serviceData.option, serviceData);
236
+ }
237
+ else if (service == "select_first") {
238
+ self.sendHASSAPI("http", "post", "/api/states/" + entityId, undefined, undefined, data(options.options[0] || "unknown"));
239
+ options.activatedCallback(options.options[0] || "unknown", serviceData);
240
+ }
241
+ else if (service == "select_last") {
242
+ self.sendHASSAPI("http", "post", "/api/states/" + entityId, undefined, undefined, data(options.options[options.options.length - 1] || "unknown"));
243
+ options.activatedCallback(options.options[options.options.length - 1] || "unknown", serviceData);
244
+ }
245
+ else if (service == "select_previous" || service == "select_next") {
246
+ self.getHASSEntityState(entityId, (currentState) => {
247
+ let index = options.options.indexOf(currentState);
248
+ if (service == "select_previous") {
249
+ index = index - 1;
250
+ if (index < 0 && serviceData.cycle) {
251
+ index = options.options.length - 1;
252
+ }
253
+ if (index < 0 && !serviceData.cycle) {
254
+ index = 0;
255
+ }
256
+ }
257
+ else if (service == "select_next") {
258
+ index = index + 1;
259
+ if (index >= options.options.length && serviceData.cycle) {
260
+ index = 0;
261
+ }
262
+ if (index >= options.options.length && !serviceData.cycle) {
263
+ index = options.options.length - 1;
264
+ }
265
+ }
266
+ self.sendHASSAPI("http", "post", "/api/states/" + entityId, undefined, undefined, data(options.options[index] || "unknown"));
267
+ options.activatedCallback(options.options[index] || "unknown", serviceData);
268
+ });
269
+ }
270
+ }
271
+ };
272
+ }
273
+ };
274
+ self.handleCallback = function (callbacks, callbackId, ...args) {
275
+ var _a;
276
+ //This is a specific callback id
277
+ if (callbackId) {
278
+ (_a = callbacks[callbackId]) === null || _a === void 0 ? void 0 : _a.call(callbacks, ...args);
279
+ delete callbacks[callbackId];
280
+ return;
281
+ }
282
+ //Send to everyone
283
+ for (const id of Object.keys(callbacks)) {
284
+ if (callbacks[id]) {
285
+ callbacks[id](...args);
286
+ }
287
+ }
288
+ };
289
+ self.msgReceived = function (msg, senderIds) {
290
+ const payload = msg.payload;
291
+ //Handle based on the topic
292
+ switch (msg.topic) {
293
+ case connectionsTypes_1.Topics.API: {
294
+ self.handleCallback(self.hassAPICallbacks, msg.callbackId, msg);
295
+ break;
296
+ }
297
+ case connectionsTypes_1.Topics.EVENT: {
298
+ if (msg.event_type == "home_assistant_client" && msg.payload == "ready") {
299
+ self.handleCallback(self.hassEventReadyCallbacks, msg.callbackId, msg);
300
+ }
301
+ if (payload) {
302
+ switch (payload.event_type) {
303
+ case "call_service":
304
+ self.handleCallback(self.hassEventCallServiceCallbacks, msg.callbackId, payload.event.domain, payload.event.service, payload.event.service_data);
305
+ break;
306
+ case "state_changed":
307
+ self.handleCallback(self.hassEventStateChangeCallbacks, msg.callbackId, payload.event.entity_id, payload.event.old_state, payload.event.new_state);
308
+ break;
309
+ }
310
+ }
311
+ self.handleCallback(self.hassEventCallbacks, msg.callbackId, msg);
312
+ break;
313
+ }
314
+ case connectionsTypes_1.Topics.ACTION: {
315
+ self.handleCallback(self.hassActionCallbacks, msg.callbackId, payload.action, payload.target, payload.data);
316
+ break;
317
+ }
318
+ case connectionsTypes_1.Topics.STATE: {
319
+ self.handleCallback(self.hassCurrentStateCallbacks, msg.callbackId, payload, msg);
320
+ break;
321
+ }
322
+ case connectionsTypes_1.Topics.GET_ENTITIES: {
323
+ self.handleCallback(self.hassGetEntitiesCallbacks, msg.callbackId, payload);
324
+ break;
325
+ }
326
+ }
327
+ };
328
+ };
329
+ RED.nodes.registerType("connections-config-node", register);
330
+ };
@@ -0,0 +1,30 @@
1
+ <script type="text/javascript" id="node-connections-node">
2
+ RED.nodes.registerType("connections-node", {
3
+ category: "HASS Stuff",
4
+ color: "#ffcc00",
5
+ inputs: 1,
6
+ outputs: 1,
7
+ icon: "bulb.svg",
8
+ defaults: {
9
+ name: { value: "" },
10
+ connectionsConfigNode: { value: '', type: "connections-config-node", required: true }
11
+ },
12
+ label: function () {
13
+ return this.name || "Connections"
14
+ }
15
+ });
16
+ </script>
17
+
18
+
19
+ <script type="text/html" data-template-name="connections-node">
20
+ <div class="form-row">
21
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
22
+ <input type="text" id="node-input-name" placeholder="Name">
23
+ </div>
24
+ <div class="form-row">
25
+ <label for="node-input-connectionsConfigNode">Config</label>
26
+ <input type="text" id="node-input-connectionsConfigNode" />
27
+ </div>
28
+ </script>
29
+ <script type="text/html" data-help-name="connections-node">
30
+ </script>
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ const baseNode_1 = require("../baseNode");
3
+ module.exports = function ConnectionsNode(RED) {
4
+ function register(config) {
5
+ RED.nodes.createNode(this, config);
6
+ (0, baseNode_1.assignBaseNode)(this);
7
+ const connectionsConfigNode = RED.nodes.getNode(config.connectionsConfigNode);
8
+ //When a config node wants to send a message to the output
9
+ connectionsConfigNode.addMsgCallback(this.id, (msg) => {
10
+ this.send(msg);
11
+ });
12
+ this.on("input", (msg, send, done) => {
13
+ connectionsConfigNode.msgReceived(msg, [this.id]);
14
+ });
15
+ }
16
+ RED.nodes.registerType("connections-node", register);
17
+ };
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Topics = void 0;
4
+ var Topics;
5
+ (function (Topics) {
6
+ Topics["API"] = "api";
7
+ Topics["EVENT"] = "event";
8
+ Topics["STATE"] = "state";
9
+ Topics["ACTION"] = "action";
10
+ Topics["GET_ENTITIES"] = "get_entities";
11
+ })(Topics = exports.Topics || (exports.Topics = {}));
@@ -0,0 +1,109 @@
1
+ <script type="text/javascript" id="node-light-control-config-node">
2
+ RED.nodes.registerType("light-control-config-node", {
3
+ category: "config",
4
+ defaults: {
5
+ name: { value: "" },
6
+ connectionsConfigNode: { value: "", type: "connections-config-node", required: true },
7
+ groupEntityId: { value: "", required: true, default: "light.example" },
8
+ nightModeEntityId: { value: "", required: false, default: "input_boolean.example" },
9
+ entitiesOffAtNight: { value: "", required: false, default: "light.example1,light.example2" },
10
+ concentrateSettings: { value: "", required: true, default: "concentrate 100" },
11
+ readSettings: { value: "", required: true, default: "read 100" },
12
+ relaxSettings: { value: "", required: true, default: "relax 100" },
13
+ restSettings: { value: "", required: true, default: "rest 100" },
14
+ nightLightSettings: { value: "", required: true, default: "nightLight 100" },
15
+ nightSettings: { value: "", required: true, default: "nightLight 30" },
16
+ },
17
+ label: function () {
18
+ return this.name || "Light Control Config"
19
+ }
20
+ });
21
+ </script>
22
+
23
+ <script type="text/html" data-template-name="light-control-config-node">
24
+ <div class="form-row">
25
+ <h2>Basic configuration</h2>
26
+ </div>
27
+ <div class="form-row">
28
+ <label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
29
+ <input type="text" id="node-config-input-name" placeholder="Name">
30
+ </div>
31
+ <div class="form-row">
32
+ <label for="node-config-input-connectionsConfigNode">Config</label>
33
+ <input type="text" id="node-config-input-connectionsConfigNode" />
34
+ </div>
35
+ <div class="form-row">
36
+ <label style="font-weight: bold;" for="node-config-input-groupEntityId">Group Entity ID</label>
37
+ <p>The entity ID of the light group to control.</p>
38
+ <input type="text" id="node-config-input-groupEntityId" />
39
+ </div>
40
+ <div class="form-row">
41
+ <label style="font-weight: bold;" for="node-config-input-nightModeEntityId">Night Mode Entity ID</label>
42
+ <p>The entity ID of the input_boolean that controls night mode. Empty to disable</p>
43
+ <input type="text" id="node-config-input-nightModeEntityId" />
44
+ </div>
45
+ <div class="form-row">
46
+ <label style="font-weight: bold;" for="node-config-input-entitiesOffAtNight">Entities Off At Night</label>
47
+ <p>Comma-separated list of entities to turn off during night mode. Empty to disable</p>
48
+ <input type="text" id="node-config-input-entitiesOffAtNight" />
49
+ </div>
50
+ <div class="form-row">
51
+ <hr>
52
+ <h2>Scene definitions</h2>
53
+ <p>
54
+ The following settings define what the scenes should do. They follow the syntax of [scene] [brightnessPercent]
55
+ where scene is the scene name, if the same as below values will use the
56
+ <a style="font-weight: bold;" href="https://github.com/basnijholt/adaptive-lighting">Adaptive Lighting HACS plugin</a>
57
+ otherwise will send to scene.apply. The brightnessPercent is the brightness percentage to set
58
+ </p>
59
+ <ul>
60
+ <li>rest</li>
61
+ <li>relax</li>
62
+ <li>concentrate</li>
63
+ <li>energize</li>
64
+ <li>coolBright</li>
65
+ <li>bright</li>
66
+ <li>dimmed</li>
67
+ <li>nightLight</li>
68
+ <li>sleepy</li>
69
+ <li>paintedSky</li>
70
+ <li>rollingHills</li>
71
+ <li>sailingAway</li>
72
+ <li>sunFlare</li>
73
+ </ul>
74
+ </div>
75
+
76
+ <div class="form-row">
77
+ <label style="font-weight: bold;" for="node-config-input-concentrateSettings">Concentrate Settings</label>
78
+ <p>The scene to use for the concentrate scene [scene] [brightnessPercent]</p>
79
+ <input type="text" id="node-config-input-concentrateSettings" />
80
+ </div>
81
+ <div class="form-row">
82
+ <label style="font-weight: bold;" for="node-config-input-readSettings">Read Settings</label>
83
+ <p>The scene to use for the read scene [scene] [brightnessPercent]</p>
84
+ <input type="text" id="node-config-input-readSettings" />
85
+ </div>
86
+ <div class="form-row">
87
+ <label style="font-weight: bold;" for="node-config-input-relaxSettings">Relax Settings</label>
88
+ <p>The scene to use for the relax scene [scene] [brightnessPercent]</p>
89
+ <input type="text" id="node-config-input-relaxSettings" />
90
+ </div>
91
+ <div class="form-row">
92
+ <label style="font-weight: bold;" for="node-config-input-restSettings">Rest Settings</label>
93
+ <p>The scene to use for the rest scene [scene] [brightnessPercent]</p>
94
+ <input type="text" id="node-config-input-restSettings" />
95
+ </div>
96
+ <div class="form-row">
97
+ <label style="font-weight: bold;" for="node-config-input-nightLightSettings">Night Light Settings</label>
98
+ <p>The scene to use for the night light scene [scene] [brightnessPercent]</p>
99
+ <input type="text" id="node-config-input-nightLightSettings" />
100
+ </div>
101
+ <div class="form-row">
102
+ <label style="font-weight: bold;" for="node-config-input-nightSettings">Night Settings</label>
103
+ <p>The scene to use for the night light scene [scene] [brightnessPercent]</p>
104
+ <input type="text" id="node-config-input-nightSettings" />
105
+ </div>
106
+ </script>
107
+
108
+ <script type="text/html" data-help-name="light-control-config-node">
109
+ </script>