-
Notifications
You must be signed in to change notification settings - Fork 65
/
Copy pathWirelessGateway_LoRaBall.device.nut
423 lines (358 loc) · 12.4 KB
/
WirelessGateway_LoRaBall.device.nut
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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
#require "JSONParser.class.nut:1.0.0"
// LoRa UART Coms Wrapper
class LoRa_RN2903 {
static BAUD_RATE = 57600;
static WORD_SIZE = 8;
static STOP_BITS = 1;
static LINE_FEED = 0x0A;
static FIRST_ASCII_PRINTABLE_CHAR = 32;
static LORA_BANNER = "RN2903"; //"RN2903 or RN2483"
static ERROR_BANNER_MISMATCH = "LoRa banner mismatch";
static ERROR_BANNER_TIMEOUT = "LoRa banner timeout";
// Pins
_uart = null;
_reset = null; // active low
// Variables
_timeout = null;
_buffer = null;
_receiveHandler = null;
_init = false;
_initCB = null;
// Debug logging flag
_debug = null;
constructor(uart, reset, debug = false) {
_debug = debug;
_reset = reset;
_uart = uart;
_clearBuffer();
_reset.configure(DIGITAL_OUT, 1);
}
function init(cb = null) {
// set init flag
_init = true;
_initCB = cb;
// Reset device
_reset.write(0);
// Start timeout error timer
_timeout = imp.wakeup(5, function() {
// pass error to callback
if (_initCB) {
_initCB(ERROR_BANNER_TIMEOUT);
} else {
server.error(ERROR_BANNER_TIMEOUT);
}
// clear init variables
_init = false;
}.bindenv(this));
// Configure UART
_uart.configure(BAUD_RATE, WORD_SIZE, PARITY_NONE, STOP_BITS, NO_CTSRTS, _uartReceive.bindenv(this));
// Release Reset pin
_reset.write(1);
}
function hwReset() {
_reset.write(0);
imp.sleep(0.01);
_reset.write(1);
}
function send(cmd) {
_debugLog("sent: "+ cmd);
_uart.write(cmd+"\r\n");
}
function setReceiveHandler(cb) {
_receiveHandler = cb;
}
function _uartReceive() {
local b = _uart.read();
while(b >= 0) {
if (b >= FIRST_ASCII_PRINTABLE_CHAR) {
_buffer += b.tochar();
} else if (b == LINE_FEED) {
// we have a line of data
_debugLog("received: "+_buffer);
// pass buffer to handler
if (_init) {
_checkBanner(_buffer);
} else if (_receiveHandler) {
_receiveHandler(_buffer);
}
_clearBuffer();
}
b = _uart.read();
}
}
function _clearBuffer() {
_buffer = "";
}
function _checkBanner(data) {
// cancel banner timeout timer
imp.cancelwakeup(_timeout);
_timeout = null;
local err = null;
// check for the expected banner
if (data.slice(0, LORA_BANNER.len()) != LORA_BANNER) {
server.log( data.slice(0, LORA_BANNER.len()) );
err = ERROR_BANNER_MISMATCH;
}
// call init callback
if (_initCB) {
_initCB(err);
} else if (err) {
server.error(err);
}
// clear init flag
_init = false;
}
function _debugLog(msg) {
if (_debug) server.log(msg);
}
}
// Configure Hardware
class WirelessAccelerator {
static UART = hardware.uart1;
static RESET_PIN = hardware.pinH;
static RED_LED = hardware.pinG;
static AMBER_LED = hardware.pinF;
static GREEN_LED = hardware.pinE;
static LED_ON = 0;
static LED_OFF = 1;
static LED_FLASH_TIME = 0.05;
lora = null;
flash_callback = null;
constructor(debug = false, radioInitCB = null) {
if (typeof debug == "function") {
radioInitCB = debug;
debug = false;
}
configureLEDs();
lora = LoRa_RN2903(UART, RESET_PIN, debug);
loraInit(radioInitCB);
}
function loraInit(cb = null) {
lora.init(cb);
}
function loraSend(command) {
lora.send(command);
}
function loraSetReceiveHandler(cb) {
lora.setReceiveHandler(cb);
}
function configureLEDs() {
RED_LED.configure(DIGITAL_OUT, LED_OFF);
AMBER_LED.configure(DIGITAL_OUT, LED_OFF);
GREEN_LED.configure(DIGITAL_OUT, LED_OFF);
}
function setLED(led, state) {
led.write(state);
}
function flashLED(led) {
led.write(LED_ON);
if (flash_callback != null) {
imp.cancelwakeup(flash_callback);
}
flash_callback = imp.wakeup(LED_FLASH_TIME, function() {
led.write(LED_OFF);
flash_callback = null;
}.bindenv(this));
}
}
// Application
class Application {
// LoRa Settings
static RADIO_MODE = "lora";
static RADIO_FREQ = 915000000; // (915000000 RN2903, 433575000 RN2483)
static RADIO_SPREADING_FACTOR = "sf7"; // 128 chips
static RADIO_BANDWIDTH = 125;
static RADIO_CODING_RATE = "4/5";
static RADIO_CRC = "on"; // crc header enabled
static RADIO_SYNC_WORD = 12;
static RADIO_WATCHDOG_TIMEOUT = 0;
static RADIO_POWER_OUT = 14;
static RADIO_RX_WINDOW_SIZE = 0; // contiuous mode
// LoRa Commands
static MAC_PAUSE = "mac pause";
static RADIO_SET = "radio set";
static RADIO_RX = "radio rx";
static RADIO_TX = "radio tx";
// LoRa Com variables
static TX_HEADER = "FF000000";
static TX_FOOTER = "00";
static ACK_COMMAND = "5458204F4B" // "TX OK"
static LORA_DEVICE_ID = "Ball_01"; // RED BALL
// Data Filtering variables
static EVENT_TIMEOUT = 3; // in sec
static MOVEMENT_TIMEOUT = 500; // in ms
static MOVEMENT_FILTER_RANGE = 0.03; // in G
eventActive = false;
prevMovementUpdate = null;
prevX = null;
prevY = null;
prevZ = null;
// LoRa Initialization varaibles
initCommands = null;
cmdIdx = null;
// Application varaibles
gateway = null;
_debug = null;
function constructor(debug = false) {
_debug = debug;
setInitCommands();
gateway = WirelessAccelerator(_debug, loraInitHandler.bindenv(this));
}
function loraInitHandler(err) {
if (err) {
server.error(err);
} else {
// set receive callback
gateway.loraSetReceiveHandler(sendNextInitCmd.bindenv(this));
cmdIdx = 0;
sendNextInitCmd();
}
}
function sendNextInitCmd(data = null) {
if (data == "invalid_param") {
// Set init command failed - log it
server.error("Radio command failed: " + data);
} else if (cmdIdx < initCommands.len()) {
local command = initCommands[cmdIdx++];
server.log(command);
// send next command to LoRa
gateway.loraSend(command);
} else {
gateway.loraSetReceiveHandler(receive.bindenv(this));
// Radio ready to receive, green LED on
gateway.setLED(gateway.GREEN_LED, gateway.LED_ON);
}
}
function setInitCommands() {
initCommands = [ format("%s mod %s", RADIO_SET, RADIO_MODE),
format("%s freq %i", RADIO_SET, RADIO_FREQ),
format("%s sf %s", RADIO_SET, RADIO_SPREADING_FACTOR),
format("%s bw %i", RADIO_SET, RADIO_BANDWIDTH),
format("%s cr %s", RADIO_SET, RADIO_CODING_RATE),
format("%s crc %s", RADIO_SET, RADIO_CRC),
format("%s sync %i", RADIO_SET, RADIO_SYNC_WORD),
format("%s wdt %i", RADIO_SET, RADIO_WATCHDOG_TIMEOUT),
format("%s pwr %i", RADIO_SET, RADIO_POWER_OUT),
MAC_PAUSE,
format("%s %i", RADIO_RX, RADIO_RX_WINDOW_SIZE) ];
// RADIO_SET responses: ok, invalid_param
// radio rx responses: ok, invalid_param, busy -- then radio_rx <data>, radio_err
}
function receive(data) {
if (data.len() > 10 && data.slice(0,10) == "radio_rx ") {
// We have received a packet
// Flash amber LED
gateway.flashLED(gateway.AMBER_LED);
// Parse data into a table
local packet = _parse(data);
// Filter data & send only when movement or event detected
_filter(packet);
// Send ACK
gateway.loraSend( format("%s %s%s%s", RADIO_TX, TX_HEADER, ACK_COMMAND, TX_FOOTER) );
} else if (data == "radio_tx_ok" || data == "radio_err") {
// Queue next receive
gateway.loraSend( format("%s %i", RADIO_RX, RADIO_RX_WINDOW_SIZE) );
} else if (data != "ok") {
// Unexpected response - log it
server.log(data);
}
}
function _filter(packet) {
// Check that message came from our ball
if ("id" in packet && packet.id == LORA_DEVICE_ID) {
if ("event" in packet && !eventActive) {
// Toggle event flag
eventActive = true;
// Turn on red LED
gateway.setLED(gateway.RED_LED, gateway.LED_ON);
// Send event to agent
agent.send("event", {"event": "freefall detected"});
// Block incoming events while current event is active
imp.wakeup(EVENT_TIMEOUT, function() {
eventActive = false;
// Turn off red LED
gateway.setLED(gateway.RED_LED, gateway.LED_OFF);
}.bindenv(this));
}
if ("reading" in packet) {
local newX = packet.reading.x;
local newY = packet.reading.y;
local newZ = packet.reading.z;
// Check for movement
if (_changed(prevX, newX) || _changed(prevY, newY) || _changed(prevZ, newZ)) {
local now = hardware.millis();
// Limit movement updates
if (prevMovementUpdate == null || now >= MOVEMENT_TIMEOUT + prevMovementUpdate) {
// Update last update timestamp
prevMovementUpdate = now;
// Send reading
agent.send("reading", packet.reading);
}
}
// update stored values
prevX = newX;
prevY = newY;
prevZ = newZ;
}
}
}
function _changed(prev, new) {
if (prev == null) return true;
return (prev + MOVEMENT_FILTER_RANGE < new || prev - MOVEMENT_FILTER_RANGE > new);
}
function _parse(data) {
// Remove radio command ("radio_x ") from data
// Turn data from hex to binary
local received = _hextobytes(data.slice(10));
// Remove Binary Header (FFFF0000)
received = received.tostring().slice(4);
// Parse data
local raw = "";
local packet = {};
foreach (val in received) {
if (val != 0x00) raw += val.tochar();
}
if (_debug) server.log(raw);
try {
if (raw.find(LORA_DEVICE_ID) != null) {
raw = split(raw, ",");
packet.id <- raw[0];
if (raw[1] == "t") packet.event <- "Freefall Detected";
if (raw.len() == 5) {
packet.reading <- {};
packet.reading.x <- raw[2].tofloat();
packet.reading.y <- raw[3].tofloat();
packet.reading.z <- raw[4].tofloat();
}
}
return packet;
} catch(e) {
if (_debug) server.log(e);
return null;
}
}
function _hextobytes(hex) {
if (_debug) server.log(hex);
local b = blob(hex.len()/2);
local byte;
for(local i=0; i < hex.len(); i+=2) {
byte = (hex[i] >= 'A') ? (hex[i] - 'A' + 10) << 4 : (hex[i] - '0') << 4;
byte += (hex[i+1] >= 'A') ? (hex[i+1] - 'A' + 10) : (hex[i+1] - '0');
b.writen(byte, 'b');
}
return b;
}
}
// RUNTIME
// -------------------------------------------------------------
server.log(imp.getsoftwareversion());
imp.enableblinkup(true);
local enableDebugLogging = false;
// Initialize LoRa radio, and open a receive listener
// Green LED will be lit when radio ready
// Amber LED will flash when radio receives a packet
// Red LED will be lit when event has been triggered
// Filter incoming data
// Send accel data when ball moves
// Send event when ball throw detected
app <- Application(enableDebugLogging);