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,301 @@
|
|
|
1
|
+
import { NodeAPI } from "node-red";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import pjson from "../package.json";
|
|
4
|
+
import {
|
|
5
|
+
AliceDeviceConfig,
|
|
6
|
+
AliceDeviceNode,
|
|
7
|
+
AliceServiceNode,
|
|
8
|
+
CapabilityRegistration,
|
|
9
|
+
CapabilityState,
|
|
10
|
+
SensorRegistration,
|
|
11
|
+
SensorState,
|
|
12
|
+
DeviceConfig,
|
|
13
|
+
DeviceStates
|
|
14
|
+
} from "./types.js";
|
|
15
|
+
|
|
16
|
+
export = (RED: NodeAPI): void => {
|
|
17
|
+
function AliceDevice(this: AliceDeviceNode, config: AliceDeviceConfig): void {
|
|
18
|
+
RED.nodes.createNode(this, config);
|
|
19
|
+
const service = RED.nodes.getNode(config.service) as AliceServiceNode;
|
|
20
|
+
service.setMaxListeners(service.getMaxListeners() + 1);
|
|
21
|
+
|
|
22
|
+
this.initState = false;
|
|
23
|
+
let updating = false;
|
|
24
|
+
let needSendEvent = false;
|
|
25
|
+
const capabilites: Record<string, string> = {};
|
|
26
|
+
const sensors: Record<string, string> = {};
|
|
27
|
+
|
|
28
|
+
const deviceconfig: DeviceConfig = {
|
|
29
|
+
id: this.id,
|
|
30
|
+
name: config.name,
|
|
31
|
+
description: config.description,
|
|
32
|
+
room: config.room,
|
|
33
|
+
type: config.dtype,
|
|
34
|
+
device_info: {
|
|
35
|
+
manufacturer: "NodeRed Home",
|
|
36
|
+
model: "virtual device",
|
|
37
|
+
sw_version: pjson.version
|
|
38
|
+
},
|
|
39
|
+
capabilities: [],
|
|
40
|
+
properties: []
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const states: DeviceStates = {
|
|
44
|
+
id: this.id,
|
|
45
|
+
capabilities: [],
|
|
46
|
+
properties: []
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (service.isOnline) {
|
|
50
|
+
this.emit("online");
|
|
51
|
+
this.initState = true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// функция обновления информации об устройстве
|
|
55
|
+
const _updateDeviceInfo = (): void => {
|
|
56
|
+
if (deviceconfig.capabilities.length == 0 && deviceconfig.properties.length == 0) {
|
|
57
|
+
this.debug("DELETE Device config from gateway ...");
|
|
58
|
+
axios.request({
|
|
59
|
+
timeout: 5000,
|
|
60
|
+
method: 'POST',
|
|
61
|
+
url: 'https://api.nodered-home.ru/gtw/device/config',
|
|
62
|
+
headers: {
|
|
63
|
+
'content-type': 'application/json',
|
|
64
|
+
'Authorization': "Bearer " + service.getToken()
|
|
65
|
+
},
|
|
66
|
+
data: {
|
|
67
|
+
id: this.id,
|
|
68
|
+
config: deviceconfig
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
.then(() => {
|
|
72
|
+
this.trace("Device config deleted on gateway successfully");
|
|
73
|
+
})
|
|
74
|
+
.catch(error => {
|
|
75
|
+
this.debug("Error when delete device config on gateway: " + error.message);
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!updating) {
|
|
81
|
+
updating = true;
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
this.debug("Updating Device config ...");
|
|
84
|
+
updating = false;
|
|
85
|
+
axios.request({
|
|
86
|
+
timeout: 5000,
|
|
87
|
+
method: 'POST',
|
|
88
|
+
url: 'https://api.nodered-home.ru/gtw/device/config',
|
|
89
|
+
headers: {
|
|
90
|
+
'content-type': 'application/json',
|
|
91
|
+
'Authorization': "Bearer " + service.getToken()
|
|
92
|
+
},
|
|
93
|
+
data: {
|
|
94
|
+
id: this.id,
|
|
95
|
+
config: deviceconfig
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
.then(() => {
|
|
99
|
+
this.trace("Device config updated successfully");
|
|
100
|
+
})
|
|
101
|
+
.catch(error => {
|
|
102
|
+
this.debug("Error when update device config: " + error.message);
|
|
103
|
+
});
|
|
104
|
+
}, 1000);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// функция обновления состояния устройства (умений и сенсоров)
|
|
109
|
+
const _updateDeviceState = (event: Record<string, any> | null = null): void => {
|
|
110
|
+
axios.request({
|
|
111
|
+
timeout: 5000,
|
|
112
|
+
method: 'POST',
|
|
113
|
+
url: 'https://api.nodered-home.ru/gtw/device/state',
|
|
114
|
+
headers: {
|
|
115
|
+
'content-type': 'application/json',
|
|
116
|
+
'Authorization': "Bearer " + service.getToken()
|
|
117
|
+
},
|
|
118
|
+
data: {
|
|
119
|
+
id: this.id,
|
|
120
|
+
event: event,
|
|
121
|
+
state: states
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
.then(() => {
|
|
125
|
+
this.trace("Device state updated successfully");
|
|
126
|
+
})
|
|
127
|
+
.catch(error => {
|
|
128
|
+
this.debug("Error when update device state: " + error.message);
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// отправка эвентов
|
|
133
|
+
const _sendEvent = (event: CapabilityState): void => {
|
|
134
|
+
const data = JSON.stringify(event);
|
|
135
|
+
service.send2gate('$me/device/events/' + this.id, data, false);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Установка параметров умения
|
|
139
|
+
this.setCapability = (capId: string, capab: CapabilityRegistration): Promise<boolean> => {
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
const instance = capab.parameters.instance || '';
|
|
142
|
+
const capabIndex = capab.type + "." + instance;
|
|
143
|
+
if (capabilites[capabIndex] && capabilites[capabIndex] != capId) {
|
|
144
|
+
reject(new Error("Dublicated capability on same device!"));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// проверям было ли такое умение раньше и удаляем перед обновлением
|
|
148
|
+
if (deviceconfig.capabilities.findIndex(a => a.id === capId) > -1) {
|
|
149
|
+
this.delCapability(capId);
|
|
150
|
+
}
|
|
151
|
+
capabilites[capabIndex] = capId;
|
|
152
|
+
capab.id = capId;
|
|
153
|
+
deviceconfig.capabilities.push(capab);
|
|
154
|
+
_updateDeviceInfo();
|
|
155
|
+
resolve(true);
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Установка параметров сенсора
|
|
160
|
+
this.setSensor = (sensId: string, sensor: SensorRegistration): Promise<boolean> => {
|
|
161
|
+
return new Promise((resolve, reject) => {
|
|
162
|
+
const sensorIndex = sensor.type + "." + sensor.parameters.instance;
|
|
163
|
+
if (sensors[sensorIndex] && sensors[sensorIndex] != sensId) {
|
|
164
|
+
reject(new Error("Dublicated sensor on same device!"));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// проверяем было ли такой сенсор раньше и удаляем перед обновлением
|
|
168
|
+
if (deviceconfig.properties.findIndex(a => a.id === sensId) > -1) {
|
|
169
|
+
this.delSensor(sensId);
|
|
170
|
+
}
|
|
171
|
+
sensors[sensorIndex] = sensId;
|
|
172
|
+
sensor.id = sensId;
|
|
173
|
+
deviceconfig.properties.push(sensor);
|
|
174
|
+
_updateDeviceInfo();
|
|
175
|
+
resolve(true);
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// обновление текущего state умения
|
|
180
|
+
this.updateCapabState = (capId: string, state: CapabilityState): Promise<boolean> => {
|
|
181
|
+
return new Promise((resolve) => {
|
|
182
|
+
state.id = capId;
|
|
183
|
+
if (needSendEvent) {
|
|
184
|
+
_sendEvent(state);
|
|
185
|
+
}
|
|
186
|
+
const index = states.capabilities.findIndex(a => a.id === capId);
|
|
187
|
+
if (index > -1) {
|
|
188
|
+
states.capabilities.splice(index, 1);
|
|
189
|
+
}
|
|
190
|
+
states.capabilities.push(state);
|
|
191
|
+
const currentevent = {
|
|
192
|
+
id: this.id,
|
|
193
|
+
capabilities: [state]
|
|
194
|
+
};
|
|
195
|
+
_updateDeviceState(currentevent);
|
|
196
|
+
resolve(true);
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// обновление текущего state сенсора
|
|
201
|
+
this.updateSensorState = (sensID: string, state: SensorState): Promise<boolean> => {
|
|
202
|
+
return new Promise((resolve) => {
|
|
203
|
+
state.id = sensID;
|
|
204
|
+
const index = states.properties.findIndex(a => a.id === sensID);
|
|
205
|
+
if (index > -1) {
|
|
206
|
+
states.properties.splice(index, 1);
|
|
207
|
+
}
|
|
208
|
+
states.properties.push(state);
|
|
209
|
+
const currentevent = {
|
|
210
|
+
id: this.id,
|
|
211
|
+
properties: [state]
|
|
212
|
+
};
|
|
213
|
+
_updateDeviceState(currentevent);
|
|
214
|
+
resolve(true);
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// удаление умения
|
|
219
|
+
this.delCapability = (capId: string): Promise<boolean> => {
|
|
220
|
+
return new Promise((resolve) => {
|
|
221
|
+
const index = deviceconfig.capabilities.findIndex(a => a.id === capId);
|
|
222
|
+
if (index > -1) {
|
|
223
|
+
deviceconfig.capabilities.splice(index, 1);
|
|
224
|
+
}
|
|
225
|
+
const capabIndex = Object.keys(capabilites).find(key => capabilites[key] === capId);
|
|
226
|
+
if (capabIndex) delete capabilites[capabIndex];
|
|
227
|
+
_updateDeviceInfo();
|
|
228
|
+
const stateindex = states.capabilities.findIndex(a => a.id === capId);
|
|
229
|
+
if (stateindex > -1) {
|
|
230
|
+
states.capabilities.splice(stateindex, 1);
|
|
231
|
+
}
|
|
232
|
+
_updateDeviceState();
|
|
233
|
+
resolve(true);
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// удаление сенсора
|
|
238
|
+
this.delSensor = (sensID: string): Promise<boolean> => {
|
|
239
|
+
return new Promise((resolve) => {
|
|
240
|
+
const index = deviceconfig.properties.findIndex(a => a.id === sensID);
|
|
241
|
+
if (index > -1) {
|
|
242
|
+
deviceconfig.properties.splice(index, 1);
|
|
243
|
+
}
|
|
244
|
+
const sensorIndex = Object.keys(sensors).find(key => sensors[key] === sensID);
|
|
245
|
+
if (sensorIndex) delete sensors[sensorIndex];
|
|
246
|
+
_updateDeviceInfo();
|
|
247
|
+
const stateindex = states.properties.findIndex(a => a.id === sensID);
|
|
248
|
+
if (stateindex > -1) {
|
|
249
|
+
states.properties.splice(stateindex, 1);
|
|
250
|
+
}
|
|
251
|
+
_updateDeviceState();
|
|
252
|
+
resolve(true);
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
service.on("online", () => {
|
|
257
|
+
this.debug("Received a signal online from the service");
|
|
258
|
+
this.emit("online");
|
|
259
|
+
this.initState = true;
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
service.on("offline", () => {
|
|
263
|
+
this.debug("Received a signal offline from the service");
|
|
264
|
+
this.emit("offline");
|
|
265
|
+
this.initState = false;
|
|
266
|
+
this.status({ fill: "red", shape: "dot", text: "offline" });
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
service.on(this.id, (incomingStates: any[]) => {
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
needSendEvent = false;
|
|
272
|
+
}, 2000);
|
|
273
|
+
needSendEvent = true;
|
|
274
|
+
incomingStates.forEach(cap => {
|
|
275
|
+
let capabIndex = cap.type + "." + cap.state.instance;
|
|
276
|
+
if (cap.type === "devices.capabilities.color_setting") {
|
|
277
|
+
capabIndex = cap.type + ".";
|
|
278
|
+
}
|
|
279
|
+
const capId = capabilites[capabIndex];
|
|
280
|
+
this.emit(capId, cap.state.value, cap.state);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
this.on('close', (removed: boolean, done: () => void) => {
|
|
285
|
+
this.emit('offline');
|
|
286
|
+
if (removed) {
|
|
287
|
+
deviceconfig.capabilities = [];
|
|
288
|
+
deviceconfig.properties = [];
|
|
289
|
+
states.capabilities = [];
|
|
290
|
+
states.properties = [];
|
|
291
|
+
_updateDeviceState();
|
|
292
|
+
_updateDeviceInfo();
|
|
293
|
+
}
|
|
294
|
+
setTimeout(() => {
|
|
295
|
+
done();
|
|
296
|
+
}, 500);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
RED.nodes.registerType("alice-device", AliceDevice);
|
|
301
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('Event',{
|
|
3
|
+
category: 'alice',
|
|
4
|
+
defaults:{
|
|
5
|
+
device: {value:"", type:"alice-device"},
|
|
6
|
+
name: {value:"Event"},
|
|
7
|
+
stype: {value:'devices.properties.event'},
|
|
8
|
+
instance: {value:undefined, validate: (v)=>{
|
|
9
|
+
if (v){
|
|
10
|
+
return true;
|
|
11
|
+
}else{
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}},
|
|
15
|
+
events:{value:[]}
|
|
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").typedInput({
|
|
26
|
+
types: [
|
|
27
|
+
{
|
|
28
|
+
options: [
|
|
29
|
+
{ value: "vibration", label: "Vibration"},
|
|
30
|
+
{ value: "open", label: "Open"},
|
|
31
|
+
{ value: "button", label: "Button"},
|
|
32
|
+
{ value: "motion", label: "Motion"},
|
|
33
|
+
{ value: "smoke", label: "Smoke"},
|
|
34
|
+
{ value: "gas", label: "Gas"},
|
|
35
|
+
{ value: "battery_level", label: "Battery level"},
|
|
36
|
+
{ value: "water_level", label: "Water level"},
|
|
37
|
+
{ value: "water_leak", label: "Water leak"}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
});
|
|
42
|
+
$('#node-input-instance').on('change',()=>{
|
|
43
|
+
let val= $("#node-input-instance").val();
|
|
44
|
+
$("#node-input-eventslist").editableList('empty');
|
|
45
|
+
|
|
46
|
+
switch (val) {
|
|
47
|
+
case "vibration":
|
|
48
|
+
$("#node-input-eventslist").editableList('addItems',["tilt","fall","vibration"]);
|
|
49
|
+
break;
|
|
50
|
+
case "open":
|
|
51
|
+
$("#node-input-eventslist").editableList('addItems',["opened","closed"]);
|
|
52
|
+
break;
|
|
53
|
+
case "button":
|
|
54
|
+
$("#node-input-eventslist").editableList('addItems',["click","double_click","long_press"]);
|
|
55
|
+
break;
|
|
56
|
+
case "motion":
|
|
57
|
+
$("#node-input-eventslist").editableList('addItems',["detected","not_detected"]);
|
|
58
|
+
break;
|
|
59
|
+
case "smoke":
|
|
60
|
+
$("#node-input-eventslist").editableList('addItems',["detected","not_detected","high"]);
|
|
61
|
+
break;
|
|
62
|
+
case "gas":
|
|
63
|
+
$("#node-input-eventslist").editableList('addItems',["detected","not_detected","high"]);
|
|
64
|
+
break;
|
|
65
|
+
case "battery_level":
|
|
66
|
+
$("#node-input-eventslist").editableList('addItems',["low","normal"]);
|
|
67
|
+
break;
|
|
68
|
+
case "water_level":
|
|
69
|
+
$("#node-input-eventslist").editableList('addItems',["low","normal"]);
|
|
70
|
+
break;
|
|
71
|
+
case "water_leak":
|
|
72
|
+
$("#node-input-eventslist").editableList('addItems',["dry","leak"]);
|
|
73
|
+
break;
|
|
74
|
+
default:
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
$("#node-input-eventslist").editableList({
|
|
79
|
+
addButton: false,
|
|
80
|
+
height: 150,
|
|
81
|
+
addItem: function(container,i,opt){
|
|
82
|
+
var row = $('<div style="padding-left: 3px; class="event_name">'+opt+'</div>').appendTo(container);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
oneditsave: function(){
|
|
87
|
+
deivcename = $('#node-input-device option:selected').text();
|
|
88
|
+
instance = $("#node-input-instance").val();
|
|
89
|
+
$('#node-input-name').val(deivcename+":"+instance);
|
|
90
|
+
let events = $("#node-input-eventslist").editableList('items');
|
|
91
|
+
let node = this;
|
|
92
|
+
node.events= [];
|
|
93
|
+
events.each(function(i) {
|
|
94
|
+
let name = $(this).text();
|
|
95
|
+
node.events.push(name);
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
<script type="text/x-red" data-template-name="Event">
|
|
102
|
+
<input type="hidden" id="node-input-name">
|
|
103
|
+
<div class="form-row">
|
|
104
|
+
<label for="node-input-device">Device</label>
|
|
105
|
+
<input id="node-input-device">
|
|
106
|
+
</div>
|
|
107
|
+
<div class="form-row">
|
|
108
|
+
<label for="node-input-instance">Event Type</label>
|
|
109
|
+
<input type="text" id="node-input-instance">
|
|
110
|
+
</div>
|
|
111
|
+
<div class="form-row node-input-rule-container-row">
|
|
112
|
+
<label for="node-input-modes" style="width:auto">Supported events</label>
|
|
113
|
+
<ol id="node-input-eventslist"></ol>
|
|
114
|
+
</div>
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
<script type="text/x-red" data-help-name="Event">
|
|
118
|
+
<p>Transmits event from sensors built into the device.</p>
|
|
119
|
+
|
|
120
|
+
<h3>Property</h3>
|
|
121
|
+
<dl class="message-properties">
|
|
122
|
+
<dt>Device
|
|
123
|
+
<span class="property-type">Select</span>
|
|
124
|
+
</dt>
|
|
125
|
+
<dd> The device to which this sensor is connected </dd>
|
|
126
|
+
<dt>Event Type
|
|
127
|
+
<span class="property-type">Select</span>
|
|
128
|
+
</dt>
|
|
129
|
+
<dd>a type of event that helps to better understand the area of its use and the data it collects</dd>
|
|
130
|
+
<dt>Supported events
|
|
131
|
+
<span class="property-type">Read only</span>
|
|
132
|
+
</dt>
|
|
133
|
+
<dd>list of events that the sensor can send</dd>
|
|
134
|
+
</dl>
|
|
135
|
+
|
|
136
|
+
<h3>Inputs</h3>
|
|
137
|
+
<dl class="message-properties">
|
|
138
|
+
<dt>payload
|
|
139
|
+
<span class="property-type">string</span>
|
|
140
|
+
</dt>
|
|
141
|
+
<dd>events can only be from the allowed list</dd>
|
|
142
|
+
</dl>
|
|
143
|
+
|
|
144
|
+
<h3>References</h3>
|
|
145
|
+
<ul>
|
|
146
|
+
<li><a href="https://yandex.ru/dev/dialogs/smart-home/doc/concepts/event.html"> - Yandex documentation</a></li>
|
|
147
|
+
</ul>
|
|
148
|
+
</script>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { NodeAPI, Node } from "node-red";
|
|
2
|
+
import { AliceEventConfig, AliceDeviceNode, SensorState } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export = (RED: NodeAPI): void => {
|
|
5
|
+
function AliceEvent(this: Node, config: AliceEventConfig): 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 = 'devices.properties.event';
|
|
12
|
+
const instance = config.instance;
|
|
13
|
+
const events = config.events;
|
|
14
|
+
let initState = false;
|
|
15
|
+
|
|
16
|
+
const curentState: SensorState = {
|
|
17
|
+
type: stype,
|
|
18
|
+
state: {
|
|
19
|
+
instance: instance,
|
|
20
|
+
value: ''
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
this.status({ fill: "red", shape: "dot", text: "offline" });
|
|
25
|
+
|
|
26
|
+
const init = (): void => {
|
|
27
|
+
this.debug("Starting sensor initilization ...");
|
|
28
|
+
const objEvents = events.map(v => ({ value: v }));
|
|
29
|
+
const sensor = {
|
|
30
|
+
type: stype,
|
|
31
|
+
reportable: true,
|
|
32
|
+
retrievable: false,
|
|
33
|
+
parameters: {
|
|
34
|
+
instance: instance,
|
|
35
|
+
events: objEvents
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
device.setSensor(id, sensor)
|
|
40
|
+
.then(() => {
|
|
41
|
+
this.debug("Sensor initilization - success!");
|
|
42
|
+
this.status({ fill: "green", shape: "dot", text: "online" });
|
|
43
|
+
initState = true;
|
|
44
|
+
})
|
|
45
|
+
.catch(err => {
|
|
46
|
+
this.error("Error on create sensor: " + err.message);
|
|
47
|
+
this.status({ fill: "red", shape: "dot", text: "error" });
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (device.initState) init();
|
|
52
|
+
|
|
53
|
+
device.on("online", () => {
|
|
54
|
+
if (!initState) {
|
|
55
|
+
init();
|
|
56
|
+
} else {
|
|
57
|
+
this.status({ fill: "green", shape: "dot", text: curentState.state.value });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
device.on("offline", () => {
|
|
62
|
+
this.status({ fill: "red", shape: "dot", text: "offline" });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.on('input', (msg, _send, done) => {
|
|
66
|
+
if (!events.includes(msg.payload as string)) {
|
|
67
|
+
this.error("Wrong type! msg.payload must be from the list of allowed events.");
|
|
68
|
+
if (done) { done(); }
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (curentState.state.value == msg.payload) {
|
|
72
|
+
this.debug("Value not changed. Cancel update");
|
|
73
|
+
if (done) { done(); }
|
|
74
|
+
return;
|
|
75
|
+
} else {
|
|
76
|
+
curentState.state.value = msg.payload;
|
|
77
|
+
}
|
|
78
|
+
// для кнопок обнуляем значение через 1 сек
|
|
79
|
+
if (instance == 'button') {
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
curentState.state.value = null;
|
|
82
|
+
this.status({ fill: "green", shape: "dot", text: "" });
|
|
83
|
+
}, 1000);
|
|
84
|
+
}
|
|
85
|
+
device.updateSensorState(id, curentState)
|
|
86
|
+
.then(() => {
|
|
87
|
+
this.status({ fill: "green", shape: "dot", text: curentState.state.value });
|
|
88
|
+
if (done) { done(); }
|
|
89
|
+
})
|
|
90
|
+
.catch(err => {
|
|
91
|
+
this.error("Error on update sensor state: " + err.message);
|
|
92
|
+
this.status({ fill: "red", shape: "dot", text: "Error" });
|
|
93
|
+
if (done) { done(); }
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
this.on('close', (removed: boolean, done: () => void) => {
|
|
98
|
+
if (removed) {
|
|
99
|
+
device.delSensor(id)
|
|
100
|
+
.then(() => { done(); })
|
|
101
|
+
.catch(err => {
|
|
102
|
+
this.error("Error on delete property: " + err.message);
|
|
103
|
+
done();
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
done();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
RED.nodes.registerType("Event", AliceEvent);
|
|
112
|
+
};
|
package/src/alice-get.html
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script type="text/javascript">
|
|
2
|
+
let udyaconfig={};
|
|
2
3
|
RED.nodes.registerType('Alice-Get',{
|
|
3
4
|
category: 'alice',
|
|
4
5
|
inputs:1,
|
|
@@ -7,24 +8,84 @@
|
|
|
7
8
|
color: "#D8BFD8",
|
|
8
9
|
defaults:{
|
|
9
10
|
service: {value:"", type:"alice-service"},
|
|
10
|
-
|
|
11
|
+
device: {value:undefined}
|
|
11
12
|
},
|
|
12
13
|
label: function(){
|
|
13
14
|
return this.name || "Alice-Get";
|
|
14
15
|
},
|
|
15
16
|
oneditprepare: ()=>{
|
|
16
|
-
|
|
17
|
+
$("#node-input-service").change(function(e){
|
|
18
|
+
if (this.value &&this.value!='_ADD_'){
|
|
19
|
+
$.ajax({
|
|
20
|
+
url: "/noderedhome/"+this.value+"/getfullconfig",
|
|
21
|
+
type:"GET"
|
|
22
|
+
})
|
|
23
|
+
.done(result=>{
|
|
24
|
+
// RED.notify("Full Alice SmartHome config successfully retrieved", {type:"success"});
|
|
25
|
+
console.log(result);
|
|
26
|
+
udyaconfig=result;
|
|
27
|
+
updateHouse(result.households);
|
|
28
|
+
})
|
|
29
|
+
.fail(error=>{
|
|
30
|
+
RED.notify("Error when retrieve Alice SmartHome config", {type:"error"});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
$('#node-input-home')
|
|
35
|
+
.prop('disabled', 'disabled')
|
|
36
|
+
.change((e)=>{
|
|
37
|
+
let val = $('#node-input-home').find(":selected").val();
|
|
38
|
+
console.log(val);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
$('#node-input-room').prop('disabled', 'disabled');
|
|
42
|
+
$('#node-input-device').prop('disabled', 'disabled');
|
|
43
|
+
|
|
17
44
|
}
|
|
18
|
-
})
|
|
45
|
+
});
|
|
46
|
+
function updateHouse(data){
|
|
47
|
+
$('#node-input-home')
|
|
48
|
+
.find('option')
|
|
49
|
+
.remove()
|
|
50
|
+
.end();
|
|
51
|
+
udyaconfig.households.forEach(h => {
|
|
52
|
+
$('#node-input-home')
|
|
53
|
+
.append('<option value="'+h.id+'">'+h.name+'</option>');
|
|
54
|
+
});
|
|
55
|
+
$('#node-input-home')
|
|
56
|
+
.prop('disabled', false);
|
|
57
|
+
};
|
|
58
|
+
function updaterooms(house){
|
|
59
|
+
$('#node-input-room')
|
|
60
|
+
.find('option')
|
|
61
|
+
.remove()
|
|
62
|
+
.end();
|
|
63
|
+
udyaconfig.households.forEach(h => {
|
|
64
|
+
if (h.household_id==house){
|
|
65
|
+
$('#node-input-room')
|
|
66
|
+
.append('<option value="'+h.id+'">'+h.name+'</option>');
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
$('#node-input-room').prop('disabled', false);
|
|
70
|
+
};
|
|
71
|
+
|
|
19
72
|
</script>
|
|
20
73
|
|
|
21
74
|
<script type="text/x-red" data-template-name="Alice-Get">
|
|
22
75
|
<div class="form-row">
|
|
23
|
-
<label for="node-input-service">
|
|
76
|
+
<label for="node-input-service">Account</label>
|
|
24
77
|
<input id="node-input-service">
|
|
25
78
|
</div>
|
|
26
79
|
<div class="form-row">
|
|
27
|
-
<label for="node-
|
|
28
|
-
<
|
|
80
|
+
<label for="node-input-home">Home</label>
|
|
81
|
+
<select id="node-input-home" style="width: 70%;"></select>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="form-row">
|
|
84
|
+
<label for="node-input-room">Room</label>
|
|
85
|
+
<select id="node-input-room" style="width: 70%;"></select>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="form-row">
|
|
88
|
+
<label for="node-input-device">Room</label>
|
|
89
|
+
<select id="node-input-device" style="width: 70%;"></select>
|
|
29
90
|
</div>
|
|
30
91
|
</script>
|
package/src/alice-get.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import {NodeAPI, Node, NodeDef } from "node-red";
|
|
2
|
-
import axios from "axios";
|
|
1
|
+
import { NodeAPI, Node, NodeDef } from "node-red";
|
|
3
2
|
|
|
3
|
+
interface NodeAliceGetConfig extends NodeDef {
|
|
4
|
+
service: string;
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export = (RED: NodeAPI):void =>{
|
|
12
|
-
function AliceGet(this:Node, config:NodeAliceGetConfig):void {
|
|
13
|
-
RED.nodes.createNode(this,config);
|
|
14
|
-
const service = RED.nodes.getNode(config.service);
|
|
15
|
-
};
|
|
8
|
+
export = (RED: NodeAPI): void => {
|
|
9
|
+
function AliceGet(this: Node, config: NodeAliceGetConfig): void {
|
|
10
|
+
RED.nodes.createNode(this, config);
|
|
11
|
+
const _service = RED.nodes.getNode(config.service);
|
|
12
|
+
}
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
}
|
|
14
|
+
RED.nodes.registerType("Alice-Get", AliceGet);
|
|
15
|
+
};
|