Branch data Line data Source code
1 [ + ]: 1 : /**
2 : 1 : * Module for reading and writing LoRa packets.
3 : 1 : * @module lora-comms
4 : 1 : * @extends events.EventEmitter
5 : 1 : */
6 : 1 : "use strict";
7 : 1 :
8 : 1 : process.env.UV_THREADPOOL_SIZE = (process.env.UV_THREADPOOL_SIZE || 4) + 5;
9 : 1 :
10 : 1 : const stream = require('stream'),
11 : 1 : path = require('path'),
12 : 1 : EventEmitter = require('events').EventEmitter,
13 : 1 : LoRaComms = require('bindings')('lora_comms').LoRaComms;
14 : 1 :
15 : 1 : LoRaComms.set_gw_send_hwm(LoRaComms.uplink, -1);
16 : 1 : LoRaComms.set_gw_send_timeout(LoRaComms.uplink, -1, -1);
17 : 1 : LoRaComms.set_gw_recv_timeout(LoRaComms.uplink, -1, -1);
18 : 1 :
19 : 1 : LoRaComms.set_gw_send_hwm(LoRaComms.downlink, -1);
20 : 1 : LoRaComms.set_gw_send_timeout(LoRaComms.downlink, -1, -1);
21 : 1 : LoRaComms.set_gw_recv_timeout(LoRaComms.downlink, -1, -1);
22 : 1 :
23 : 1 : LoRaComms.set_log_max_msg_size(1024);
24 : 1 : LoRaComms.set_log_write_hwm(-1);
25 : 1 : LoRaComms.set_log_write_timeout(-1, -1);
26 : 1 :
27 : 1 : class LinkDuplex extends stream.Duplex
28 : 1 : {
29 [ + ]: 1 : constructor(link, options)
30 : 44 : {
31 : 44 : super(options);
32 : 44 : this._link = link;
33 : 44 : this._reading = false;
34 : 44 : }
35 : 1 :
36 [ + ]: 1 : _write(data, encoding, cb)
37 : 28 : {
38 [ + ]: 28 : LoRaComms.send_to(this._link, data, -1, -1, -1, (err, r) =>
39 : 28 : {
40 : 28 : if (err)
41 [ + ]: 28 : {
42 : 5 : if (err.errno === LoRaComms.EBADF)
43 [ + ]: 5 : {
44 : 4 : this.end();
45 : 4 : }
46 : 5 :
47 : 5 : return cb(err);
48 : 5 : }
49 [ + ]: 23 :
50 : 23 : if (r !== data.length)
51 [ + ]: 28 : {
52 : 1 : return cb(new Error('not all data was written'));
53 : 1 : }
54 [ + ]: 22 :
55 : 22 : cb();
56 : 28 : });
57 : 28 : }
58 : 1 :
59 [ + ]: 1 : _read()
60 : 52 : {
61 [ + ]: 52 : if (this._reading) { return; }
62 [ + ]: 35 : this._reading = true;
63 : 35 :
64 : 35 : let buf = Buffer.alloc(LoRaComms.recv_from_buflen);
65 [ + ]: 35 : LoRaComms.recv_from(this._link, buf, -1, -1, (err, r) =>
66 : 35 : {
67 : 35 : this._reading = false;
68 : 35 : if (err)
69 [ + ]: 35 : {
70 : 18 : if (err.errno === LoRaComms.EBADF)
71 [ + ]: 18 : {
72 : 17 : return this.push(null);
73 : 17 : }
74 [ + ]: 1 :
75 [ + ]: 1 : return process.nextTick(() => this.emit('error', err));
76 : 1 : }
77 [ + ]: 17 :
78 : 17 : if (this.push(buf.slice(0, r)))
79 : 17 : {
80 [ + ]: 17 : process.nextTick(() => this._read());
81 : 17 : }
82 : 35 : });
83 : 52 : }
84 : 1 : }
85 : 1 :
86 : 1 : class LogReadable extends stream.Readable
87 : 1 : {
88 [ + ]: 1 : constructor(get_log_message, options)
89 : 32 : {
90 : 32 : super(options);
91 : 32 : this._get_log_message = get_log_message;
92 : 32 : }
93 : 1 :
94 [ + ]: 1 : _read()
95 : 45 : {
96 : 45 : let buf = Buffer.alloc(LoRaComms.get_log_max_msg_size());
97 [ + ]: 45 : this._get_log_message(buf, -1, -1, (err, r) =>
98 : 45 : {
99 : 45 : if (err)
100 [ + ]: 45 : {
101 : 31 : if (err.errno === LoRaComms.EBADF)
102 [ + ]: 31 : {
103 : 30 : return this.push(null);
104 : 30 : }
105 [ + ]: 1 :
106 [ + ]: 1 : return process.nextTick(() => this.emit('error', err));
107 : 1 : }
108 [ + ]: 14 :
109 : 14 : if (this.push(buf.slice(0, r)))
110 [ + ]: 45 : {
111 [ + ]: 13 : process.nextTick(() => this.read());
112 : 13 : }
113 : 45 : });
114 : 45 : }
115 : 1 : }
116 : 1 :
117 : 1 : class lora_comms extends EventEmitter
118 : 1 : {
119 [ + ]: 1 : get LoRaComms()
120 : 19 : {
121 : 19 : return LoRaComms;
122 : 19 : }
123 : 1 :
124 [ + ]: 1 : constructor()
125 : 1 : {
126 : 1 : super();
127 : 1 : this._uplink = null;
128 : 1 : this._downlink = null;
129 : 1 : this._active = false;
130 : 1 : this._needs_reset = false;
131 : 1 : this._log_info = null;
132 : 1 : this._log_error = null;
133 : 1 : this._logging_active = false;
134 : 1 : this._logging_needs_reset = false;
135 : 1 : }
136 : 1 :
137 : 1 : /**
138 : 1 : * Start the LoRa radio, receiving and transmitting packets to
139 : 1 : * {@link lora-commsuplink|uplink} and {@link lora-commsdownlink|downlink}.
140 : 1 : *
141 : 1 : * @memberof lora-comms
142 : 1 : * @param {Object} options - Configuration options. This is passed to stream.Duplex when constructing {@link lora-commsuplink|uplink} and {@link lora-commsdownlink|downlink} and supports the following additional option:
143 : 1 : * @param {string} [options.cfg_dir] - Path to directory containing LoRa radio configuration files. Defaults to `packet_forwarder_shared/lora_pkt_fwd` in the module directory.
144 : 1 : */
145 [ + ]: 1 : start(options)
146 : 17 : {
147 : 17 : options = Object.assign(
148 : 17 : {
149 : 17 : cfg_dir: path.join(__dirname, '..',
150 : 17 : 'packet_forwarder_shared', 'lora_pkt_fwd')
151 : 17 : }, options);
152 : 17 :
153 : 17 : if (this._active)
154 [ + ]: 17 : {
155 : 1 : return;
156 : 1 : }
157 [ + ]: 16 :
158 : 16 : if (this._needs_reset)
159 [ + ]: 17 : {
160 : 15 : LoRaComms.reset();
161 : 15 : }
162 [ + ]: 16 :
163 : 16 : this._active = true;
164 : 16 : this._needs_reset = true;
165 : 16 :
166 : 16 : if (options.no_streams)
167 [ + ]: 17 : {
168 : 5 : this._uplink = null;
169 : 5 : this._downlink = null;
170 : 5 : }
171 [ + ]: 11 : else
172 : 11 : {
173 : 11 : this._uplink = new LinkDuplex(LoRaComms.uplink, options);
174 : 11 : this._downlink = new LinkDuplex(LoRaComms.downlink, options);
175 : 11 : }
176 [ + ]: 16 :
177 [ + ][ + ]: 16 : LoRaComms.start(options.cfg_dir, err => process.nextTick(() =>
178 : 16 : {
179 : 16 : this._active = false;
180 : 16 :
181 : 16 : if (this._uplink)
182 [ + ]: 16 : {
183 : 11 : this._uplink.push(null);
184 : 11 : this._uplink.end();
185 : 11 : }
186 : 16 :
187 : 16 : if (this._downlink)
188 [ + ]: 16 : {
189 : 11 : this._downlink.push(null);
190 : 11 : this._downlink.end();
191 : 11 : }
192 : 16 :
193 : 16 : if (err)
194 [ + ]: 16 : {
195 : 1 : this.emit('error', err);
196 : 1 : }
197 : 16 :
198 : 16 : this.emit('stop');
199 : 16 : }));
200 : 17 : }
201 : 1 :
202 : 1 : /**
203 : 1 : * Stop the LoRa radio.
204 : 1 : *
205 : 1 : * A {@link lora-comms.event:stop|stop} event is emitted when the radio
206 : 1 : * stops.
207 : 1 : *
208 : 1 : * @memberof lora-comms
209 : 1 : */
210 [ + ]: 1 : stop()
211 : 15 : {
212 : 15 : LoRaComms.stop();
213 : 15 : }
214 : 1 :
215 : 1 : /**
216 : 1 : * Duplex stream for receiving packets from the LoRa radio.
217 : 1 : * Read `PUSH_DATA` packets. Write `PUSH_ACK` packets.
218 : 1 : *
219 : 1 : * @memberof lora-comms
220 : 1 : * @type {?stream.Duplex}
221 : 1 : */
222 [ + ]: 1 : get uplink()
223 : 59 : {
224 : 59 : return this._uplink;
225 : 59 : }
226 : 1 :
227 : 1 : /**
228 : 1 : * Duplex stream for transmitting packets using the LoRa radio.
229 : 1 : * Read `PULL_DATA` and `TX_ACK` packets. Write `PULL_ACK` and `PULL_RESP`
230 : 1 : * packets.
231 : 1 : *
232 : 1 : * @memberof lora-comms
233 : 1 : * @type {?stream.Duplex}
234 : 1 : */
235 [ + ]: 1 : get downlink()
236 : 40 : {
237 : 40 : return this._downlink;
238 : 40 : }
239 : 1 :
240 : 1 : /**
241 : 1 : * Whether the LoRa radio is switched on.
242 : 1 : *
243 : 1 : * @memberof lora-comms
244 : 1 : * @type {boolean}
245 : 1 : */
246 [ + ]: 1 : get active()
247 : 16 : {
248 : 16 : return this._active;
249 : 16 : }
250 : 1 :
251 : 1 : /**
252 : 1 : * Start logging diagnostic messages to
253 : 1 : * {@link lora-commslog_info|log_info} and
254 : 1 : * {@link lora-commslog_error|log_error}.
255 : 1 : *
256 : 1 : * @memberof lora-comms
257 : 1 : * @param {Object} options - Configuration options. This is passed to stream.Readable when constructing {@link lora-commslog_info|log_info} and {@link lora-commslog_error|log_error}.
258 : 1 : */
259 [ + ]: 1 : start_logging(options)
260 : 17 : {
261 : 17 : if (this._logging_active)
262 [ + ]: 17 : {
263 : 1 : return;
264 : 1 : }
265 [ + ]: 16 :
266 : 16 : if (this._logging_needs_reset)
267 [ + ]: 17 : {
268 : 15 : LoRaComms.reset_logging();
269 : 15 : }
270 [ + ]: 16 :
271 : 16 : this._logging_active = true;
272 : 16 : this._logging_needs_reset = true;
273 : 16 :
274 : 16 : this._log_info = new LogReadable(LoRaComms.get_log_info_message,
275 : 16 : options);
276 : 16 : this._log_error = new LogReadable(LoRaComms.get_log_error_message,
277 : 16 : options);
278 : 16 :
279 [ + ]: 16 : let end_count = 0, check = () =>
280 : 32 : {
281 : 32 : if (++end_count === 2)
282 [ + ]: 32 : {
283 : 16 : this._logging_active = false;
284 : 16 : this.emit('logging_stop');
285 : 16 : }
286 : 16 : };
287 : 16 : this._log_info.on('end', check);
288 : 16 : this._log_error.on('end', check);
289 : 16 :
290 : 16 : LoRaComms.start_logging();
291 : 17 : }
292 : 1 :
293 : 1 : /**
294 : 1 : * Stop logging diagnostic messages.
295 : 1 : *
296 : 1 : * A {@link lora-comms.event:logging_stop|logging_stop} event is emitted
297 : 1 : * when logging stops.
298 : 1 : *
299 : 1 : * @memberof lora-comms
300 : 1 : */
301 [ + ]: 1 : stop_logging()
302 : 1 : {
303 : 1 : LoRaComms.stop_logging();
304 : 1 : this._log_info.push(null);
305 : 1 : this._log_error.push(null);
306 : 1 : }
307 : 1 :
308 : 1 : /**
309 : 1 : * Readable stream containing diagnostic messages.
310 : 1 : *
311 : 1 : * @memberof lora-comms
312 : 1 : * @type {?stream.Readable}
313 : 1 : */
314 [ + ]: 1 : get log_info()
315 : 21 : {
316 : 21 : return this._log_info;
317 : 21 : }
318 : 1 :
319 : 1 : /**
320 : 1 : * Readable stream containing diagnostic messages.
321 : 1 : *
322 : 1 : * @memberof lora-comms
323 : 1 : * @type {?stream.Readable}
324 : 1 : */
325 [ + ]: 1 : get log_error()
326 : 16 : {
327 : 16 : return this._log_error;
328 : 16 : }
329 : 1 :
330 : 1 : /**
331 : 1 : * Whether diagnostic logging is enabled.
332 : 1 : *
333 : 1 : * @memberof lora-comms
334 : 1 : * @type {boolean}
335 : 1 : */
336 [ + ]: 1 : get logging_active()
337 : 16 : {
338 : 16 : return this._logging_active;
339 : 16 : }
340 : 1 :
341 : 1 : /**
342 : 1 : * Stop event. Emitted when the radio stops.
343 : 1 : *
344 : 1 : * @memberof lora-comms
345 : 1 : * @event stop
346 : 1 : */
347 : 1 :
348 : 1 : /**
349 : 1 : * Logging stop event. Emitted when logging stops.
350 : 1 : *
351 : 1 : * @memberof lora-comms
352 : 1 : * @event logging_stop
353 : 1 : */
354 : 1 :
355 : 1 : /**
356 : 1 : * Error event.
357 : 1 : *
358 : 1 : * @memberof lora-comms
359 : 1 : * @event error
360 : 1 : * @param {Object} err - The error which occurred.
361 : 1 : */
362 : 1 : }
363 : 1 :
364 : 1 : module.exports = new lora_comms();
|