forked from gcanivet/node-insteon
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsend.js
188 lines (181 loc) · 8.48 KB
/
send.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
/*
** Send Functions
*/
var utils = require('./utils.js');
var config = require('./config.js');
var receive = require('./receive.js');
var insteon = require('./insteon.js');
var trans_queue = config.trans_queue;
/*
** sendRaSerial: send a serial command, bypassing transaction queue
*/
var sendRawSerial = exports.sendRawSerial = function sendRawSerial(data) {
//console.log('sendRawSerial::write serial dec: '+data);
console.log('sendRawSerial::write serial hex: '+utils.byteArrayToHexStringArray(data));
//io.sockets.send('sendRawSerial::write serial hex: '+utils.byteArrayToHexStringArray(data));
var buf = new Buffer(data);
config.sp.write(buf);
};
/*
** send: adds a serial command to the transaction queue
*/
var send = exports.send = function send(data, retry) {
// pre: data is a byte array (ie. [0x02, 0x05])
// adds message to back of queue
if(data.length < 2) throw 'send::not a valid PLM message ('+data+'), must be greater than eq 2 bytes';
if(data[1] < 0x60 || data[1] > 0x73) throw 'send::not a valid PLM message ('+data+'), command must be between 0x60 and 0x73';
if(trans_queue.length == config.INSTEON_QUEUE_LIMIT) throw 'send::INSTEON_QUEUE_LIMIT reached on PLM message ('+data+')';
if(data[1] == 0x63) throw 'send::X10 commands not supported at this time ('+data+')';
if(retry == undefined) retry = config.INSTEON_DEFAULT_RETRIES;
// type determines when transaction complete; plm messages only require plm ack/nak while INSTEON/X10 messages require plm ack/nak plus INSTEON/X10 ack/nak
var cmd = data[1];
var date = new Date();
var tmstamp = date.getTime();
var item = {
id: tmstamp,
request: data,
state: 'QUEUED',
retries_left: retry,
cmd: cmd,
timer: config.INSTEON_PLM_TRANS_TIME_LIMIT
};
Object.defineProperty(item, 'timerid', {
value:null,
writable:true,
configurable:true,
enumerable:false // timerid does not jsonify
});
if(cmd == config.INSTEON_SEND_STANDARD_OR_EXTENDED) {
item.type = 'INSTEON';
item.to_address = utils.dec2hexstr(data[2]) + utils.dec2hexstr(data[3]) + utils.dec2hexstr(data[4]);
var message_flags = utils.getMessageFlags(data[5]);
item.timer = utils.getInsteonTimer(message_flags.extended, 1, message_flags.max_hops); // ack = 1
// todo - determine if message has ack or nak response to improve timer, for now assume ack (true) in last parameter
} else if(cmd == config.INSTEON_SEND_X10) {
item.type = 'X10';
} else {
item.type = 'PLM';
item.timer = utils.getPlmTimer(cmd);
}
trans_queue.unshift(item);
console.log('send::added message ('+item.id+') hex ('+utils.byteArrayToHexStringArray(data)+') (retries_left:'+item.retries_left+') to queue (length:'+trans_queue.length+')');
dequeue(); // run immediately instead of evented; puts command on the wire sooner for the most common case
};
/*
** dequeue: execute next appopropriate serial command(s) from the queue; not always FIFO; does not actually remove it (should be renamed) since cleanup() does that later
*/
var dequeue = exports.dequeue = function dequeue() {
if(config.PLM_BUSY) return;
// process next
switch(config.mode) {
case 'non_synchronous':
// send messages immediately (no waiting)
for(var i = trans_queue.length-1; i >= 0; i--) {
if(trans_queue[i].state == 'QUEUED') {
sendRawSerial(trans_queue[i].request);
trans_queue[i].state = 'SENT';
(function(item) {
config.active_timers++;
item.timerid = setTimeout(function(){timer(item)}, item.timer);
})(trans_queue[i]);
}
}
break;
case 'queue_all':
// send last message
if(trans_queue.length > 0) {
var item = trans_queue[trans_queue.length-1];
if(item.state == 'QUEUED') {
sendRawSerial(item.request);
item.state = 'SENT';
(function(item) {
config.active_timers++;
item.timerid = setTimeout(function(){timer(item)}, item.timer);
})(item);
}
}
break;
case 'queue_network_msgs':
for(var i = trans_queue.length-1; i >= 0; i--) {
if(trans_queue[i].type == 'PLM') {
// send all PLM
if(trans_queue[i].state == 'QUEUED') {
sendRawSerial(trans_queue[i].request);
trans_queue[i].state = 'SENT';
(function(item) {
config.active_timers++;
item.timerid = setTimeout(function(){timer(item)}, item.timer);
})(trans_queue[i]);
}
} else {
// send the last network message if and only if queued (ready)
var last_network_msg = trans_queue[i];
if(last_network_msg.state == 'QUEUED') {
sendRawSerial(last_network_msg.request);
last_network_msg.state = 'SENT';
(function(item) {
config.active_timers++;
item.timerid = setTimeout(function(){timer(item)}, item.timer);
})(last_network_msg);
}
}
}
break;
case 'queue_device_msgs':
var busy_devices = {}; // rebuild each time
for(var i = trans_queue.length-1; i >= 0; i--) {
if(trans_queue[i].type == 'PLM') {
// send all PLM
if(trans_queue[i].state == 'QUEUED') {
sendRawSerial(trans_queue[i].request);
trans_queue[i].state = 'SENT';
(function(item) {
config.active_timers++;
item.timerid = setTimeout(function(){timer(item);}, item.timer);
})(trans_queue[i]); // pass static reference to queued object
}
} else {
// send network message as long as target device not busy
var to = trans_queue[i].to_address;
if(trans_queue[i].state != 'QUEUED') busy_devices[to] = true;
if(trans_queue[i].state == 'QUEUED' && (busy_devices[to] == false || busy_devices[to] == undefined)) {
console.log('dequeue::releasing message ('+trans_queue[i].id+'), check back in '+trans_queue[i].timer+'ms');
sendRawSerial(trans_queue[i].request);
trans_queue[i].state = 'SENT';
(function(item) {
config.active_timers++;
item.timerid = setTimeout(function(){timer(item);}, item.timer); // like passing by value; new scope so item is not changed by parent scope
})(trans_queue[i]);
busy_devices[to] = true;
}
}
}
break;
default:
throw 'dequeue::mode not supported';
}
};
/*
** timer: handle any expired transactions
*/
var timer = exports.timer = function(trans_queue_item, messageid) {
console.log('timer::message ('+trans_queue_item.id+') timer executing');
if(trans_queue_item == undefined) return;
config.active_timers--;
if(trans_queue_item.state != 'COMPLETE') {
trans_queue_item.prev_state = trans_queue_item.state;
trans_queue_item.state = 'EXPIRED';
console.log('timer::messsage ('+trans_queue_item.id+') state updated to EXPIRED');
receive.cleanup(); // run immediately instead of evented; instead of waiting for next cleanup then next dequeue event, creating a delay
// recconnect check
config.expired_count++;
if(config.expired_count > config.expired_count_reconnect) {
console.log('timer::consecutive expired messages exceeds '+config.expired_count_reconnect+', attempting reconnect');
insteon.connect();
}
// retry expired messages, add to back of queue (not front, based on heuristics)
if(trans_queue_item.retries_left > 0) {
send(trans_queue_item.request, trans_queue_item.retries_left - 1); //
}
}
};