-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Expand file tree
/
Copy pathSimRadio.cpp
More file actions
429 lines (377 loc) · 15.7 KB
/
Copy pathSimRadio.cpp
File metadata and controls
429 lines (377 loc) · 15.7 KB
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
424
425
426
427
428
429
#include "SimRadio.h"
#include "MeshService.h"
#include "Router.h"
SimRadio::SimRadio() : NotifiedWorkerThread("SimRadio")
{
instance = this;
}
SimRadio *SimRadio::instance;
ErrorCode SimRadio::send(meshtastic_MeshPacket *p)
{
printPacket("enqueuing for send", p);
bool dropped = false;
ErrorCode res = txQueue.enqueue(p, &dropped) ? ERRNO_OK : ERRNO_UNKNOWN;
if (dropped) {
txDrop++;
}
if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks
packetPool.release(p);
return res;
}
// set (random) transmit delay to let others reconfigure their radio,
// to avoid collisions and implement timing-based flooding
LOG_TRACE("Set random delay before tx");
setTransmitDelay();
return res;
}
void SimRadio::setTransmitDelay()
{
meshtastic_MeshPacket *p = txQueue.getFront();
// We want all sending/receiving to be done by our daemon thread.
// We use a delay here because this packet might have been sent in response to a packet we just received.
// So we want to make sure the other side has had a chance to reconfigure its radio.
/* We assume if rx_snr = 0 and rx_rssi = 0, the packet was generated locally.
* This assumption is valid because of the offset generated by the radio to account for the noise
* floor.
*/
if (p->rx_snr == 0 && p->rx_rssi == 0) {
startTransmitTimer(true);
} else {
// If there is a SNR, start a timer scaled based on that SNR.
LOG_TRACE("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr);
startTransmitTimerRebroadcast(p);
}
}
void SimRadio::startTransmitTimer(bool withDelay)
{
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (!txQueue.empty()) {
uint32_t delayMsec = !withDelay ? 1 : getTxDelayMsec();
// LOG_DEBUG("xmit timer %d", delay);
notifyLater(delayMsec, TRANSMIT_DELAY_COMPLETED, false);
}
}
void SimRadio::startTransmitTimerRebroadcast(meshtastic_MeshPacket *p)
{
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (!txQueue.empty()) {
uint32_t delayMsec = getTxDelayMsecWeighted(p);
// LOG_DEBUG("xmit timer %d", delay);
notifyLater(delayMsec, TRANSMIT_DELAY_COMPLETED, false);
}
}
void SimRadio::handleTransmitInterrupt()
{
// This can be null if we forced the device to enter standby mode. In that case
// ignore the transmit interrupt
if (sendingPacket)
completeSending();
isReceiving = true;
if (receivingPacket) // This happens when we don't consider something a collision if we weren't sending long enough
handleReceiveInterrupt();
}
void SimRadio::completeSending()
{
// We are careful to clear sending packet before calling printPacket because
// that can take a long time
auto p = sendingPacket;
sendingPacket = NULL;
if (p) {
txGood++;
if (!isFromUs(p))
txRelay++;
printPacket("Completed sending", p);
// We are done sending that packet, release it
packetPool.release(p);
// LOG_DEBUG("Done with send");
}
}
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
bool SimRadio::canSendImmediately()
{
// We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one).
// To do otherwise would be doubly bad because not only would we drop the packet that was on the way in,
// we almost certainly guarantee no one outside will like the packet we are sending.
bool busyTx = sendingPacket != NULL;
bool busyRx = isReceiving && isActivelyReceiving();
if (busyTx || busyRx) {
if (busyTx)
LOG_WARN("Can not send yet, busyTx");
if (busyRx)
LOG_WARN("Can not send yet, busyRx");
return false;
} else
return true;
}
bool SimRadio::isActivelyReceiving()
{
return receivingPacket != nullptr;
}
bool SimRadio::isChannelActive()
{
return receivingPacket != nullptr;
}
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
bool SimRadio::cancelSending(NodeNum from, PacketId id)
{
auto p = txQueue.remove(from, id);
if (p)
packetPool.release(p); // free the packet we just removed
bool result = (p != NULL);
LOG_DEBUG("cancelSending id=0x%08x, removed=%d", id, result);
return result;
}
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
bool SimRadio::findInTxQueue(NodeNum from, PacketId id)
{
return txQueue.find(from, id);
}
void SimRadio::onNotify(uint32_t notification)
{
switch (notification) {
case ISR_TX:
handleTransmitInterrupt();
// LOG_DEBUG("tx complete - starting timer");
startTransmitTimer();
break;
case ISR_RX:
handleReceiveInterrupt();
// LOG_DEBUG("rx complete - starting timer");
startTransmitTimer();
break;
case TRANSMIT_DELAY_COMPLETED:
if (receivingPacket) { // This happens when we had a timer pending and we started receiving
handleReceiveInterrupt();
startTransmitTimer();
break;
}
LOG_TRACE("delay done");
// If we are not currently in receive mode, then restart the random delay (this can happen if the main thread
// has placed the unit into standby) FIXME, how will this work if the chipset is in sleep mode?
if (!txQueue.empty()) {
if (!canSendImmediately()) {
// LOG_DEBUG("Currently Rx/Tx-ing: set random delay");
setTransmitDelay(); // currently Rx/Tx-ing: reset random delay
} else {
if (isChannelActive()) { // check if there is currently a LoRa packet on the channel
// LOG_DEBUG("Channel is active: set random delay");
setTransmitDelay(); // reset random delay
} else {
// Send any outgoing packets we have ready
meshtastic_MeshPacket *txp = txQueue.dequeue();
assert(txp);
startSend(txp);
// Packet has been sent, count it toward our TX airtime utilization.
uint32_t xmitMsec = RadioInterface::getPacketTime(txp);
airTime->logAirtime(TX_LOG, xmitMsec);
notifyLater(xmitMsec, ISR_TX, false); // Model the time it is busy sending
}
}
} else {
// LOG_DEBUG("done with txqueue");
}
break;
default:
assert(0); // We expected to receive a valid notification from the ISR
}
}
/** start an immediate transmit */
void SimRadio::startSend(meshtastic_MeshPacket *txp)
{
printPacket("Start low level send", txp);
isReceiving = false;
size_t numbytes = beginSending(txp);
meshtastic_MeshPacket *p = packetPool.allocCopy(*txp);
if (!p)
return;
// A packet we originate that's encrypted for someone else (a PKI DM, channel == 0) can't be
// decrypted here. Attempting it only logs a spurious "no suitable channel" miss, and the
// ciphertext (up to MAX_LORA_PAYLOAD_LEN + MESHTASTIC_PKC_OVERHEAD) overflows the decoded
// loopback payload. Carry such packets as ciphertext instead so the receiving sim node can
// decrypt them as if they had arrived over the air (see unpackAndReceive()).
bool carryEncrypted = p->pki_encrypted;
if (!carryEncrypted) {
perhapsDecode(p);
// Channel packets we couldn't decrypt (e.g. relaying an unknown channel) are carried too.
carryEncrypted = (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag);
}
meshtastic_Compressed c = meshtastic_Compressed_init_default;
// The Compressed wrapper is re-encoded back into decoded.payload.bytes (the same 233-byte field
// its data is copied from), so the carried bytes must leave room for the protobuf framing or
// pb_encode_to_bytes() below overflows and silently drops the loopback payload. meshtastic_Compressed_size
// is the max encoded size for a full data field, so (meshtastic_Compressed_size - sizeof(c.data.bytes))
// is the worst-case framing overhead to reserve.
constexpr size_t loopbackCapacity = sizeof(p->decoded.payload.bytes) - (meshtastic_Compressed_size - sizeof(c.data.bytes));
if (carryEncrypted) {
// Sentinel portnum UNKNOWN_APP marks the payload as ciphertext for unpackAndReceive().
c.portnum = meshtastic_PortNum_UNKNOWN_APP;
if (p->encrypted.size <= loopbackCapacity) {
memcpy(&c.data.bytes, p->encrypted.bytes, p->encrypted.size);
c.data.size = p->encrypted.size;
} else {
LOG_WARN("Encrypted payload (%u) > sim loopback capacity (%u), send empty", (unsigned)p->encrypted.size,
(unsigned)loopbackCapacity);
}
} else {
c.portnum = p->decoded.portnum;
// LOG_DEBUG("Send back to simulator with portNum %d", p->decoded.portnum);
if (p->decoded.payload.size <= loopbackCapacity) {
memcpy(&c.data.bytes, p->decoded.payload.bytes, p->decoded.payload.size);
c.data.size = p->decoded.payload.size;
} else {
LOG_WARN("Payload > compressed max, send empty");
}
}
// pb_encode_to_bytes writes into decoded.payload, which aliases `encrypted` in the union, so all
// reads of p->encrypted above must be complete before this point.
if (carryEncrypted) {
// On the encrypted path, `decoded` aliases the ciphertext we just copied into c.data;
// the remaining `Data` fields hold ciphertext bytes that would serialize as spurious
// wire fields, so clear the struct.
p->decoded = meshtastic_Data_init_zero;
}
// On the decoded path, `p->decoded` is already a valid Data from perhapsDecode(),
// so retain the existing fields (want_response, request_id, bitfield, etc.)
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
p->decoded.payload.size =
pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Compressed_msg, &c);
p->decoded.portnum = meshtastic_PortNum_SIMULATOR_APP;
service->sendQueueStatusToPhone(router->getQueueStatus(), 0, p->id);
service->sendToPhone(p); // Sending back to simulator
service->loop(); // Process the send immediately
}
// Simulates device received a packet via the LoRa chip
void SimRadio::unpackAndReceive(meshtastic_MeshPacket &p)
{
// Simulator packet (=Compressed packet) is encapsulated in a MeshPacket, so need to unwrap first
meshtastic_Compressed scratch;
if (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
memset(&scratch, 0, sizeof(scratch));
if (pb_decode_from_bytes(p.decoded.payload.bytes, p.decoded.payload.size, &meshtastic_Compressed_msg, &scratch)) {
if (scratch.portnum == meshtastic_PortNum_UNKNOWN_APP) {
// The sender carried ciphertext verbatim (a packet it couldn't decrypt, e.g. a PKI DM,
// see startSend()). Restore it as an encrypted packet so the router decrypts it as if
// received over the air, instead of treating the ciphertext as a plaintext payload. The
// outer MeshPacket still carries from/to/id/channel/pki_encrypted, which decrypt needs.
p.which_payload_variant = meshtastic_MeshPacket_encrypted_tag;
memcpy(&p.encrypted.bytes, scratch.data.bytes, scratch.data.size);
p.encrypted.size = scratch.data.size;
} else {
// Extract the original payload and replace
memcpy(&p.decoded.payload, &scratch.data, sizeof(scratch.data));
// Switch the port from PortNum_SIMULATOR_APP back to the original PortNum
p.decoded.portnum = scratch.portnum;
}
} else
LOG_ERROR("Error decoding proto for simulator message");
}
// Let SimRadio receive as if it did via its LoRa chip
startReceive(&p);
}
void SimRadio::startReceive(meshtastic_MeshPacket *p)
{
#ifdef USERPREFS_SIMRADIO_EMULATE_COLLISIONS
if (isActivelyReceiving()) {
LOG_WARN("Collision detected, dropping current and previous packet");
rxBad++;
airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket, true));
packetPool.release(receivingPacket);
receivingPacket = nullptr;
return;
} else if (sendingPacket) {
uint32_t airtimeLeft = tillRun(millis());
if (airtimeLeft <= 0) {
LOG_WARN("Transmitting packet was already done");
handleTransmitInterrupt(); // Finish sending first
} else if ((interval - airtimeLeft) > preambleTimeMsec) {
// Only if transmitting for longer than preamble there is a collision
// (channel should actually be detected as active otherwise)
LOG_WARN("Collision detected during transmission");
return;
}
}
receivingPacket = packetPool.allocCopy(*p);
if (!receivingPacket) {
return;
}
isReceiving = true;
uint32_t airtimeMsec = getPacketTime(p, true);
notifyLater(airtimeMsec, ISR_RX, false); // Model the time it is busy receiving
#else
receivingPacket = packetPool.allocCopy(*p);
if (!receivingPacket) {
return;
}
isReceiving = true;
handleReceiveInterrupt(); // Simulate receiving the packet immediately
startTransmitTimer();
#endif
}
meshtastic_QueueStatus SimRadio::getQueueStatus()
{
meshtastic_QueueStatus qs;
qs.res = qs.mesh_packet_id = 0;
qs.free = txQueue.getFree();
qs.maxlen = txQueue.getMaxLen();
return qs;
}
void SimRadio::handleReceiveInterrupt()
{
if (receivingPacket == nullptr) {
return;
}
if (!isReceiving) {
LOG_DEBUG("*** WAS_ASSERT *** handleReceiveInterrupt outside receive mode");
return;
}
LOG_TRACE("HANDLE RECEIVE INTERRUPT");
rxGood++;
meshtastic_MeshPacket *mp = packetPool.allocCopy(*receivingPacket); // keep a copy in packetPool
packetPool.release(receivingPacket); // release the original
receivingPacket = nullptr;
if (!mp)
return;
printPacket("Lora RX", mp);
airTime->logAirtime(RX_LOG, RadioInterface::getPacketTime(mp, true));
deliverToReceiver(mp);
}
size_t SimRadio::getPacketLength(meshtastic_MeshPacket *mp)
{
auto &p = mp->decoded;
return (size_t)p.payload.size + sizeof(PacketHeader);
}
int16_t SimRadio::readData(uint8_t *data, size_t len)
{
int16_t state = RADIOLIB_ERR_NONE;
if (state == RADIOLIB_ERR_NONE) {
// add null terminator
data[len] = 0;
}
return state;
}
/**
* Calculate airtime per
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
* section 4
*
* @return num msecs for the packet
*/
uint32_t SimRadio::getPacketTime(uint32_t pl, bool received)
{
float bandwidthHz = bw * 1000.0f;
bool headDisable = false; // we currently always use the header
float tSym = (1 << sf) / bandwidthHz;
bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms
float tPreamble = (preambleLength + 4.25f) * tSym;
float numPayloadSym =
8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f);
float tPayload = numPayloadSym * tSym;
float tPacket = tPreamble + tPayload;
uint32_t msecs = tPacket * 1000;
return msecs;
}
int16_t SimRadio::getCurrentRSSI()
{
// Simulated radio - return a reasonable default noise floor
return -120;
}