node-red-contrib-alice 2.2.4 → 2.3.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.

Potentially problematic release.


This version of node-red-contrib-alice might be problematic. Click here for more details.

Files changed (43) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/CLAUDE.md +54 -0
  3. package/nodes/alice-color.js +208 -231
  4. package/nodes/alice-device.html +6 -1
  5. package/nodes/alice-device.js +252 -286
  6. package/nodes/alice-event.js +110 -114
  7. package/nodes/alice-get.html +91 -0
  8. package/nodes/alice-get.js +9 -0
  9. package/nodes/alice-mode.js +136 -145
  10. package/nodes/alice-onoff.js +126 -130
  11. package/nodes/alice-range.js +144 -150
  12. package/nodes/alice-sensor.html +0 -2
  13. package/nodes/alice-sensor.js +101 -106
  14. package/nodes/alice-togle.js +118 -125
  15. package/nodes/alice-video.js +88 -132
  16. package/nodes/alice.js +127 -122
  17. package/nodes/types.js +3 -0
  18. package/package.json +22 -8
  19. package/src/alice-color.html +255 -0
  20. package/src/alice-color.ts +227 -0
  21. package/src/alice-device.html +94 -0
  22. package/src/alice-device.ts +301 -0
  23. package/src/alice-event.html +148 -0
  24. package/src/alice-event.ts +112 -0
  25. package/src/alice-get.html +67 -6
  26. package/src/alice-get.ts +12 -15
  27. package/src/alice-mode.html +296 -0
  28. package/src/alice-mode.ts +139 -0
  29. package/src/alice-onoff.html +93 -0
  30. package/src/alice-onoff.ts +132 -0
  31. package/src/alice-range.html +293 -0
  32. package/src/alice-range.ts +144 -0
  33. package/src/alice-sensor.html +307 -0
  34. package/src/alice-sensor.ts +103 -0
  35. package/src/alice-togle.html +96 -0
  36. package/src/alice-togle.ts +122 -0
  37. package/src/alice-video.html +90 -0
  38. package/src/alice-video.ts +99 -0
  39. package/src/alice.html +242 -0
  40. package/src/alice.ts +146 -0
  41. package/src/types.ts +157 -0
  42. package/tsconfig.json +13 -106
  43. package/.eslintrc.json +0 -20
@@ -0,0 +1,293 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('Range',{
3
+ category: 'alice',
4
+ defaults:{
5
+ device: {value:"", type:"alice-device"},
6
+ name: {value:""},
7
+ instance: {value:undefined, validate: (v)=>{
8
+ if (v){
9
+ return true;
10
+ }else{
11
+ return false;
12
+ }
13
+ }},
14
+ unit: {value:""},
15
+ min:{value:1, validate: (v)=>{
16
+ if (v){
17
+ return true;
18
+ }else{
19
+ return false;
20
+ }
21
+ }},
22
+ max:{value:100, validate: (v)=>{
23
+ if (v){
24
+ return true;
25
+ }else{
26
+ return false;
27
+ }
28
+ }},
29
+ precision:{value: 1, validate: (v)=>{
30
+ if (v){
31
+ return true;
32
+ }else{
33
+ return false;
34
+ }
35
+ }},
36
+ response:{value:true},
37
+ retrievable: {value: true}
38
+ },
39
+ inputs:1,
40
+ outputs:1,
41
+ icon: "alice.png",
42
+ color: "#D8BFD8",
43
+ label: function(){
44
+ return this.name + ":"+this.instance;
45
+ },
46
+ oneditsave: function(){
47
+ deivcename = $('#node-input-device option:selected').text();
48
+ $('#node-input-name').val(deivcename);
49
+ },
50
+ oneditprepare: function(){
51
+ var firstRun = true;
52
+ function instanceChange(inst){
53
+ var unit, min, max, precision;
54
+ // проверяем первый ли это запуск редактирования или он уже был настроен
55
+ if (firstRun && typeof inst !== "undefined"){
56
+ unit = $('#node-input-unit').find(":selected").val();
57
+ min = $('#node-input-min').val();
58
+ max = $('#node-input-max').val();
59
+ precision = $('#node-input-precision').val();
60
+ };
61
+ switch (inst) {
62
+ case 'brightness':
63
+ min = (typeof min !== "undefined") ? min : 0;
64
+ max = (typeof max !== "undefined") ? max : 100;
65
+ precision = (typeof precision !== "undefined") ? precision : 10;
66
+ $('#node-input-unit').replaceWith('<select id="node-input-unit" style="width: 70%;"></select>');
67
+ $('#node-input-unit').append('<option value="unit.percent">%</option>');
68
+ $('#node-input-unit select').val("unit.percent");
69
+ $('#node-input-unit').prop( "disabled", true );
70
+ $('#node-input-min').val(min);
71
+ $('#node-input-min').prop( "disabled", true );
72
+ $('#node-input-max').val(max);
73
+ $('#node-input-max').prop( "disabled", true );
74
+ $('#node-input-precision').val(precision);
75
+ $('#node-input-precision').prop( "disabled", false );
76
+ break;
77
+ case 'channel':
78
+ min = (typeof min !== "undefined") ? min : 1;
79
+ max = (typeof max !== "undefined") ? max : 1000;
80
+ precision = (typeof precision !== "undefined") ? precision : 1;
81
+ $('#node-input-unit').replaceWith('<select id="node-input-unit" style="width: 70%;"></select>');
82
+ $('#node-input-unit').append('<option value="unit.number">Number</option>');
83
+ $('#node-input-unit select').val("unit.number");
84
+ $('#node-input-unit').prop( "disabled", true );
85
+ $('#node-input-min').val(min);
86
+ $('#node-input-min').prop( "disabled", false );
87
+ $('#node-input-max').val(max);
88
+ $('#node-input-max').prop( "disabled", false );
89
+ $('#node-input-precision').val(precision);
90
+ $('#node-input-precision').prop( "disabled", false );
91
+ break;
92
+ case 'humidity':
93
+ min = (typeof min !== "undefined") ? min : 0;
94
+ max = (typeof max !== "undefined") ? max : 100;
95
+ precision = (typeof precision !== "undefined") ? precision : 10;
96
+
97
+ $('#node-input-unit').replaceWith('<select id="node-input-unit" style="width: 70%;"></select>');
98
+ $('#node-input-unit').append('<option value="unit.percent">%</option>');
99
+ $('#node-input-unit select').val("unit.percent");
100
+ $('#node-input-unit').prop( "disabled", true );
101
+ $('#node-input-min').val(min);
102
+ $('#node-input-min').prop( "disabled", true );
103
+ $('#node-input-max').val(max);
104
+ $('#node-input-max').prop( "disabled", true );
105
+ $('#node-input-precision').val(precision);
106
+ $('#node-input-precision').prop( "disabled", false );
107
+ break;
108
+ case 'open':
109
+ min = (typeof min !== "undefined") ? min : 0;
110
+ max = (typeof max !== "undefined") ? max : 100;
111
+ precision = (typeof precision !== "undefined") ? precision : 10;
112
+
113
+ $('#node-input-unit').replaceWith('<select id="node-input-unit" style="width: 70%;"></select>');
114
+ $('#node-input-unit').append('<option value="unit.percent">%</option>');
115
+ $('#node-input-unit select').val("unit.percent");
116
+ $('#node-input-unit').prop( "disabled", true );
117
+ $('#node-input-min').val(min);
118
+ $('#node-input-min').prop( "disabled", true );
119
+ $('#node-input-max').val(max);
120
+ $('#node-input-max').prop( "disabled", true );
121
+ $('#node-input-precision').val(precision);
122
+ $('#node-input-precision').prop( "disabled", false );
123
+ break;
124
+ case 'temperature':
125
+ unit = (typeof unit !== "unit") ? unit : "unit.temperature.celsius";
126
+ min = (typeof min !== "undefined") ? min : 0;
127
+ max = (typeof max !== "undefined") ? max : 100;
128
+ precision = (typeof precision !== "undefined") ? precision : 2;
129
+
130
+ $('#node-input-unit').replaceWith('<select id="node-input-unit" style="width: 70%;"></select>');
131
+ $('#node-input-unit').append('<option value="unit.temperature.celsius">Celsius (C)</option>');
132
+ $('#node-input-unit').append('<option value="unit.temperature.kelvin">Kelvin (K)</option>');
133
+ $('#node-input-unit select').val(unit);
134
+ $('#node-input-unit').prop( "disabled", false );
135
+ $('#node-input-min').val(min);
136
+ $('#node-input-min').prop( "disabled", false );
137
+ $('#node-input-max').val(max);
138
+ $('#node-input-max').prop( "disabled", false );
139
+ $('#node-input-precision').val(precision);
140
+ $('#node-input-precision').prop( "disabled", false );
141
+ break;
142
+ case 'volume':
143
+ min = (typeof min !== "undefined") ? min : 0;
144
+ max = (typeof max !== "undefined") ? max : 100;
145
+ precision = (typeof precision !== "undefined") ? precision : 10;
146
+
147
+ $('#node-input-unit').replaceWith('<select id="node-input-unit" style="width: 70%;"></select>');
148
+ $('#node-input-unit').append('<option value="unit.number">Number</option>');
149
+ $('#node-input-unit select').val("unit.number");
150
+ $('#node-input-unit').prop( "disabled", true );
151
+ $('#node-input-min').val(min);
152
+ $('#node-input-min').prop( "disabled", false );
153
+ $('#node-input-max').val(max);
154
+ $('#node-input-max').prop( "disabled", false );
155
+ $('#node-input-precision').val(precision);
156
+ $('#node-input-precision').prop( "disabled", false );
157
+ break;
158
+ default:
159
+ $('#node-input-unit').prop( "disabled", true );
160
+ $('#node-input-min').prop( "disabled", true );
161
+ $('#node-input-max').prop( "disabled", true );
162
+ $('#node-input-precision').prop( "disabled", true );
163
+ }
164
+ firstRun = false;
165
+ };
166
+ $('#node-input-instance').on('change',()=>{
167
+ var val = $('#node-input-instance').find(":selected").val();
168
+ instanceChange(val);
169
+ });
170
+ if (this.retrievable === undefined){
171
+ $( "#node-input-retrievable").prop('checked', true);
172
+ }
173
+ if (this.response === undefined){
174
+ $( "#node-input-response").prop('checked', true);
175
+ }
176
+ }
177
+ })
178
+ </script>
179
+
180
+ <script type="text/x-red" data-template-name="Range">
181
+ <input type="hidden" id="node-input-name">
182
+ <div class="form-row">
183
+ <label for="node-input-device">Device</label>
184
+ <input id="node-input-device">
185
+ </div>
186
+ <div class="form-row">
187
+ <label for="node-input-instance">Range Type</label>
188
+ <select id="node-input-instance" style="width: 70%;">
189
+ <option value="brightness">Brightness</option>
190
+ <option value="channel">Channel</option>
191
+ <option value="humidity">Humidity</option>
192
+ <option value="open">Open</option>
193
+ <option value="temperature">Temperature</option>
194
+ <option value="volume">Volume</option>
195
+ </select>
196
+ </div>
197
+ <div class="form-row">
198
+ <label for="node-input-unit">Unit</label>
199
+ <select id="node-input-unit" style="width: 70%;">
200
+ <option value="unit.temperature.celsius">Celsius</option>
201
+ <option value="unit.temperature.kelvin">Kelvin</option>
202
+ <option value="unit.percent">Percent</option>
203
+ </select>
204
+ </div>
205
+ <div class="form-row">
206
+ <label for="node-input-min">Min</label>
207
+ <input type="number" id="node-input-min">
208
+ </div>
209
+ <div class="form-row">
210
+ <label for="node-input-max">Max</label>
211
+ <input type="number" id="node-input-max">
212
+ </div>
213
+ <div class="form-row">
214
+ <label for="node-input-precision">Step</label>
215
+ <input type="number" id="node-input-precision">
216
+ </div>
217
+ <div class="form-row">
218
+ <label for="node-input-retrievable"><i class="fa fa-sign-out"></i> <span >Output</span></label>
219
+ <label for="node-input-retrievable" style="width:70%">
220
+ <input type="checkbox" id="node-input-retrievable" style="display:inline-block; width:22px; vertical-align:baseline;" autocomplete="off"><span>full value/changes only</span>
221
+ </label>
222
+ </div>
223
+ <div class="form-row">
224
+ <label for="node-input-response"><i class="fa fa-refresh"></i> <span >Response</span></label>
225
+ <label for="node-input-response" style="width:70%">
226
+ <input type="checkbox" id="node-input-response" style="display:inline-block; width:22px; vertical-align:baseline;" autocomplete="off"><span>Always answer Alice with success</span>
227
+ </label>
228
+ </div>
229
+
230
+ </script>
231
+
232
+ <script type="text/x-red" data-help-name="Range">
233
+ <p>Manage device settings that have a range. For example, lamp brightness, sound volume, heater temperature, etc.</p>
234
+
235
+ <h3>Property</h3>
236
+ <dl class="message-properties">
237
+ <dt>Device
238
+ <span class="property-type">Select</span>
239
+ </dt>
240
+ <dd> The device to which this feature is connected </dd>
241
+ <dt>Range Type
242
+ <span class="property-type">Select</span>
243
+ </dt>
244
+ <dd> Selection of the type of ranges (brightness, sound, channel, temperature, etc.) </dd>
245
+ <dt>Unit
246
+ <span class="property-type">Select</span>
247
+ </dt>
248
+ <dd> Unit of measure for the range </dd>
249
+ <dt>Min
250
+ <span class="property-type">Float</span>
251
+ </dt>
252
+ <dd> Minimum value </dd>
253
+ <dt>Max
254
+ <span class="property-type">Float</span>
255
+ </dt>
256
+ <dd> Maximum value </dd>
257
+ <dt>precision
258
+ <span class="property-type">Float</span>
259
+ </dt>
260
+ <dd> The minimum step of changing values within the range </dd>
261
+ <dt>Output
262
+ <span class="property-type">checkbox</span>
263
+ </dt>
264
+ <dd> Determines what kind of value will be output. The full value that the device should set or the delta of the value change.
265
+ When the checkbox is unchecked, the current device values are not stored in Yandex.. </dd>
266
+ <dt>Response
267
+ <span class="property-type">checkbox</span>
268
+ </dt>
269
+ <dd> In order for the device to respond to Alice that the command was successful, the corresponding value should arrive at the input within 2.5 seconds.
270
+ If your device takes longer or doesn’t return a confirmation at all, just check this box. </dd>
271
+ </dl>
272
+ </dl>
273
+
274
+ <h3>Inputs</h3>
275
+ <dl class="message-properties">
276
+ <dt>payload
277
+ <span class="property-type">Float</span>
278
+ </dt>
279
+ <dd> Value </dd>
280
+ </dl>
281
+
282
+ <h3>Outputs</h3>
283
+ <dl class="message-properties">
284
+ <dt>payload
285
+ <span class="property-type">Float</span>
286
+ </dt>
287
+ <dd> Value </dd>
288
+ </dl>
289
+ <h3>References</h3>
290
+ <ul>
291
+ <li><a href="https://yandex.ru/dev/dialogs/alice/doc/smart-home/concepts/range-docpage/"> Yandex documentation</a></li>
292
+ </ul>
293
+ </script>
@@ -0,0 +1,144 @@
1
+ import { NodeAPI, Node } from "node-red";
2
+ import { AliceRangeConfig, AliceDeviceNode, CapabilityState } from "./types.js";
3
+
4
+ export = (RED: NodeAPI): void => {
5
+ function AliceRange(this: Node, config: AliceRangeConfig): void {
6
+ RED.nodes.createNode(this, config);
7
+ const device = RED.nodes.getNode(config.device) as AliceDeviceNode;
8
+ device.setMaxListeners(device.getMaxListeners() + 1);
9
+
10
+ const ctype = 'devices.capabilities.range';
11
+ const retrievable = config.retrievable;
12
+ const instance = config.instance || '';
13
+ const unit = config.unit;
14
+ const random_access = true;
15
+ let min = parseFloat(config.min);
16
+ let max = parseFloat(config.max);
17
+ let precision = parseFloat(config.precision);
18
+ let response = config.response;
19
+ let value: number | null = null;
20
+
21
+ if (config.response === undefined) {
22
+ response = true;
23
+ }
24
+ if (typeof min != 'number' || isNaN(min)) { min = 0; }
25
+ if (typeof max != 'number' || isNaN(max)) { max = 100; }
26
+ if (typeof precision != 'number' || isNaN(precision)) { precision = 1; }
27
+
28
+ this.status({ fill: "red", shape: "dot", text: "offline" });
29
+
30
+ const init = (): void => {
31
+ const parameters: Record<string, any> = {
32
+ instance: instance,
33
+ unit: unit,
34
+ random_access: random_access,
35
+ range: { min, max, precision }
36
+ };
37
+ // если unit не применим к параметру, то нужно удалить
38
+ if (unit == "unit.number") {
39
+ delete parameters.unit;
40
+ }
41
+
42
+ device.setCapability(this.id, {
43
+ type: ctype,
44
+ retrievable: retrievable,
45
+ reportable: true,
46
+ parameters: parameters
47
+ })
48
+ .then(() => {
49
+ this.status({ fill: "green", shape: "dot", text: "online" });
50
+ })
51
+ .catch(err => {
52
+ this.error("Error on create capability: " + err.message);
53
+ this.status({ fill: "red", shape: "dot", text: "error" });
54
+ });
55
+ };
56
+
57
+ if (device.initState) init();
58
+
59
+ device.on("online", () => {
60
+ init();
61
+ });
62
+
63
+ device.on("offline", () => {
64
+ this.status({ fill: "red", shape: "dot", text: "offline" });
65
+ });
66
+
67
+ device.on(this.id, (val: number, fullstate: { relative?: boolean }) => {
68
+ let newValue = val;
69
+ //проверка является ли значение относительным и нужно ли отдавать полное значение
70
+ if (fullstate.relative && retrievable && value !== null) {
71
+ newValue = value + val;
72
+ if (val < 0 && newValue < min) newValue = min;
73
+ if (val > 0 && newValue > max) newValue = max;
74
+ }
75
+ this.send({ payload: newValue });
76
+ const state: CapabilityState = {
77
+ type: ctype,
78
+ state: {
79
+ instance: instance,
80
+ value: newValue
81
+ }
82
+ };
83
+ if (response) {
84
+ device.updateCapabState(this.id, state)
85
+ .then(() => {
86
+ value = newValue;
87
+ this.status({ fill: "green", shape: "dot", text: "online" });
88
+ })
89
+ .catch(err => {
90
+ this.error("Error on update capability state: " + err.message);
91
+ this.status({ fill: "red", shape: "dot", text: "Error" });
92
+ });
93
+ }
94
+ });
95
+
96
+ this.on('input', (msg, _send, done) => {
97
+ const newValue = msg.payload;
98
+ if (typeof newValue != 'number') {
99
+ this.error("Wrong type! msg.payload must be Number.");
100
+ if (done) { done(); }
101
+ return;
102
+ }
103
+ if (newValue === value) {
104
+ this.debug("Value not changed. Cancel update");
105
+ if (done) { done(); }
106
+ return;
107
+ }
108
+ const state: CapabilityState = {
109
+ type: ctype,
110
+ state: {
111
+ instance: instance,
112
+ value: newValue
113
+ }
114
+ };
115
+ device.updateCapabState(this.id, state)
116
+ .then(() => {
117
+ value = newValue;
118
+ this.status({ fill: "green", shape: "dot", text: String(newValue) });
119
+ if (done) { done(); }
120
+ })
121
+ .catch(err => {
122
+ this.error("Error on update capability state: " + err.message);
123
+ this.status({ fill: "red", shape: "dot", text: "Error" });
124
+ if (done) { done(); }
125
+ });
126
+ });
127
+
128
+ this.on('close', (removed: boolean, done: () => void) => {
129
+ device.setMaxListeners(device.getMaxListeners() - 1);
130
+ if (removed) {
131
+ device.delCapability(this.id)
132
+ .then(() => { done(); })
133
+ .catch(err => {
134
+ this.error("Error on delete capability: " + err.message);
135
+ done();
136
+ });
137
+ } else {
138
+ done();
139
+ }
140
+ });
141
+ }
142
+
143
+ RED.nodes.registerType("Range", AliceRange);
144
+ };