-
Notifications
You must be signed in to change notification settings - Fork 0
/
zwave_node.js
211 lines (172 loc) · 4.75 KB
/
zwave_node.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import {async_mutex, hex_bytes} from "./zwave_utils.js"
import {zwave_cc} from "./zwave_cc.js"
/*
* This class handles sending and receiving commands to a specific node according to the command class definitions
*/
export class zwave_node {
constructor(z, nodeid) {
this.z = z;
this.nodeid = nodeid;
this.mutex = new async_mutex();
// gen proxy
this.gen = new Proxy(this, zwave_node.gen_proxy_handler);
// this object will be populated with receive callbacks
this.recv = {
_ep: [],
ep(epid) {
if (!this._ep[epid]) {
this._ep[epid] = {};
}
return this._ep[epid];
}
};
}
static gen_proxy_handler = {
get(node, cmd_name) {
const cmd_def = zwave_cc._cmd_name_map.get(cmd_name);
if (cmd_def?.encode) {
const cmd = node.new_cmd(cmd_def);
return async function (args) {
cmd.args = args;
await cmd_def.encode(cmd);
return cmd;
}
}
}
}
get send() {
const send = {
node: this,
ep(epid) {this.epid = epid; return this.proxy}
};
send.proxy = new Proxy(send, zwave_node.send_proxy_handler);
return send.proxy;
}
static send_proxy_handler = {
get(send, cmd_name) {
const cmd_def = zwave_cc._cmd_name_map.get(cmd_name);
if (cmd_def?.encode) {
const cmd = send.node.new_cmd(cmd_def, send.epid);
return function (args = {}) {
cmd.args = args;
const promise = send.node.run_cmd(cmd);
const recv = {promise, cmd};
// return the async function promise, but allow cmd modification using recv_proxy_handler
promise.recv = new Proxy(recv, zwave_node.recv_proxy_handler);
return promise;
};
}
// default to support node, ep, s0 properties
return Reflect.get(...arguments);
}
}
static recv_proxy_handler = {
get(recv, property) {
const cmd_def = zwave_cc._cmd_name_map.get(property);
if (cmd_def?.decode) {
recv.cmd.recv_cmd_def = cmd_def;
return function (timeout = 1) {
recv.cmd.recv_timeout = timeout;
return recv.promise;
}
}
}
};
new_cmd(cmd_def, epid) {
return {
node: this,
def: cmd_def,
id: [cmd_def.cc_id, cmd_def.id],
msg: [cmd_def.name],
epid: epid
}
}
async run_cmd(cmd) {
// this await also allows cmd modification using recv_proxy_handler before we continue
await cmd.def.encode(cmd);
const cmd_orig = cmd;
// encapsulate
if (cmd_orig.epid > 0) {
cmd = await zwave_cc.MULTI_CHANNEL.encapsulate(cmd);
}
if (this.security) {
cmd = await this.security.cc.encapsulate(cmd);
if (typeof(cmd) == "string") {
return this.error(cmd, cmd_orig);
}
}
// send to node only
if (!cmd_orig.recv_cmd_def) {
return await this.z.bridge_node_send(cmd);
}
// send/recv flow
await this.mutex.lock();
this.cmd_current = cmd_orig;
const result = await this.send_recv_cmd(cmd);
delete this.cmd_current;
this.mutex.unlock();
if (typeof(result) == "string") {
return this.error(result, cmd_orig);
}
return result;
}
error(msg, cmd) {
this.z.log_func("ERROR:", msg, "| node:" + this.nodeid, ...cmd.msg);
this.z.log_func();
return false;
}
async send_recv_cmd(cmd) {
// retrieve original non-encapsulated command
const cmd_orig = this.cmd_current;
// setup promise for response or error
const promise = new Promise((resolve) => {cmd_orig.resolve = resolve});
// send request
if (!await this.z.bridge_node_send(cmd)) {
cmd_orig.resolve("send failed");
}
// start timer
const cmd_timeout = setTimeout(cmd_orig.resolve.bind(null, "timeout"), 1000 * (cmd_orig.recv_timeout ?? 1));
// wait for response (args object) or error (string)
const result = await promise;
clearTimeout(cmd_timeout);
return result;
}
async recv_cmd(cmd) {
cmd.node = this;
while (true) {
// decode
var cmd_def = zwave_cc._cc_id_map.get(cmd.id[0])?._cmd_id_map.get(cmd.id[1]);
if (!cmd_def?.decode) {
cmd.msg.push("unsupported command for receive:", hex_bytes(cmd.id));
return;
}
cmd.msg.push(cmd_def.name);
await cmd_def.decode(cmd);
if (!cmd.args) {
return;
}
if (!cmd.args.cmd) {
// not encapuslated
break;
}
// encapsulated - replace id/pld and repeat
cmd.id = cmd.args.cmd.id;
cmd.pld = cmd.args.cmd.pld;
delete cmd.args;
}
// check if should be secure
if (this.security && !cmd_def.no_encap && !cmd.secure) {
cmd.msg.push("(not secure - ignored)");
return;
}
// check if this matches an expected command
const cmd_current = this.cmd_current;
if (cmd_current && (cmd_current.recv_cmd_def == cmd_def) && (cmd_current.epid == cmd.epid)) {
cmd_current.resolve(cmd.args);
} else {
// user callback
let recv = cmd.epid ? this.recv.ep(cmd.epid) : this.recv;
recv[cmd_def.name]?.(cmd.args);
}
}
}