Newer
Older
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
/********************************************
* Data Prep / Validation *
********************************************/
/**
* Return ESP Module ID, based on MAC address
* Source: https://arduino.stackexchange.com/a/58678
*
* 2021-11-07 - This method proved unreliable, switched to casting ESP.getChipID() as a string instead
*
* @return Unique ID for sensor
*/
String getSensorUID() {
String sensorID = "EMIEI-"; // [E]nviornmental [M]onitoring [I]ndepenet of [E]xisting [I]nfrastructure
sensorID.concat(String(ESP.getChipId()));
return sensorID;
}
/********************************************
* Timings *
********************************************/
/**
* Introduce random delay, with optional minimum delay time
*
* @param minSecods Min number of secods to wait, default 0 (i.e.) any random delay between MIN and MAX
*/
void waitRandomDelay(int minSeconds = 0) {
int randDelay = (minSeconds * 1000) + random(MIN_RAND_DELAY, MAX_RAND_DELAY);
if (DEBUG){ Serial.println("[i] Delay for " + String(randDelay) + "ms"); }
delay(randDelay);
}
/********************************************
* Transmission *
********************************************/
/**
* Send JSON Payload over LoRa
*
* @param payload JSON Payload to be send
*/
void sendJsonPayloadWithLoRa(DynamicJsonDocument payload) {
Serial.println("[+] Transmit - Payload");
LoRa.beginPacket();
serializeJson(payload, LoRa);
LoRa.endPacket();
}
/********************************************
* Recieving *
********************************************/
/**
* Listen on Lora for other messages, returns true if no messages detected within [listenDuration] seconds
* Use prior to transmissions to avoid interruption of other messages
*
* @param int listenDuration How long to listen on Lora RX in Seconds
* @returns boolean If messages detected within listenDuration
*/
boolean clearToSend(int listenDuration) {
int listenUntil = now() + listenDuration;
// Listen until timeout expires
while (listenUntil >= now()) {
int packetSize = LoRa.parsePacket();
if (packetSize) {
Serial.println("[-] TX Busy - Not Clear To Send");
return false; // Other message heard on Rx, infer that we can not transmit just now.
}
delay(5);
}
return true; // We didn't hear anything, so continue
}
/**
* Listen for messages on TX, expecting JSON
*
* @param listenDuration How long to listen on Lora RX in Seconds
* @return StaticJsonDocument|null RX Payload in JSON, null on timeout reached or error
*/
StaticJsonDocument<1024> listenForAndConsumeMessage(int listenDuration) {
int listenUntil = now() + listenDuration;
StaticJsonDocument<1024> json;
// Listen until timeout expires
while (listenUntil >= now()) {
int packetSize = LoRa.parsePacket();
if (!packetSize) {
delay(3);
continue;
}
// Read in packet, ensure we only bring in anything after { and before } inclusive
String incoming = "";
char temp;
while (LoRa.available()) {
temp = (char)LoRa.read();
if (incoming.length() == 0 && temp == '{') { // Opening {
incoming = "{";
} else if (temp == '}') { // Closing }
incoming.concat("}");
break;
} else if (incoming.length() > 0) { // Anything else that's valid
incoming.concat(temp);
}
}
Serial.println(incoming);
// Deserialize - TODO Error Handling https://arduinojson.org/v6/api/json/deserializejson/
deserializeJson(json, incoming);
break;
}
return json;
}
// TODO DOC
boolean listenForTxPayloadAccept(int listenDuration, int messageID) {
Serial.println("[.] Transmit - Payload - Listening for Ack");
StaticJsonDocument<1024> json = listenForAndConsumeMessage(listenDuration);
// Timeout, likely TX Payload was not recieved or ack got lost.
if (json.isNull()) {
Serial.println("[-] Transmit - Payload - Ack Timeout Reached - Assuming Message Was Not Delivered\n\n");
return false;
}
const bool ackStatus = json["ackStatus"];
const String authIsForUid = json["uid"];
const int authIsForMessageID = json["replyMsgID"];
const String gatewayUid = json["gatewayUid"];
// Verify Sensor UID Match
if (authIsForUid == getSensorUID()
&& authIsForMessageID == messageID) {
Serial.println("[+] Transmit - Payload - Ack Recieved: " + String(ackStatus) + "\n");
if (ackStatus) { // It all worked :)
// Check for any updated config values
const int newTxAfterNReadings = json["txAfterNReadings"];
if (newTxAfterNReadings != NULL && newTxAfterNReadings != TX_AFTER_N_READINGS) {
TX_AFTER_N_READINGS = newTxAfterNReadings;
Serial.println("[+] Tx After N Readings Updated, now: " + String(TX_AFTER_N_READINGS) + " samples");
}
const int newPollingFrequency = json["pollingFrequency"];
if (newPollingFrequency != NULL && newPollingFrequency != POLLING_FREQUENCY) {
POLLING_FREQUENCY = newPollingFrequency;
Serial.println("[+] Polling Frequency Updated, now: " + String(POLLING_FREQUENCY) + "ms");
}
return true;
}
// TODO Retransmit, recover, etc
Serial.println("[-] Transmit - Payload - Ack Failed - TODO Setup Retransmission");
return false;
}
// TODO Else UID Mis-Match so we wait for next message
Serial.println("[-] Transmit - Payload - Ack Message ID or Sensor ID Mis-Match");
return false;
}
/********************************************
* TX Hello *
********************************************/
/**
* Send short "clear to send?" packet via LoRa
* Tx - "I've got data to send!" + UID + how long to reserve radio
* Note: Gateway has final say over TX_RESERVATION_TIME and may override requested value
*
* @param messageID ID of this message
*/
void sendTxHello(int messageID) {
if (DEBUG){ Serial.println("[+] Transmit - \"Hello\""); }
DynamicJsonDocument txHello(2048);
txHello["uid"] = getSensorUID();
txHello["reservationTime"] = TX_RESERVATION_TIME;
txHello["messageID"] = messageID;
sendJsonPayloadWithLoRa(txHello);
}
/**
* Listen for response to TxHello for [listenDuration] seconds.
* Upon recieving response, check Sensor UID & Message ID match and return true if we have clear to transmit payload.
*
* @param listenDuration How long to listen on Lora RX (Seconds)
* @param messageID ID of message we are expecting to receive
* @return Clear to Transmit (Y/N)
*/
boolean listenForTxHelloAccept(int listenDuration, int messageID) {
if (DEBUG){ Serial.println("[+] Transmit - \"Hello\" - Listening for \"Hello\" Ack & Clear to Send"); }
StaticJsonDocument<1024> json = listenForAndConsumeMessage(listenDuration);
// Timeout, likely TX Hello was not recieved or ack got lost
if (json.isNull()) {
Serial.println("[-] Transmit - \"Hello\" - Timeout while waiting for Clear to Send\n\n");
return false;
}
const bool okToTransmit = json["okTransmit"];
const String authIsForUid = json["uid"];
const int authIsForMessageID = json["messageID"];
const String gatewayUid = json["gatewayUid"];
// Verify txHello.okTransmit is True and Sensor UID & Message IDs Match
if (DEBUG) {
if (authIsForUid == getSensorUID()) {
Serial.println("[+] Transmit - \"Hello\" - Sensor UID Match!");
} else {
Serial.println("[-] Transmit - \"Hello\" - Sensor UID Mis-Match! " + String(authIsForUid) + " vs " + String(getSensorUID()));
}
if (authIsForMessageID == messageID) {
Serial.println("[+] Transmit - \"Hello\" - Message ID Match!");
} else {
Serial.println("[-] Transmit - \"Hello\" - MessageID Mis-Match!");
}
}
// Ok To Trasmit, Sensor UID Match & Message ID Match
bool clearToSend = (okToTransmit
&& authIsForUid == getSensorUID()
&& authIsForMessageID == messageID);
if (DEBUG) {
if (clearToSend) {
Serial.println("[+] Transmit - \"Hello\" - Recieved \"Clear to Transmit\" Payload");
} else {
Serial.println("[-] Transmit - \"Hello\" - Can Not Transmit At This Time");
}
}
return clearToSend;
}
// TODO - Finish Acks & Naks
boolean transmitData(DynamicJsonDocument payload) {
// TODO MAX_RETRIES
// Listen for other communication
// Rx - Listen for other messages, if no messages heard then continue
if (!clearToSend(0.5)) {
waitRandomDelay(); // Wait for short time before retrying
while (!clearToSend(0.5)) { waitRandomDelay(); } // TODO MAX_TIMEOUT
}
// Send TX Hello
sendTxHello(msgCount);
// Await TX Hello Auth - Expect: Timeout | Not Auth | Accept + Clear To Transmit
if (!listenForTxHelloAccept(TX_RESERVATION_TIME * 4, msgCount)) {
return false; // Can't transmit just now, we will retry
}
// Send TX Payload
sendJsonPayloadWithLoRa(payload);
// Await TX Payload Ack - Expect: Timeout | Nack | Accept + Data Match
if (!listenForTxPayloadAccept(2, msgCount)) {
return false; // TODO Ack Failed Setup a retry here!
}
// TODO Update Sensor Config
// TODO Clear values & Continue
//resetCounters(); TODO
Serial.println("Packet Sent Succesfully\n");
Serial.println("---------------+++++-------------------");
msgCount++;
return true;
}