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.
- 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,296 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('Mode',{
|
|
3
|
+
category: 'alice',
|
|
4
|
+
defaults:{
|
|
5
|
+
device: {value:"", type:"alice-device"},
|
|
6
|
+
name: {value:""},
|
|
7
|
+
instance: { value:null, validate: (v)=>{
|
|
8
|
+
if (v){
|
|
9
|
+
return true;
|
|
10
|
+
}else{
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}},
|
|
14
|
+
modes:{value:[], validate:(ms)=>{
|
|
15
|
+
return ms.length > 0
|
|
16
|
+
}},
|
|
17
|
+
response:{value:true}
|
|
18
|
+
},
|
|
19
|
+
inputs:1,
|
|
20
|
+
outputs:1,
|
|
21
|
+
icon: "alice.png",
|
|
22
|
+
color: "#D8BFD8",
|
|
23
|
+
label: function(){
|
|
24
|
+
return this.name + ":" + this.instance;
|
|
25
|
+
},
|
|
26
|
+
oneditsave: function(){
|
|
27
|
+
deivcename = $('#node-input-device option:selected').text();
|
|
28
|
+
$('#node-input-name').val(deivcename);
|
|
29
|
+
currentInstance = $('#node-input-instance').val();
|
|
30
|
+
if (currentInstance=='multicooker_mode'){
|
|
31
|
+
$('#node-input-instance option[value=program]').prop('selected', true);
|
|
32
|
+
};
|
|
33
|
+
this.modes = getCurrentModes();
|
|
34
|
+
},
|
|
35
|
+
oneditprepare: function(){
|
|
36
|
+
var firstRun = true;
|
|
37
|
+
$('#node-input-instance').on('change',()=>{
|
|
38
|
+
var val = $('#node-input-instance').find(":selected").val();
|
|
39
|
+
if (firstRun){
|
|
40
|
+
firstRun=false;
|
|
41
|
+
return;
|
|
42
|
+
};
|
|
43
|
+
switch (val) {
|
|
44
|
+
case 'cleanup_mode':
|
|
45
|
+
this.modes = ['auto','eco','express','normal','quiet']
|
|
46
|
+
break;
|
|
47
|
+
case 'coffee_mode':
|
|
48
|
+
this.modes = ['americano','cappuccino','double_espresso','espresso','latte']
|
|
49
|
+
break;
|
|
50
|
+
case 'multicooker_mode':
|
|
51
|
+
this.modes = ['vacuum','boiling','baking','dessert','baby_food','fowl','frying','yogurt','cereals','macaroni','milk_porridge','multicooker','steam','pasta','pizza','pilaf','sauce','soup','stewing','slow_cook','deep_fryer','bread','aspic','cheesecake','preheat']
|
|
52
|
+
break;
|
|
53
|
+
case 'fan_speed':
|
|
54
|
+
this.modes = ['auto','high','low','medium','turbo']
|
|
55
|
+
break;
|
|
56
|
+
case 'heat':
|
|
57
|
+
this.modes = ['auto','max','min','normal','turbo']
|
|
58
|
+
break;
|
|
59
|
+
case 'input_source':
|
|
60
|
+
this.modes = ['one','two','three','four','five','six','seven','eight','nine','ten']
|
|
61
|
+
break;
|
|
62
|
+
case 'program':
|
|
63
|
+
this.modes = ['auto','express','one','two','three','four','five','six','seven','eight','nine','ten']
|
|
64
|
+
break;
|
|
65
|
+
case 'swing':
|
|
66
|
+
this.modes = ['auto','horizontal','stationary','vertical']
|
|
67
|
+
break;
|
|
68
|
+
case 'thermostat':
|
|
69
|
+
this.modes = ['auto','cool','dry','fan_only','heat','preheat']
|
|
70
|
+
break;
|
|
71
|
+
case 'work_speed':
|
|
72
|
+
this.modes = ['auto','fast','max','medium','min','slow','turbo']
|
|
73
|
+
break;
|
|
74
|
+
case 'tea_mode':
|
|
75
|
+
this.modes = ['black_tea','flower_tea','green_tea','herbal_tea','oolong_tea','puerh_tea','red_tea','white_tea']
|
|
76
|
+
break;
|
|
77
|
+
default:
|
|
78
|
+
this.modes = [];
|
|
79
|
+
break;
|
|
80
|
+
};
|
|
81
|
+
updateAllModes(this.modes);
|
|
82
|
+
});
|
|
83
|
+
updateAllModes(this.modes);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
function updateAllModes(modes){
|
|
87
|
+
deleteAllMode();
|
|
88
|
+
modes.forEach((m,i)=>{
|
|
89
|
+
addMode2List(m,i);
|
|
90
|
+
});
|
|
91
|
+
if (modes.length<1){
|
|
92
|
+
$('#node-input-modes-container').parent().css('border-color', "rgb(214, 97, 95)")
|
|
93
|
+
}else{
|
|
94
|
+
$('#node-input-modes-container').parent().css('border-color', "")
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
function addMode2List(mode,index){
|
|
98
|
+
$('#node-input-modes-container').append('<li rel="'+mode+'" class="red-ui-editableList-item-sortable red-ui-editableList-item-removable"><div class="red-ui-editableList-item-content" style="overflow: hidden; white-space: nowrap;"><span>'+mode+'</span></div><a href="#" onclick="delMode('+index+')" class="red-ui-editableList-item-remove red-ui-button red-ui-button-small"><i class="fa fa-remove"></i></a></li>')
|
|
99
|
+
};
|
|
100
|
+
function deleteAllMode() {
|
|
101
|
+
$('#node-input-modes-container').empty();
|
|
102
|
+
};
|
|
103
|
+
function delMode(index) {
|
|
104
|
+
var modes = getCurrentModes();
|
|
105
|
+
modes.splice(index, 1);
|
|
106
|
+
updateAllModes(modes);
|
|
107
|
+
};
|
|
108
|
+
function addMode() {
|
|
109
|
+
var mode = $('#select-custom-mode').find(":selected").val();
|
|
110
|
+
var modes = getCurrentModes();
|
|
111
|
+
if (modes.indexOf(mode)>-1){
|
|
112
|
+
return;
|
|
113
|
+
}else{
|
|
114
|
+
modes.push(mode);
|
|
115
|
+
updateAllModes(modes);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
function getCurrentModes() {
|
|
119
|
+
var modes = [];
|
|
120
|
+
$('#node-input-modes-container li').each(function(){
|
|
121
|
+
modes.push($(this).attr('rel'));
|
|
122
|
+
});
|
|
123
|
+
return modes;
|
|
124
|
+
}
|
|
125
|
+
</script>
|
|
126
|
+
|
|
127
|
+
<script type="text/x-red" data-template-name="Mode">
|
|
128
|
+
<input type="hidden" id="node-input-name">
|
|
129
|
+
<div class="form-row">
|
|
130
|
+
<label for="node-input-device">Device</label>
|
|
131
|
+
<input id="node-input-device">
|
|
132
|
+
</div>
|
|
133
|
+
<div class="form-row">
|
|
134
|
+
<label for="node-input-instance">Mode Type</label>
|
|
135
|
+
<select id="node-input-instance" style="width: 70%;">
|
|
136
|
+
<option value="cleanup_mode">Cleanup mode</option>
|
|
137
|
+
<option value="coffee_mode">Coffee mode</option>
|
|
138
|
+
<option value="multicooker_mode">Multicooker Mode</option>
|
|
139
|
+
<option value="tea_mode">Tea Mode</option>
|
|
140
|
+
<option value="fan_speed">Fan speed</option>
|
|
141
|
+
<option value="heat">Heat Mode</option>
|
|
142
|
+
<option value="input_source">Input source</option>
|
|
143
|
+
<option value="program">Program</option>
|
|
144
|
+
<option value="swing">Swing Mode</option>
|
|
145
|
+
<option value="thermostat">Thermostat</option>
|
|
146
|
+
<option value="work_speed">Work speed</option>
|
|
147
|
+
</select>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="form-row node-input-rule-container-row">
|
|
150
|
+
<label for="node-input-modes" style="width:auto">Supported commands</label>
|
|
151
|
+
<div class="red-ui-editableList">
|
|
152
|
+
<div class="red-ui-editableList-border red-ui-editableList-container" style="min-height: 150px; max-height: none; overflow-y: scroll; height: 308.4px;">
|
|
153
|
+
<ol id="node-input-modes-container" class="red-ui-editableList-list" style="min-height: 0px; min-width: 450px; height: auto;">
|
|
154
|
+
<li class="red-ui-editableList-item-sortable red-ui-editableList-item-removable">
|
|
155
|
+
<div class="red-ui-editableList-item-content" style="overflow: hidden; white-space: nowrap;">
|
|
156
|
+
<span>Tets</span>
|
|
157
|
+
</div>
|
|
158
|
+
<i class="red-ui-editableList-item-handle fa fa-bullhorn" style="cursor:auto"></i><a href="#" class="red-ui-editableList-item-remove red-ui-button red-ui-button-small"><i class="fa fa-remove"></i></a>
|
|
159
|
+
</li>
|
|
160
|
+
<li class="red-ui-editableList-item-sortable red-ui-editableList-item-removable">
|
|
161
|
+
<div class="red-ui-editableList-item-content" style="overflow: hidden; white-space: nowrap;">
|
|
162
|
+
<span>Tets2</span>
|
|
163
|
+
</div>
|
|
164
|
+
<i class="red-ui-editableList-item-handle fa fa-bullhorn" style="cursor:auto"></i><a href="#" class="red-ui-editableList-item-remove red-ui-button red-ui-button-small"><i class="fa fa-remove"></i></a>
|
|
165
|
+
</li>
|
|
166
|
+
</ol>
|
|
167
|
+
</div>
|
|
168
|
+
<div style="margin-top:4px">
|
|
169
|
+
<select id="select-custom-mode">
|
|
170
|
+
<option value="auto">auto</option>
|
|
171
|
+
<option value="eco">eco</option>
|
|
172
|
+
<option value="turbo">turbo</option>
|
|
173
|
+
<option value="cool">cool</option>
|
|
174
|
+
<option value="dry">dry</option>
|
|
175
|
+
<option value="fan_only">fan_only</option>
|
|
176
|
+
<option value="heat">heat</option>
|
|
177
|
+
<option value="preheat">preheat</option>
|
|
178
|
+
<option value="high">high</option>
|
|
179
|
+
<option value="low">low</option>
|
|
180
|
+
<option value="medium">medium</option>
|
|
181
|
+
<option value="max">max</option>
|
|
182
|
+
<option value="min">min</option>
|
|
183
|
+
<option value="fast">fast</option>
|
|
184
|
+
<option value="slow">slow</option>
|
|
185
|
+
<option value="express">express</option>
|
|
186
|
+
<option value="normal">normal</option>
|
|
187
|
+
<option value="quiet">quiet</option>
|
|
188
|
+
<option value="horizontal">horizontal</option>
|
|
189
|
+
<option value="stationary">stationary</option>
|
|
190
|
+
<option value="vertical">vertical</option>
|
|
191
|
+
<option value="americano">americano</option>
|
|
192
|
+
<option value="cappuccino">cappuccino</option>
|
|
193
|
+
<option value="double_espresso">double_espresso</option>
|
|
194
|
+
<option value="espresso">espresso</option>
|
|
195
|
+
<option value="latte">latte</option>
|
|
196
|
+
<option value="one">one</option>
|
|
197
|
+
<option value="two">two</option>
|
|
198
|
+
<option value="three">three</option>
|
|
199
|
+
<option value="four">four</option>
|
|
200
|
+
<option value="five">five</option>
|
|
201
|
+
<option value="six">six</option>
|
|
202
|
+
<option value="seven">seven</option>
|
|
203
|
+
<option value="eight">eight</option>
|
|
204
|
+
<option value="nine">nine</option>
|
|
205
|
+
<option value="ten">ten</option>
|
|
206
|
+
<option value="vacuum">vacuum</option>
|
|
207
|
+
<option value="boiling">boiling</option>
|
|
208
|
+
<option value="baking">baking</option>
|
|
209
|
+
<option value="dessert">dessert</option>
|
|
210
|
+
<option value="baby_food">baby_food</option>
|
|
211
|
+
<option value="fowl">fowl</option>
|
|
212
|
+
<option value="frying">frying</option>
|
|
213
|
+
<option value="yogurt">yogurt</option>
|
|
214
|
+
<option value="cereals">cereals</option>
|
|
215
|
+
<option value="macaroni">macaroni</option>
|
|
216
|
+
<option value="milk_porridge">milk_porridge</option>
|
|
217
|
+
<option value="multicooker">multicooker</option>
|
|
218
|
+
<option value="steam">steam</option>
|
|
219
|
+
<option value="pasta">pasta</option>
|
|
220
|
+
<option value="pizza">pizza</option>
|
|
221
|
+
<option value="pilaf">pilaf</option>
|
|
222
|
+
<option value="sauce">sauce</option>
|
|
223
|
+
<option value="soup">soup</option>
|
|
224
|
+
<option value="stewing">stewing</option>
|
|
225
|
+
<option value="slow_cook">slow_cook</option>
|
|
226
|
+
<option value="deep_fryer">deep_fryer</option>
|
|
227
|
+
<option value="bread">bread</option>
|
|
228
|
+
<option value="aspic">aspic</option>
|
|
229
|
+
<option value="cheesecake">cheesecake</option>
|
|
230
|
+
<option value="preheat">preheat</option>
|
|
231
|
+
<option value="black_tea">black_tea</option>
|
|
232
|
+
<option value="flower_tea">flower_tea</option>
|
|
233
|
+
<option value="green_tea">green_tea</option>
|
|
234
|
+
<option value="herbal_tea">herbal_tea</option>
|
|
235
|
+
<option value="oolong_tea">oolong_tea</option>
|
|
236
|
+
<option value="puerh_tea">puerh_tea</option>
|
|
237
|
+
<option value="red_tea">red_tea</option>
|
|
238
|
+
<option value="white_tea">white_tea</option>
|
|
239
|
+
</select>
|
|
240
|
+
<a id="button-addMode" href="#" class="red-ui-button" onclick="addMode()" style="margin-top: 4px;"><i class="fa fa-plus"></i></a>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
<div class="form-row">
|
|
245
|
+
<label for="node-input-response"><i class="fa fa-refresh"></i> <span >Response</span></label>
|
|
246
|
+
<label for="node-input-response" style="width:70%">
|
|
247
|
+
<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>
|
|
248
|
+
</label>
|
|
249
|
+
</div>
|
|
250
|
+
</script>
|
|
251
|
+
|
|
252
|
+
<script type="text/x-red" data-help-name="Mode">
|
|
253
|
+
<p>Controlling the operating modes of the device, for example, switching between the temperature operating modes of the air conditioner: "Cooling", "Heating" or "Auto".</p>
|
|
254
|
+
|
|
255
|
+
<h3>Property</h3>
|
|
256
|
+
<dl class="message-properties">
|
|
257
|
+
<dt>Device
|
|
258
|
+
<span class="property-type">Select</span>
|
|
259
|
+
</dt>
|
|
260
|
+
<dd> The device to which this feature is connected </dd>
|
|
261
|
+
<dt>Mode Type
|
|
262
|
+
<span class="property-type">Select</span>
|
|
263
|
+
</dt>
|
|
264
|
+
<dd> Selection of the type of modes (cleanup, coffee, input source, work speed, etc.) </dd>
|
|
265
|
+
<dt>Supported commands
|
|
266
|
+
<span class="property-type">List</span>
|
|
267
|
+
</dt>
|
|
268
|
+
<dd> List of commands that are supported by this mode. You can add other commands yourself. </dd>
|
|
269
|
+
<dt>Response
|
|
270
|
+
<span class="property-type">checkbox</span>
|
|
271
|
+
</dt>
|
|
272
|
+
<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.
|
|
273
|
+
If your device takes longer or doesn’t return a confirmation at all, just check this box. </dd>
|
|
274
|
+
</dl>
|
|
275
|
+
</dl>
|
|
276
|
+
|
|
277
|
+
<h3>Inputs</h3>
|
|
278
|
+
<dl class="message-properties">
|
|
279
|
+
<dt>payload
|
|
280
|
+
<span class="property-type">String</span>
|
|
281
|
+
</dt>
|
|
282
|
+
<dd> command from the supported list of commands </dd>
|
|
283
|
+
</dl>
|
|
284
|
+
|
|
285
|
+
<h3>Outputs</h3>
|
|
286
|
+
<dl class="message-properties">
|
|
287
|
+
<dt>payload
|
|
288
|
+
<span class="property-type">Strin</span>
|
|
289
|
+
</dt>
|
|
290
|
+
<dd> command from the supported list of commands </dd>
|
|
291
|
+
</dl>
|
|
292
|
+
<h3>References</h3>
|
|
293
|
+
<ul>
|
|
294
|
+
<li><a href="https://yandex.ru/dev/dialogs/alice/doc/smart-home/concepts/mode-instance-modes-docpage/"> Yandex documentation</a></li>
|
|
295
|
+
</ul>
|
|
296
|
+
</script>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { NodeAPI, Node } from "node-red";
|
|
2
|
+
import { AliceModeConfig, AliceDeviceNode, CapabilityState } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export = (RED: NodeAPI): void => {
|
|
5
|
+
function AliceMode(this: Node, config: AliceModeConfig): void {
|
|
6
|
+
RED.nodes.createNode(this, config);
|
|
7
|
+
const device = RED.nodes.getNode(config.device) as AliceDeviceNode;
|
|
8
|
+
|
|
9
|
+
const ctype = 'devices.capabilities.mode';
|
|
10
|
+
const instance = config.instance || '';
|
|
11
|
+
const modes = config.modes;
|
|
12
|
+
let response = config.response;
|
|
13
|
+
let value: string | undefined;
|
|
14
|
+
|
|
15
|
+
if (config.response === undefined) {
|
|
16
|
+
response = true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const init = (): void => {
|
|
20
|
+
if (modes.length < 1) {
|
|
21
|
+
this.status({ fill: "red", shape: "dot", text: "error" });
|
|
22
|
+
this.error("In the list of supported commands, there must be at least one command");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!instance) {
|
|
26
|
+
this.status({ fill: "red", shape: "dot", text: "error" });
|
|
27
|
+
this.error("Mode type not selected");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const cfgModes = modes.map(v => ({ value: v }));
|
|
32
|
+
const capab = {
|
|
33
|
+
type: ctype,
|
|
34
|
+
retrievable: true,
|
|
35
|
+
reportable: true,
|
|
36
|
+
parameters: {
|
|
37
|
+
instance: instance,
|
|
38
|
+
modes: cfgModes
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
device.setCapability(this.id, capab)
|
|
43
|
+
.then(() => {
|
|
44
|
+
this.status({ fill: "green", shape: "dot", text: "online" });
|
|
45
|
+
})
|
|
46
|
+
.catch(err => {
|
|
47
|
+
this.error("Error on create capability: " + err.message);
|
|
48
|
+
this.status({ fill: "red", shape: "dot", text: "error" });
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (device.initState) init();
|
|
53
|
+
|
|
54
|
+
device.on("online", () => {
|
|
55
|
+
init();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
device.on("offline", () => {
|
|
59
|
+
this.status({ fill: "red", shape: "dot", text: "offline" });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
device.on(this.id, (val: string) => {
|
|
63
|
+
this.send({ payload: val });
|
|
64
|
+
const state: CapabilityState = {
|
|
65
|
+
type: ctype,
|
|
66
|
+
state: {
|
|
67
|
+
instance: instance,
|
|
68
|
+
value: val
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
if (response) {
|
|
72
|
+
device.updateCapabState(this.id, state)
|
|
73
|
+
.then(() => {
|
|
74
|
+
value = val;
|
|
75
|
+
this.status({ fill: "green", shape: "dot", text: "online" });
|
|
76
|
+
})
|
|
77
|
+
.catch(err => {
|
|
78
|
+
this.error("Error on update capability state: " + err.message);
|
|
79
|
+
this.status({ fill: "red", shape: "dot", text: "Error" });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
this.on('input', (msg, _send, done) => {
|
|
85
|
+
const newValue = msg.payload;
|
|
86
|
+
if (typeof newValue != 'string') {
|
|
87
|
+
this.error("Wrong type! msg.payload must be String.");
|
|
88
|
+
this.status({ fill: "red", shape: "dot", text: "Error" });
|
|
89
|
+
if (done) { done(); }
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (modes.indexOf(newValue) < 0) {
|
|
93
|
+
this.error("Error! Unsupported command.");
|
|
94
|
+
this.status({ fill: "red", shape: "dot", text: "Error" });
|
|
95
|
+
if (done) { done(); }
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (newValue === value) {
|
|
99
|
+
this.debug("Value not changed. Cancel update");
|
|
100
|
+
if (done) { done(); }
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const state: CapabilityState = {
|
|
105
|
+
type: ctype,
|
|
106
|
+
state: {
|
|
107
|
+
instance: instance,
|
|
108
|
+
value: newValue
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
device.updateCapabState(this.id, state)
|
|
112
|
+
.then(() => {
|
|
113
|
+
value = newValue;
|
|
114
|
+
this.status({ fill: "green", shape: "dot", text: newValue });
|
|
115
|
+
if (done) { done(); }
|
|
116
|
+
})
|
|
117
|
+
.catch(err => {
|
|
118
|
+
this.error("Error on update capability state: " + err.message);
|
|
119
|
+
this.status({ fill: "red", shape: "dot", text: "Error" });
|
|
120
|
+
if (done) { done(); }
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
this.on('close', (removed: boolean, done: () => void) => {
|
|
125
|
+
if (removed) {
|
|
126
|
+
device.delCapability(this.id)
|
|
127
|
+
.then(() => { done(); })
|
|
128
|
+
.catch(err => {
|
|
129
|
+
this.error("Error on delete capability: " + err.message);
|
|
130
|
+
done();
|
|
131
|
+
});
|
|
132
|
+
} else {
|
|
133
|
+
done();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
RED.nodes.registerType("Mode", AliceMode);
|
|
139
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('On_Off',{
|
|
3
|
+
category: 'alice',
|
|
4
|
+
defaults:{
|
|
5
|
+
device: {value:"", type:"alice-device"},
|
|
6
|
+
name: {value:""},
|
|
7
|
+
// retrievable: {value:true},
|
|
8
|
+
response:{value:true},
|
|
9
|
+
split: {value:false}
|
|
10
|
+
},
|
|
11
|
+
inputs:1,
|
|
12
|
+
outputs:1,
|
|
13
|
+
icon: "alice.png",
|
|
14
|
+
color: "#D8BFD8",
|
|
15
|
+
label: function(){
|
|
16
|
+
return this.name + ":On/Off";
|
|
17
|
+
},
|
|
18
|
+
oneditprepare: function(){
|
|
19
|
+
if (this.response === undefined){
|
|
20
|
+
$( "#node-input-response").prop('checked', true);
|
|
21
|
+
};
|
|
22
|
+
if (this.retrievable === false){
|
|
23
|
+
$( "#node-input-split").prop('checked', true);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
oneditsave: function(){
|
|
27
|
+
deivcename = $('#node-input-device option:selected').text();
|
|
28
|
+
$('#node-input-name').val(deivcename);
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<script type="text/x-red" data-template-name="On_Off">
|
|
34
|
+
<input type="hidden" id="node-input-name">
|
|
35
|
+
<div class="form-row">
|
|
36
|
+
<label for="node-input-device">Device</label>
|
|
37
|
+
<input id="node-input-device">
|
|
38
|
+
</div>
|
|
39
|
+
<div class="form-row">
|
|
40
|
+
<label for="node-input-split"><i class="fa fa-power-off"></i> <span >Split button</span></label>
|
|
41
|
+
<label for="node-input-split" style="width:70%">
|
|
42
|
+
<input type="checkbox" id="node-input-split" style="display:inline-block; width:22px; vertical-align:baseline;" autocomplete="off"><span>Split On/Off button</span>
|
|
43
|
+
</label>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="form-row">
|
|
46
|
+
<label for="node-input-response"><i class="fa fa-refresh"></i> <span >Response</span></label>
|
|
47
|
+
<label for="node-input-response" style="width:70%">
|
|
48
|
+
<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>
|
|
49
|
+
</label>
|
|
50
|
+
</div>
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<script type="text/x-red" data-help-name="On_Off">
|
|
54
|
+
<p>Allows the device to turn on and off</p>
|
|
55
|
+
|
|
56
|
+
<h3>Property</h3>
|
|
57
|
+
<dl class="message-properties">
|
|
58
|
+
<dt>Device
|
|
59
|
+
<span class="property-type">Select</span>
|
|
60
|
+
</dt>
|
|
61
|
+
<dd> The device to which this feature is connected </dd>
|
|
62
|
+
<dt>Main button
|
|
63
|
+
<span class="property-type">checkbox</span>
|
|
64
|
+
</dt>
|
|
65
|
+
<dd>The main button blocks access to other controls if the device is turned off.
|
|
66
|
+
When unchecking this, the controls will be available, but you will not be able to find out whether the device is on or off</dd>
|
|
67
|
+
<dt>Response
|
|
68
|
+
<span class="property-type">checkbox</span>
|
|
69
|
+
</dt>
|
|
70
|
+
<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.
|
|
71
|
+
If your device takes longer or doesn’t return a confirmation at all, just check this box. </dd>
|
|
72
|
+
</dl>
|
|
73
|
+
|
|
74
|
+
<h3>Inputs</h3>
|
|
75
|
+
<dl class="message-properties">
|
|
76
|
+
<dt>payload
|
|
77
|
+
<span class="property-type">boolean</span>
|
|
78
|
+
</dt>
|
|
79
|
+
<dd> true or false </dd>
|
|
80
|
+
</dl>
|
|
81
|
+
|
|
82
|
+
<h3>Outputs</h3>
|
|
83
|
+
<dl class="message-properties">
|
|
84
|
+
<dt>payload
|
|
85
|
+
<span class="property-type">boolean</span>
|
|
86
|
+
</dt>
|
|
87
|
+
<dd> true or false </dd>
|
|
88
|
+
</dl>
|
|
89
|
+
<h3>References</h3>
|
|
90
|
+
<ul>
|
|
91
|
+
<li><a href="https://yandex.ru/dev/dialogs/alice/doc/smart-home/concepts/capability-types-docpage/"> - Yandex documentation</a></li>
|
|
92
|
+
</ul>
|
|
93
|
+
</script>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { NodeAPI, Node } from "node-red";
|
|
2
|
+
import { AliceOnOffConfig, AliceDeviceNode, CapabilityState } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export = (RED: NodeAPI): void => {
|
|
5
|
+
function AliceOnOff(this: Node, config: AliceOnOffConfig): 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 ctype = 'devices.capabilities.on_off';
|
|
12
|
+
const instance = 'on';
|
|
13
|
+
let response = config.response;
|
|
14
|
+
let split = config.split;
|
|
15
|
+
let initState = false;
|
|
16
|
+
|
|
17
|
+
const curentState: CapabilityState = {
|
|
18
|
+
type: ctype,
|
|
19
|
+
state: {
|
|
20
|
+
instance: instance,
|
|
21
|
+
value: false
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (config.response === undefined) {
|
|
26
|
+
response = true;
|
|
27
|
+
}
|
|
28
|
+
if (config.split === undefined) {
|
|
29
|
+
split = false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.status({ fill: "red", shape: "dot", text: "offline" });
|
|
33
|
+
|
|
34
|
+
const init = (): void => {
|
|
35
|
+
this.debug("Starting capability initilization ...");
|
|
36
|
+
const capab = {
|
|
37
|
+
type: ctype,
|
|
38
|
+
retrievable: true,
|
|
39
|
+
reportable: true,
|
|
40
|
+
parameters: {
|
|
41
|
+
instance: instance,
|
|
42
|
+
split: split
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
device.setCapability(id, capab)
|
|
47
|
+
.then(() => {
|
|
48
|
+
this.debug("Capability initilization - success!");
|
|
49
|
+
initState = true;
|
|
50
|
+
this.status({ fill: "green", shape: "dot", text: "online" });
|
|
51
|
+
})
|
|
52
|
+
.catch(err => {
|
|
53
|
+
this.error("Error on create capability: " + err.message);
|
|
54
|
+
this.status({ fill: "red", shape: "dot", text: "error" });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
device.updateCapabState(id, curentState)
|
|
58
|
+
.then(() => {
|
|
59
|
+
this.status({ fill: "green", shape: "dot", text: "online" });
|
|
60
|
+
})
|
|
61
|
+
.catch(err => {
|
|
62
|
+
this.error("Error on update capability state: " + err.message);
|
|
63
|
+
this.status({ fill: "red", shape: "dot", text: "Error" });
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (device.initState) init();
|
|
68
|
+
|
|
69
|
+
device.on("online", () => {
|
|
70
|
+
init();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
device.on("offline", () => {
|
|
74
|
+
this.status({ fill: "red", shape: "dot", text: "offline" });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
device.on(id, (val: boolean) => {
|
|
78
|
+
this.send({ payload: val });
|
|
79
|
+
if (response) {
|
|
80
|
+
curentState.state.value = val;
|
|
81
|
+
device.updateCapabState(id, curentState)
|
|
82
|
+
.then(() => {
|
|
83
|
+
this.status({ fill: "green", shape: "dot", text: val.toString() });
|
|
84
|
+
})
|
|
85
|
+
.catch(err => {
|
|
86
|
+
this.error("Error on update capability state: " + err.message);
|
|
87
|
+
this.status({ fill: "red", shape: "dot", text: "Error" });
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
this.on('input', (msg, _send, done) => {
|
|
93
|
+
if (typeof msg.payload != 'boolean') {
|
|
94
|
+
this.error("Wrong type! msg.payload must be boolean.");
|
|
95
|
+
if (done) { done(); }
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (msg.payload === curentState.state.value) {
|
|
99
|
+
this.debug("Value not changed. Cancel update");
|
|
100
|
+
if (done) { done(); }
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
curentState.state.value = msg.payload;
|
|
104
|
+
device.updateCapabState(id, curentState)
|
|
105
|
+
.then(() => {
|
|
106
|
+
this.status({ fill: "green", shape: "dot", text: String(msg.payload) });
|
|
107
|
+
if (done) { done(); }
|
|
108
|
+
})
|
|
109
|
+
.catch(err => {
|
|
110
|
+
this.error("Error on update capability state: " + err.message);
|
|
111
|
+
this.status({ fill: "red", shape: "dot", text: "Error" });
|
|
112
|
+
if (done) { done(); }
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
this.on('close', (removed: boolean, done: () => void) => {
|
|
117
|
+
device.setMaxListeners(device.getMaxListeners() - 1);
|
|
118
|
+
if (removed) {
|
|
119
|
+
device.delCapability(id)
|
|
120
|
+
.then(() => { done(); })
|
|
121
|
+
.catch(err => {
|
|
122
|
+
this.error("Error on delete capability: " + err.message);
|
|
123
|
+
done();
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
done();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
RED.nodes.registerType("On_Off", AliceOnOff);
|
|
132
|
+
};
|