-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathreceive.js
189 lines (179 loc) · 8.68 KB
/
receive.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
/*
** Receive Functions
*/
var utils = require('./utils.js');
var config = require('./config.js');
var send = require('./send.js');
var insteon = require('./insteon.js');
var trans_queue = config.trans_queue;
/*
** handler: registered event handler for any data from serialport
*/
var serialport_handler = exports.serialport_handler = function serialport_handler(data) {
console.log('serialport_handler::read serial hex: '+utils.byteArrayToHexStringArray(data));
var insteonMsg = utils.insteonJS(data);
console.log('serialport_handler::incoming insteon message:');
console.log(insteonMsg);
processMsg(insteonMsg);
};
/*
** processMsg: determine message type and handle message accordingly
*/
var processMsg = exports.processMsg = function processMsg(insteonMsg) {
expired_count = 0; // message recd, reset expire counter
// error handling
if(insteonMsg.error) {
console.log('processMsg::INSTEON message error ('+insteonMsg.error+')');
//console.log(insteonMsg);
if(insteonMsg.dec[0] == config.INSTEON_PLM_NAK) {
if(!config.PLM_BUSY) {
console.log('processMsg::PLM_BUSY detected; set PLM_BUSY for 3s');
setTimeout(function(){ plm_wait(); }, 3000);
config.PLM_BUSY = true;
}
if(config.auto_sync) {
config.mode = 'queue_all';
console.log('process::auto_sync set to queue_all mode');
}
}
return;
} else {
if(config.auto_sync && config.mode != 'queue_device_msgs') {
config.mode = 'queue_device_msgs';
console.log('process::auto_sync returned to queue_device_msgs mode');
}
}
// match sent messages to received messaged; complete transactions
var matched = false;
switch(insteonMsg.cmd) {
case config.INSTEON_RECV_STANDARD:
console.log('process::incoming INSTEON standard message');
var flags = utils.getMessageFlagsByHex(insteonMsg.message_flags);
if(flags.type == 'ACK of Direct Message') matched = match(insteonMsg);
break;
case config.INSTEON_RECV_EXTENDED:
console.log('process::incoming INSTEON extended message');
var flags = utils.getMessageFlagsByHex(insteonMsg.message_flags);
if(flags.type == 'ACK of Direct Message') matched = match(insteonMsg);
break;
case config.INSTEON_RECV_X10: // do nothing
console.log('process::incoming X10 message');
break;
default: // plm message
console.log('process::incoming PLM message');
matched = match(insteonMsg);
break;
}
// unsolicited or unhandled messages
if(!matched) {
var date = new Date();
var transaction = {
id: date.getTime(),
request: null,
response: insteonMsg,
state: 'INCOMING',
cmd: insteonMsg.cmd,
};
config.eventEmitter.emit('message', transaction); // trigger new message event
}
process.nextTick(send.dequeue);
};
/*
** match: check incoming message against the queue and update state accordingly
*/
var match = exports.match = function match(insteonMsg) {
for(var i = trans_queue.length - 1; i >= 0; i--) {
// INSTEON response - match inbound fromdeviceid to last PLM_ACK'd todeviceid; dequeue() promises only 1 outbound msg to a given deviceid at a time so this is safe
if((insteonMsg.cmd == config.INSTEON_RECV_STANDARD || insteonMsg.cmd == config.INSTEON_RECV_EXTENDED)
&& trans_queue[i].type == 'INSTEON' && trans_queue[i].state == 'PLM_ACK') {
var flags = utils.getMessageFlagsByHex(insteonMsg.message_flags);
var toDeviceId = utils.byteArrayToHexStringArray(trans_queue[i].request.slice(2,5));
if(flags.type == 'ACK of Direct Message' && utils.compareArray(insteonMsg.from, toDeviceId)) {
trans_queue[i].state = 'COMPLETE';
trans_queue[i].response = insteonMsg;
console.log('match::INSTEON transaction ('+trans_queue[i].id+') state updated to COMPLETE');
config.eventEmitter.emit('message', trans_queue[i]); // trigger complete transaction
config.eventEmitter.emit('cleanup');
return true;
}
}
// PLM response - match inbound INSTEON send SD or ED message against last sent message (echo)
if(insteonMsg.cmd == config.INSTEON_SEND_STANDARD_OR_EXTENDED && insteonMsg.cmd == trans_queue[i].cmd
&& trans_queue[i].type == 'INSTEON' && trans_queue[i].state == 'SENT') {
var partial = insteonMsg.dec.slice(0, insteonMsg.dec.length - 1); // remove last byte
if(utils.compareArray(partial, trans_queue[i].request)) {
var last_byte = insteonMsg.dec[insteonMsg.dec.length - 1];
if(last_byte == config.INSTEON_PLM_ACK) {
trans_queue[i].state = 'PLM_ACK';
console.log('match::INSTEON transaction ('+trans_queue[i].id+') state updated to PLM_ACK');
//config.eventEmitter.emit('cleanup'); // sorry nobody cares about PLM_ACKs, wait for COMPLETE
return true;
} else if(last_byte == config.INSTEON_PLM_NAK) {
trans_queue[i].state = 'PLM_NAK'; // todo - or expired/failed?
console.log('match::INSTEON transaction ('+trans_queue[i].id+') state updated to PLM_NAK');
config.eventEmitter.emit('message', trans_queue[i]); // trigger incomplete transaction
config.eventEmitter.emit('cleanup');
return true;
}
}
}
// PLM response - match inbound plm commands to last sent PLM command; if more than one exists, will match most recent
if(insteonMsg.cmd == trans_queue[i].cmd && trans_queue[i].type == 'PLM' && trans_queue[i].state == 'SENT') {
var last_byte = insteonMsg.dec[insteonMsg.dec.length - 1];
if(last_byte == config.INSTEON_PLM_ACK) {
trans_queue[i].state = 'COMPLETE'; // if plm command ack, then we are done
trans_queue[i].response = insteonMsg;
console.log('match::PLM transaction ('+trans_queue[i].id+') state updated to COMPLETE');
config.eventEmitter.emit('message', trans_queue[i]); // PLM-only message complete
config.eventEmitter.emit('cleanup');
return true;
} else if(last_byte == config.INSTEON_PLM_NAK) {
trans_queue[i].state = 'PLM_NAK';
trans_queue[i].response = insteonMsg;
console.log('match::PLM transaction ('+trans_queue[i].id+') state updated to PLM_NAK');
config.eventEmitter.emit('message', trans_queue[i]); // PLM-only message incomplete
config.eventEmitter.emit('cleanup');
return true;
}
}
}
// did not match anything in queue
console.log('match::inbound message did not match anything in queue');
return false;
}
/*
** plm_wait: reconnect and reset state after a plm_busy (nak) occurs
*/
var plm_wait = exports.plm_wait = function plm_wait() {
// reconnect after PLM busy detected (handle buffer overrun)
insteon.connect();
config.PLM_BUSY = false;
console.log('plm_wait::PLM no longer busy');
};
/*
** cleanup: removes items from the queue if they are no longer needed; called as an event or directly if immediate cleanup required
*/
var cleanup = exports.cleanup = function cleanup() {
// remove items from the queue - make sure item is no longer needed!
// TODO - does this actually clean object from memory or just remove reference to it in the queue?
for(var i = trans_queue.length - 1; i >= 0; i--) {
if(trans_queue[i].state == 'EXPIRED') {
console.log('cleanup::removed EXPIRED message ('+trans_queue[i].id+')');
var expired = trans_queue.splice(i,1)[0];
expired = undefined;
// io.sockets.json.emit('insteon', expired);
} else if(trans_queue[i].state == 'PLM_NAK') {
console.log('cleanup::removed PLM_NAK message ('+trans_queue[i].id+')');
var failed = trans_queue.splice(i,1)[0];
clearTimeout(failed.timerid);
config.active_timers--;
failed = undefined;
} else if(trans_queue[i].state == 'COMPLETE') {
console.log('cleanup::removed COMPLETE message ('+trans_queue[i].id+')');
var complete = trans_queue.splice(i,1)[0];
clearTimeout(complete.timerid);
config.active_timers--;
complete = undefined;
}
}
};