node-red-contrib-alice 2.2.4 → 2.3.2
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.
- package/.claude/settings.local.json +11 -0
- package/CLAUDE.md +54 -0
- package/nodes/alice-color.js +208 -231
- package/nodes/alice-device.html +6 -1
- package/nodes/alice-device.js +252 -286
- package/nodes/alice-event.js +110 -114
- package/nodes/alice-get.html +91 -0
- package/nodes/alice-get.js +9 -0
- package/nodes/alice-mode.js +136 -145
- package/nodes/alice-onoff.js +126 -130
- package/nodes/alice-range.js +144 -150
- package/nodes/alice-sensor.html +0 -2
- package/nodes/alice-sensor.js +101 -106
- package/nodes/alice-togle.js +118 -125
- package/nodes/alice-video.js +88 -132
- package/nodes/alice.js +127 -122
- package/nodes/types.js +3 -0
- package/package.json +22 -8
- package/src/alice-color.html +255 -0
- package/src/alice-color.ts +227 -0
- package/src/alice-device.html +94 -0
- package/src/alice-device.ts +301 -0
- package/src/alice-event.html +148 -0
- package/src/alice-event.ts +112 -0
- package/src/alice-get.html +67 -6
- package/src/alice-get.ts +12 -15
- package/src/alice-mode.html +296 -0
- package/src/alice-mode.ts +139 -0
- package/src/alice-onoff.html +93 -0
- package/src/alice-onoff.ts +132 -0
- package/src/alice-range.html +293 -0
- package/src/alice-range.ts +144 -0
- package/src/alice-sensor.html +307 -0
- package/src/alice-sensor.ts +103 -0
- package/src/alice-togle.html +96 -0
- package/src/alice-togle.ts +122 -0
- package/src/alice-video.html +90 -0
- package/src/alice-video.ts +99 -0
- package/src/alice.html +242 -0
- package/src/alice.ts +146 -0
- package/src/types.ts +157 -0
- package/tsconfig.json +13 -106
- package/.eslintrc.json +0 -20
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('Sensor',{
|
|
3
|
+
category: 'alice',
|
|
4
|
+
defaults:{
|
|
5
|
+
device: {value:"", type:"alice-device"},
|
|
6
|
+
name: {value:"Sensor"},
|
|
7
|
+
stype: {value:'devices.properties.float'},
|
|
8
|
+
instance: {value:undefined, validate: (v)=>{
|
|
9
|
+
if (v){
|
|
10
|
+
return true;
|
|
11
|
+
}else{
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}},
|
|
15
|
+
unit:{value:undefined}
|
|
16
|
+
},
|
|
17
|
+
inputs:1,
|
|
18
|
+
outputs:0,
|
|
19
|
+
icon: "alice.png",
|
|
20
|
+
color: "#D8BFD8",
|
|
21
|
+
label: function(){
|
|
22
|
+
return this.name;
|
|
23
|
+
},
|
|
24
|
+
oneditprepare: function(){
|
|
25
|
+
$('#node-input-instance').on('change',()=>{
|
|
26
|
+
var val = $('#node-input-instance').find(":selected").val();
|
|
27
|
+
switch (val) {
|
|
28
|
+
case 'amperage':
|
|
29
|
+
$('#node-input-unit')
|
|
30
|
+
.find('option')
|
|
31
|
+
.remove()
|
|
32
|
+
.end()
|
|
33
|
+
.append('<option value="unit.ampere">Ampere (A)</option>')
|
|
34
|
+
.val('unit.ampere')
|
|
35
|
+
.prop('disabled', 'disabled');
|
|
36
|
+
break;
|
|
37
|
+
case 'battery_level':
|
|
38
|
+
$('#node-input-unit')
|
|
39
|
+
.find('option')
|
|
40
|
+
.remove()
|
|
41
|
+
.end()
|
|
42
|
+
.append('<option value="unit.percent">Percent (%)</option>')
|
|
43
|
+
.val('unit.percent')
|
|
44
|
+
.prop('disabled', 'disabled');
|
|
45
|
+
break;
|
|
46
|
+
case 'co2_level':
|
|
47
|
+
$('#node-input-unit')
|
|
48
|
+
.find('option')
|
|
49
|
+
.remove()
|
|
50
|
+
.end()
|
|
51
|
+
.append('<option value="unit.ppm">Parts per million (ppm)</option>')
|
|
52
|
+
.val('unit.ppm')
|
|
53
|
+
.prop('disabled', 'disabled');
|
|
54
|
+
break;
|
|
55
|
+
case 'food_level':
|
|
56
|
+
$('#node-input-unit')
|
|
57
|
+
.find('option')
|
|
58
|
+
.remove()
|
|
59
|
+
.end()
|
|
60
|
+
.append('<option value="unit.percent">Percent (%)</option>')
|
|
61
|
+
.val('unit.percent')
|
|
62
|
+
.prop('disabled', 'disabled');
|
|
63
|
+
break;
|
|
64
|
+
case 'humidity':
|
|
65
|
+
$('#node-input-unit')
|
|
66
|
+
.find('option')
|
|
67
|
+
.remove()
|
|
68
|
+
.end()
|
|
69
|
+
.append('<option value="unit.percent">Percent (%)</option>')
|
|
70
|
+
.val('unit.percent')
|
|
71
|
+
.prop('disabled', 'disabled');
|
|
72
|
+
break;
|
|
73
|
+
case 'power':
|
|
74
|
+
$('#node-input-unit')
|
|
75
|
+
.find('option')
|
|
76
|
+
.remove()
|
|
77
|
+
.end()
|
|
78
|
+
.append('<option value="unit.watt">Watt (W)</option>')
|
|
79
|
+
.val('unit.watt')
|
|
80
|
+
.prop('disabled', 'disabled');
|
|
81
|
+
break;
|
|
82
|
+
case 'temperature':
|
|
83
|
+
$('#node-input-unit')
|
|
84
|
+
.find('option')
|
|
85
|
+
.remove()
|
|
86
|
+
.end()
|
|
87
|
+
.append('<option value="unit.temperature.celsius">Celsius</option>')
|
|
88
|
+
.append('<option value="unit.temperature.kelvin">Kelvin</option>')
|
|
89
|
+
.prop('disabled', false);
|
|
90
|
+
if (this.unit && this.unit.includes("unit.temperature")){
|
|
91
|
+
$('#node-input-unit').val(this.unit);
|
|
92
|
+
}else{
|
|
93
|
+
$('#node-input-unit').val('unit.temperature.celsius');
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
case 'voltage':
|
|
97
|
+
$('#node-input-unit')
|
|
98
|
+
.find('option')
|
|
99
|
+
.remove()
|
|
100
|
+
.end()
|
|
101
|
+
.append('<option value="unit.volt">Volt (V)</option>')
|
|
102
|
+
.val('unit.volt')
|
|
103
|
+
.prop('disabled', 'disabled');
|
|
104
|
+
break;
|
|
105
|
+
case 'water_level':
|
|
106
|
+
$('#node-input-unit')
|
|
107
|
+
.find('option')
|
|
108
|
+
.remove()
|
|
109
|
+
.end()
|
|
110
|
+
.append('<option value="unit.percent">Percent (%)</option>')
|
|
111
|
+
.val('unit.percent')
|
|
112
|
+
.prop('disabled', 'disabled');
|
|
113
|
+
break;
|
|
114
|
+
case 'illumination':
|
|
115
|
+
$('#node-input-unit')
|
|
116
|
+
.find('option')
|
|
117
|
+
.remove()
|
|
118
|
+
.end()
|
|
119
|
+
.append('<option value="unit.illumination.lux">Lux (lx)</option>')
|
|
120
|
+
.val('unit.illumination.lux')
|
|
121
|
+
.prop('disabled', 'disabled');
|
|
122
|
+
break;
|
|
123
|
+
case 'pressure':
|
|
124
|
+
$('#node-input-unit')
|
|
125
|
+
.find('option')
|
|
126
|
+
.remove()
|
|
127
|
+
.end()
|
|
128
|
+
.append('<option value="unit.pressure.atm">Atmosphere (atm)</option>')
|
|
129
|
+
.append('<option value="unit.pressure.pascal">Pascal (pascal)</option>')
|
|
130
|
+
.append('<option value="unit.pressure.bar">Bar (bar)</option>')
|
|
131
|
+
.append('<option value="unit.pressure.mmhg">Millimeters of mercury (mmhg)</option>')
|
|
132
|
+
// .val('unit.pressure.bar')
|
|
133
|
+
.prop('disabled', false);
|
|
134
|
+
if (this.unit && this.unit.includes("unit.pressure")){
|
|
135
|
+
$('#node-input-unit').val(this.unit);
|
|
136
|
+
}else{
|
|
137
|
+
$('#node-input-unit').val('unit.pressure.bar');
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
case 'pm1_density':
|
|
141
|
+
$('#node-input-unit')
|
|
142
|
+
.find('option')
|
|
143
|
+
.remove()
|
|
144
|
+
.end()
|
|
145
|
+
.append('<option value="unit.density.mcg_m3">μg/m3</option>')
|
|
146
|
+
.val('unit.density.mcg_m3')
|
|
147
|
+
.prop('disabled', 'disabled');
|
|
148
|
+
break;
|
|
149
|
+
case 'pm2.5_density':
|
|
150
|
+
$('#node-input-unit')
|
|
151
|
+
.find('option')
|
|
152
|
+
.remove()
|
|
153
|
+
.end()
|
|
154
|
+
.append('<option value="unit.density.mcg_m3">μg/m3</option>')
|
|
155
|
+
.val('unit.density.mcg_m3')
|
|
156
|
+
.prop('disabled', 'disabled');
|
|
157
|
+
break;
|
|
158
|
+
case 'pm10_density':
|
|
159
|
+
$('#node-input-unit')
|
|
160
|
+
.find('option')
|
|
161
|
+
.remove()
|
|
162
|
+
.end()
|
|
163
|
+
.append('<option value="unit.density.mcg_m3">μg/m3</option>')
|
|
164
|
+
.val('unit.density.mcg_m3')
|
|
165
|
+
.prop('disabled', 'disabled');
|
|
166
|
+
break;
|
|
167
|
+
case 'tvoc':
|
|
168
|
+
$('#node-input-unit')
|
|
169
|
+
.find('option')
|
|
170
|
+
.remove()
|
|
171
|
+
.end()
|
|
172
|
+
.append('<option value="unit.density.mcg_m3">μg/m3</option>')
|
|
173
|
+
.val('unit.density.mcg_m3')
|
|
174
|
+
.prop('disabled', 'disabled');
|
|
175
|
+
break;
|
|
176
|
+
case 'electricity_meter':
|
|
177
|
+
$('#node-input-unit')
|
|
178
|
+
.find('option')
|
|
179
|
+
.remove()
|
|
180
|
+
.end()
|
|
181
|
+
.append('<option value="unit.kilowatt_hour">kW⋅h</option>')
|
|
182
|
+
.val('unit.kilowatt_hour')
|
|
183
|
+
.prop('disabled', 'disabled');
|
|
184
|
+
break;
|
|
185
|
+
case 'gas_meter':
|
|
186
|
+
$('#node-input-unit')
|
|
187
|
+
.find('option')
|
|
188
|
+
.remove()
|
|
189
|
+
.end()
|
|
190
|
+
.append('<option value="unit.cubic_meter">m3</option>')
|
|
191
|
+
.val('unit.cubic_meter')
|
|
192
|
+
.prop('disabled', 'disabled');
|
|
193
|
+
break;
|
|
194
|
+
case 'heat_meter':
|
|
195
|
+
$('#node-input-unit')
|
|
196
|
+
.find('option')
|
|
197
|
+
.remove()
|
|
198
|
+
.end()
|
|
199
|
+
.append('<option value="unit.gigacalorie">Gcal</option>')
|
|
200
|
+
.val('unit.gigacalorie')
|
|
201
|
+
.prop('disabled', 'disabled');
|
|
202
|
+
break;
|
|
203
|
+
case 'water_meter':
|
|
204
|
+
$('#node-input-unit')
|
|
205
|
+
.find('option')
|
|
206
|
+
.remove()
|
|
207
|
+
.end()
|
|
208
|
+
.append('<option value="unit.cubic_meter">m3</option>')
|
|
209
|
+
.val('unit.cubic_meter')
|
|
210
|
+
.prop('disabled', 'disabled');
|
|
211
|
+
break;
|
|
212
|
+
case 'meter':
|
|
213
|
+
$('#node-input-unit')
|
|
214
|
+
.find('option')
|
|
215
|
+
.remove()
|
|
216
|
+
.end()
|
|
217
|
+
.prop('disabled', 'disabled');
|
|
218
|
+
break;
|
|
219
|
+
default:
|
|
220
|
+
$('#node-input-unit').prop('disabled', 'disabled');
|
|
221
|
+
break;
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
oneditsave: function(){
|
|
226
|
+
deivcename = $('#node-input-device option:selected').text();
|
|
227
|
+
instance = $('#node-input-instance option:selected').text();
|
|
228
|
+
$('#node-input-name').val(deivcename+":"+instance);
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
</script>
|
|
232
|
+
|
|
233
|
+
<script type="text/x-red" data-template-name="Sensor">
|
|
234
|
+
<input type="hidden" id="node-input-name">
|
|
235
|
+
<div class="form-row">
|
|
236
|
+
<label for="node-input-device">Device</label>
|
|
237
|
+
<input id="node-input-device">
|
|
238
|
+
</div>
|
|
239
|
+
<div class="form-row">
|
|
240
|
+
<label for="node-input-instance">Sensor Type</label>
|
|
241
|
+
<select id="node-input-instance" style="width: 70%;">
|
|
242
|
+
<option value="amperage">Amperage</option>
|
|
243
|
+
<option value="battery_level">Battery level</option>
|
|
244
|
+
<option value="co2_level">CO2 level</option>
|
|
245
|
+
<option value="food_level">Food level</option>
|
|
246
|
+
<option value="humidity">Humidity</option>
|
|
247
|
+
<option value="illumination">Illumination</option>
|
|
248
|
+
<option value="pm1_density">PM 1 density</option>
|
|
249
|
+
<option value="pm2.5_density">PM 2.5_density</option>
|
|
250
|
+
<option value="pm10_density">PM 10 density</option>
|
|
251
|
+
<option value="power">Power</option>
|
|
252
|
+
<option value="pressure">Pressure</option>
|
|
253
|
+
<option value="temperature">Temperature</option>
|
|
254
|
+
<option value="tvoc">TVOC</option>
|
|
255
|
+
<option value="voltage">Voltage</option>
|
|
256
|
+
<option value="water_level">Water level</option>
|
|
257
|
+
<option value="electricity_meter">Electricity counter</option>
|
|
258
|
+
<option value="gas_meter">Gas counter</option>
|
|
259
|
+
<option value="water_meter">Water counter</option>
|
|
260
|
+
<option value="meter">Universal counter</option>
|
|
261
|
+
</select>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<div class="form-row">
|
|
265
|
+
<label for="node-input-unit">Unit</label>
|
|
266
|
+
<select id="node-input-unit" style="width: 70%;">
|
|
267
|
+
<option value="unit.percent">Percent (%)</option>
|
|
268
|
+
<option value="unit.ppm">Parts per million (ppm)</option>
|
|
269
|
+
<option value="unit.temperature.celsius">Celsius</option>
|
|
270
|
+
<option value="unit.temperature.kelvin">Kelvin</option>
|
|
271
|
+
<option value="unit.ampere">Ampere (A)</option>
|
|
272
|
+
<option value="unit.volt">Volt (V)</option>
|
|
273
|
+
<option value="unit.watt">Watt (W)</option>
|
|
274
|
+
<option value="unit.illumination.lux">Lux (lx)</option>
|
|
275
|
+
<option value="unit.density.mcg_m3">μg/m3</option>
|
|
276
|
+
</select>
|
|
277
|
+
</div>
|
|
278
|
+
</script>
|
|
279
|
+
|
|
280
|
+
<script type="text/x-red" data-help-name="Sensor">
|
|
281
|
+
<p>Transmits data from sensors built into the device.</p>
|
|
282
|
+
|
|
283
|
+
<h3>Property</h3>
|
|
284
|
+
<dl class="message-properties">
|
|
285
|
+
<dt>Device
|
|
286
|
+
<span class="property-type">Select</span>
|
|
287
|
+
</dt>
|
|
288
|
+
<dd> The device to which this sensor is connected </dd>
|
|
289
|
+
<dt>Sensor Type
|
|
290
|
+
<span class="property-type">Select</span>
|
|
291
|
+
</dt>
|
|
292
|
+
<dd>a type of sensor that helps to better understand the area of its use and the data it collects</dd>
|
|
293
|
+
</dl>
|
|
294
|
+
|
|
295
|
+
<h3>Inputs</h3>
|
|
296
|
+
<dl class="message-properties">
|
|
297
|
+
<dt>payload
|
|
298
|
+
<span class="property-type">boolean/float</span>
|
|
299
|
+
</dt>
|
|
300
|
+
<dd>bollean or float</dd>
|
|
301
|
+
</dl>
|
|
302
|
+
|
|
303
|
+
<h3>References</h3>
|
|
304
|
+
<ul>
|
|
305
|
+
<li><a href="https://yandex.ru/dev/dialogs/alice/doc/smart-home/concepts/properties-types.html"> - Yandex documentation</a></li>
|
|
306
|
+
</ul>
|
|
307
|
+
</script>
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { NodeAPI, Node } from "node-red";
|
|
2
|
+
import { AliceSensorConfig, AliceDeviceNode, SensorState } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export = (RED: NodeAPI): void => {
|
|
5
|
+
function AliceSensor(this: Node, config: AliceSensorConfig): 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 id = JSON.parse(JSON.stringify(this.id));
|
|
11
|
+
const stype = config.stype;
|
|
12
|
+
const unit = config.unit;
|
|
13
|
+
const instance = config.instance;
|
|
14
|
+
|
|
15
|
+
const curentState: SensorState = {
|
|
16
|
+
type: stype,
|
|
17
|
+
state: {
|
|
18
|
+
instance: instance,
|
|
19
|
+
value: 0
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
this.status({ fill: "red", shape: "dot", text: "offline" });
|
|
24
|
+
|
|
25
|
+
const init = (): void => {
|
|
26
|
+
this.debug("Starting sensor initilization ...");
|
|
27
|
+
const sensor = {
|
|
28
|
+
type: stype,
|
|
29
|
+
reportable: true,
|
|
30
|
+
retrievable: true,
|
|
31
|
+
parameters: {
|
|
32
|
+
instance: instance,
|
|
33
|
+
unit: unit
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
device.setSensor(id, sensor)
|
|
38
|
+
.then(() => {
|
|
39
|
+
this.debug("Sensor initilization - success!");
|
|
40
|
+
this.status({ fill: "green", shape: "dot", text: "online" });
|
|
41
|
+
})
|
|
42
|
+
.catch(err => {
|
|
43
|
+
this.error("Error on create sensor: " + err.message);
|
|
44
|
+
this.status({ fill: "red", shape: "dot", text: "error" });
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (device.initState) init();
|
|
49
|
+
|
|
50
|
+
device.on("online", () => {
|
|
51
|
+
init();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
device.on("offline", () => {
|
|
55
|
+
this.status({ fill: "red", shape: "dot", text: "offline" });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.on('input', (msg, _send, done) => {
|
|
59
|
+
if (typeof msg.payload != 'number') {
|
|
60
|
+
this.error("Wrong type! msg.payload must be number.");
|
|
61
|
+
if (done) { done(); }
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (unit == 'unit.temperature.celsius' || unit == 'unit.ampere') {
|
|
65
|
+
msg.payload = +msg.payload.toFixed(1);
|
|
66
|
+
} else {
|
|
67
|
+
msg.payload = +msg.payload.toFixed(0);
|
|
68
|
+
}
|
|
69
|
+
if (curentState.state.value == msg.payload) {
|
|
70
|
+
this.debug("Value not changed. Cancel update");
|
|
71
|
+
if (done) { done(); }
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
curentState.state.value = msg.payload;
|
|
75
|
+
device.updateSensorState(id, curentState)
|
|
76
|
+
.then(() => {
|
|
77
|
+
this.status({ fill: "green", shape: "dot", text: String(msg.payload) });
|
|
78
|
+
if (done) { done(); }
|
|
79
|
+
})
|
|
80
|
+
.catch(err => {
|
|
81
|
+
this.error("Error on update sensor state: " + err.message);
|
|
82
|
+
this.status({ fill: "red", shape: "dot", text: "Error" });
|
|
83
|
+
if (done) { done(); }
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
this.on('close', (removed: boolean, done: () => void) => {
|
|
88
|
+
if (removed) {
|
|
89
|
+
device.delSensor(id)
|
|
90
|
+
.then(() => { done(); })
|
|
91
|
+
.catch(err => {
|
|
92
|
+
this.error("Error on delete property: " + err.message);
|
|
93
|
+
done();
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
device.setMaxListeners(device.getMaxListeners() - 1);
|
|
97
|
+
done();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
RED.nodes.registerType("Sensor", AliceSensor);
|
|
103
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('Toggle',{
|
|
3
|
+
category: 'alice',
|
|
4
|
+
defaults:{
|
|
5
|
+
device: {value:"", type:"alice-device"},
|
|
6
|
+
name: {value:""},
|
|
7
|
+
instance: {value:"backlight"},
|
|
8
|
+
response: {value:true}
|
|
9
|
+
},
|
|
10
|
+
inputs:1,
|
|
11
|
+
outputs:1,
|
|
12
|
+
icon: "alice.png",
|
|
13
|
+
color: "#D8BFD8",
|
|
14
|
+
label: function(){
|
|
15
|
+
return this.name + ":"+this.instance;
|
|
16
|
+
},
|
|
17
|
+
oneditprepare: function(){
|
|
18
|
+
if (this.response === undefined){
|
|
19
|
+
$( "#node-input-response").prop('checked', true);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
oneditsave: function(){
|
|
23
|
+
deivcename = $('#node-input-device option:selected').text();
|
|
24
|
+
$('#node-input-name').val(deivcename);
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<script type="text/x-red" data-template-name="Toggle">
|
|
30
|
+
<input type="hidden" id="node-input-name">
|
|
31
|
+
<div class="form-row">
|
|
32
|
+
<label for="node-input-device">Device</label>
|
|
33
|
+
<input id="node-input-device">
|
|
34
|
+
</div>
|
|
35
|
+
<div class="form-row">
|
|
36
|
+
<label for="node-input-instance">Instance</label>
|
|
37
|
+
<select id="node-input-instance" style="width: 70%;">
|
|
38
|
+
<option value="backlight">Backlight</option>
|
|
39
|
+
<option value="controls_locked">Locking</option>
|
|
40
|
+
<option value="ionization" >Ionization</option>
|
|
41
|
+
<option value="keep_warm">Keep warm</option>
|
|
42
|
+
<option value="mute">Mute</option>
|
|
43
|
+
<option value="oscillation">Oscillation</option>
|
|
44
|
+
<option value="pause">Pause</option>
|
|
45
|
+
</select>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="form-row">
|
|
48
|
+
<label for="node-input-response"><i class="fa fa-refresh"></i> <span >Response</span></label>
|
|
49
|
+
<label for="node-input-response" style="width:70%">
|
|
50
|
+
<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>
|
|
51
|
+
</label>
|
|
52
|
+
</div>
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<script type="text/x-red" data-help-name="Toggle">
|
|
56
|
+
<p>Manage device settings that can only be in one of two states.</p>
|
|
57
|
+
<p>The toggle skill has features. Functions are characterized by certain parameters and
|
|
58
|
+
voice scripts that allow you to describe the corresponding skills of the device.</p>
|
|
59
|
+
|
|
60
|
+
<h3>Property</h3>
|
|
61
|
+
<dl class="message-properties">
|
|
62
|
+
<dt>Device
|
|
63
|
+
<span class="property-type">Select</span>
|
|
64
|
+
</dt>
|
|
65
|
+
<dd> The device to which this feature is connected </dd>
|
|
66
|
+
<dt>Instance
|
|
67
|
+
<span class="property-type">Select</span>
|
|
68
|
+
</dt>
|
|
69
|
+
<dd> The name of the function for this skill. Affects which voice command is called </dd>
|
|
70
|
+
<dt>Response
|
|
71
|
+
<span class="property-type">checkbox</span>
|
|
72
|
+
</dt>
|
|
73
|
+
<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.
|
|
74
|
+
If your device takes longer or doesn’t return a confirmation at all, just check this box. </dd>
|
|
75
|
+
</dl>
|
|
76
|
+
|
|
77
|
+
<h3>Inputs</h3>
|
|
78
|
+
<dl class="message-properties">
|
|
79
|
+
<dt>payload
|
|
80
|
+
<span class="property-type">boolean</span>
|
|
81
|
+
</dt>
|
|
82
|
+
<dd> true or false </dd>
|
|
83
|
+
</dl>
|
|
84
|
+
|
|
85
|
+
<h3>Outputs</h3>
|
|
86
|
+
<dl class="message-properties">
|
|
87
|
+
<dt>payload
|
|
88
|
+
<span class="property-type">boolean</span>
|
|
89
|
+
</dt>
|
|
90
|
+
<dd> true or false </dd>
|
|
91
|
+
</dl>
|
|
92
|
+
<h3>References</h3>
|
|
93
|
+
<ul>
|
|
94
|
+
<li><a href="https://yandex.ru/dev/dialogs/alice/doc/smart-home/concepts/toggle-instance-docpage/"> - Yandex documentation</a></li>
|
|
95
|
+
</ul>
|
|
96
|
+
</script>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { NodeAPI, Node } from "node-red";
|
|
2
|
+
import { AliceCapabilityConfig, AliceDeviceNode, CapabilityState } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export = (RED: NodeAPI): void => {
|
|
5
|
+
function AliceToggle(this: Node, config: AliceCapabilityConfig): 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.toggle';
|
|
11
|
+
const instance = config.instance || '';
|
|
12
|
+
let response = config.response;
|
|
13
|
+
let value = false;
|
|
14
|
+
|
|
15
|
+
if (config.response === undefined) {
|
|
16
|
+
response = true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this.status({ fill: "red", shape: "dot", text: "offline" });
|
|
20
|
+
|
|
21
|
+
const init = (): void => {
|
|
22
|
+
this.debug("Starting capability initilization ...");
|
|
23
|
+
const capab = {
|
|
24
|
+
type: ctype,
|
|
25
|
+
retrievable: true,
|
|
26
|
+
reportable: true,
|
|
27
|
+
parameters: {
|
|
28
|
+
instance: instance,
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
device.setCapability(this.id, capab)
|
|
32
|
+
.then(() => {
|
|
33
|
+
this.debug("Capability initilization - success!");
|
|
34
|
+
this.status({ fill: "green", shape: "dot", text: "online" });
|
|
35
|
+
})
|
|
36
|
+
.catch(err => {
|
|
37
|
+
this.error("Error on create capability: " + err.message);
|
|
38
|
+
this.status({ fill: "red", shape: "dot", text: "error" });
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (device.initState) init();
|
|
43
|
+
|
|
44
|
+
device.on("online", () => {
|
|
45
|
+
init();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
device.on("offline", () => {
|
|
49
|
+
this.status({ fill: "red", shape: "dot", text: "offline" });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
device.on(this.id, (val: boolean) => {
|
|
53
|
+
this.debug("Received a new value from Yandex...");
|
|
54
|
+
this.send({ payload: val });
|
|
55
|
+
const state: CapabilityState = {
|
|
56
|
+
type: ctype,
|
|
57
|
+
state: {
|
|
58
|
+
instance: instance,
|
|
59
|
+
value: val
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
if (response) {
|
|
63
|
+
this.debug("Automatic confirmation is true, sending confirmation to Yandex ...");
|
|
64
|
+
device.updateCapabState(this.id, state)
|
|
65
|
+
.then(() => {
|
|
66
|
+
value = val;
|
|
67
|
+
this.status({ fill: "green", shape: "dot", text: String(val) });
|
|
68
|
+
})
|
|
69
|
+
.catch(err => {
|
|
70
|
+
this.error("Error on update capability state: " + err.message);
|
|
71
|
+
this.status({ fill: "red", shape: "dot", text: "Error" });
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.on('input', (msg, _send, done) => {
|
|
77
|
+
if (typeof msg.payload != 'boolean') {
|
|
78
|
+
this.error("Wrong type! msg.payload must be boolean.");
|
|
79
|
+
if (done) { done(); }
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (msg.payload === value) {
|
|
83
|
+
this.debug("Value not changed. Cancel update");
|
|
84
|
+
if (done) { done(); }
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const state: CapabilityState = {
|
|
88
|
+
type: ctype,
|
|
89
|
+
state: {
|
|
90
|
+
instance: instance,
|
|
91
|
+
value: msg.payload
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
device.updateCapabState(this.id, state)
|
|
95
|
+
.then(() => {
|
|
96
|
+
value = msg.payload as boolean;
|
|
97
|
+
this.status({ fill: "green", shape: "dot", text: String(msg.payload) });
|
|
98
|
+
if (done) { done(); }
|
|
99
|
+
})
|
|
100
|
+
.catch(err => {
|
|
101
|
+
this.error("Error on update capability state: " + err.message);
|
|
102
|
+
this.status({ fill: "red", shape: "dot", text: "Error" });
|
|
103
|
+
if (done) { done(); }
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
this.on('close', (removed: boolean, done: () => void) => {
|
|
108
|
+
if (removed) {
|
|
109
|
+
device.delCapability(this.id)
|
|
110
|
+
.then(() => { done(); })
|
|
111
|
+
.catch(err => {
|
|
112
|
+
this.error("Error on delete capability: " + err.message);
|
|
113
|
+
done();
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
done();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
RED.nodes.registerType("Toggle", AliceToggle);
|
|
122
|
+
};
|