2020-04-29 21:54:03 +00:00
|
|
|
#include "RadioLibInterface.h"
|
2020-04-30 19:37:58 +00:00
|
|
|
#include "MeshTypes.h"
|
2020-05-03 02:51:25 +00:00
|
|
|
#include "OSTimer.h"
|
2020-04-30 19:37:58 +00:00
|
|
|
#include "mesh-pb-constants.h"
|
2020-04-30 23:36:59 +00:00
|
|
|
#include <NodeDB.h> // FIXME, this class shouldn't need to look into nodedb
|
2020-04-29 21:54:03 +00:00
|
|
|
#include <configuration.h>
|
2020-04-30 19:37:58 +00:00
|
|
|
#include <pb_decode.h>
|
|
|
|
#include <pb_encode.h>
|
2020-04-29 21:54:03 +00:00
|
|
|
|
|
|
|
// FIXME, we default to 4MHz SPI, SPI mode 0, check if the datasheet says it can really do that
|
2020-04-29 23:06:23 +00:00
|
|
|
static SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
|
2020-04-29 21:54:03 +00:00
|
|
|
|
|
|
|
RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
|
2020-04-29 23:28:11 +00:00
|
|
|
SPIClass &spi, PhysicalLayer *_iface)
|
2020-05-01 00:43:29 +00:00
|
|
|
: module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
|
2020-04-29 21:54:03 +00:00
|
|
|
{
|
2020-04-30 16:44:16 +00:00
|
|
|
assert(!instance); // We assume only one for now
|
|
|
|
instance = this;
|
2020-04-29 21:54:03 +00:00
|
|
|
}
|
|
|
|
|
2020-05-01 19:11:04 +00:00
|
|
|
#ifndef NO_ESP32
|
|
|
|
// ESP32 doesn't use that flag
|
|
|
|
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
|
|
|
|
#else
|
|
|
|
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x)
|
|
|
|
#endif
|
|
|
|
|
2020-05-02 15:29:51 +00:00
|
|
|
void INTERRUPT_ATTR RadioLibInterface::isrLevel0Common(PendingISR cause)
|
2020-04-30 16:44:16 +00:00
|
|
|
{
|
|
|
|
instance->disableInterrupt();
|
2020-05-01 19:11:04 +00:00
|
|
|
|
2020-05-02 15:29:51 +00:00
|
|
|
instance->pending = cause;
|
2020-05-01 19:11:04 +00:00
|
|
|
BaseType_t xHigherPriorityTaskWoken;
|
2020-05-02 15:29:51 +00:00
|
|
|
instance->notifyFromISR(&xHigherPriorityTaskWoken, cause, eSetValueWithOverwrite);
|
2020-05-01 19:11:04 +00:00
|
|
|
|
|
|
|
/* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE.
|
|
|
|
The macro used to do this is dependent on the port and may be called
|
|
|
|
portEND_SWITCHING_ISR. */
|
|
|
|
YIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
2020-04-30 16:44:16 +00:00
|
|
|
}
|
|
|
|
|
2020-05-02 15:29:51 +00:00
|
|
|
void INTERRUPT_ATTR RadioLibInterface::isrRxLevel0()
|
2020-04-30 16:44:16 +00:00
|
|
|
{
|
2020-05-02 15:29:51 +00:00
|
|
|
isrLevel0Common(ISR_RX);
|
|
|
|
}
|
2020-05-01 19:11:04 +00:00
|
|
|
|
2020-05-02 15:29:51 +00:00
|
|
|
void INTERRUPT_ATTR RadioLibInterface::isrTxLevel0()
|
|
|
|
{
|
|
|
|
isrLevel0Common(ISR_TX);
|
2020-04-30 16:44:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Our ISR code currently needs this to find our active instance
|
|
|
|
*/
|
|
|
|
RadioLibInterface *RadioLibInterface::instance;
|
|
|
|
|
2020-04-30 01:46:32 +00:00
|
|
|
/**
|
|
|
|
* Convert our modemConfig enum into wf, sf, etc...
|
|
|
|
*/
|
|
|
|
void RadioLibInterface::applyModemConfig()
|
|
|
|
{
|
2020-05-09 23:32:26 +00:00
|
|
|
RadioInterface::applyModemConfig();
|
|
|
|
|
2020-04-30 01:46:32 +00:00
|
|
|
switch (modemConfig) {
|
2020-05-01 04:42:11 +00:00
|
|
|
case Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range
|
2020-04-30 01:46:32 +00:00
|
|
|
bw = 125;
|
|
|
|
cr = 5;
|
|
|
|
sf = 7;
|
|
|
|
break;
|
2020-05-01 04:42:11 +00:00
|
|
|
case Bw500Cr45Sf128: ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range
|
2020-04-30 01:46:32 +00:00
|
|
|
bw = 500;
|
|
|
|
cr = 5;
|
|
|
|
sf = 7;
|
|
|
|
break;
|
2020-05-01 04:42:11 +00:00
|
|
|
case Bw31_25Cr48Sf512: ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range
|
2020-04-30 01:46:32 +00:00
|
|
|
bw = 31.25;
|
|
|
|
cr = 8;
|
|
|
|
sf = 9;
|
|
|
|
break;
|
2020-05-01 04:42:11 +00:00
|
|
|
case Bw125Cr48Sf4096:
|
2020-04-30 01:46:32 +00:00
|
|
|
bw = 125;
|
|
|
|
cr = 8;
|
|
|
|
sf = 12;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(0); // Unknown enum
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-01 15:32:16 +00:00
|
|
|
/** Could we send right now (i.e. either not actively receving or transmitting)? */
|
|
|
|
bool RadioLibInterface::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();
|
|
|
|
|
2020-05-03 02:51:25 +00:00
|
|
|
if (busyTx || busyRx) {
|
|
|
|
if (busyTx)
|
|
|
|
DEBUG_MSG("Can not send yet, busyTx\n");
|
|
|
|
if (busyRx)
|
|
|
|
DEBUG_MSG("Can not send yet, busyRx\n");
|
|
|
|
return false;
|
|
|
|
} else
|
|
|
|
return true;
|
2020-05-01 15:32:16 +00:00
|
|
|
}
|
|
|
|
|
2020-04-30 16:44:16 +00:00
|
|
|
/// Send a packet (possibly by enquing in a private fifo). This routine will
|
|
|
|
/// later free() the packet to pool. This routine is not allowed to stall because it is called from
|
|
|
|
/// bluetooth comms code. If the txmit queue is empty it might return an error
|
2020-04-29 21:54:03 +00:00
|
|
|
ErrorCode RadioLibInterface::send(MeshPacket *p)
|
|
|
|
{
|
2020-05-03 02:51:25 +00:00
|
|
|
DEBUG_MSG("enqueuing for send on mesh fr=0x%x,to=0x%x,id=%d (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, txGood,
|
|
|
|
rxGood, rxBad);
|
2020-05-02 15:29:51 +00:00
|
|
|
ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN;
|
2020-04-30 16:44:16 +00:00
|
|
|
|
2020-05-02 15:29:51 +00:00
|
|
|
if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks
|
|
|
|
packetPool.release(p);
|
2020-04-30 16:44:16 +00:00
|
|
|
return res;
|
|
|
|
}
|
2020-05-02 15:29:51 +00:00
|
|
|
|
2020-05-03 02:51:25 +00:00
|
|
|
// 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
|
|
|
|
startTransmitTimer(true);
|
2020-05-02 15:29:51 +00:00
|
|
|
|
|
|
|
return res;
|
2020-04-30 16:44:16 +00:00
|
|
|
}
|
|
|
|
|
2020-04-30 22:50:07 +00:00
|
|
|
bool RadioLibInterface::canSleep()
|
|
|
|
{
|
|
|
|
bool res = txQueue.isEmpty();
|
|
|
|
if (!res) // only print debug messages if we are vetoing sleep
|
|
|
|
DEBUG_MSG("radio wait to sleep, txEmpty=%d\n", txQueue.isEmpty());
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2020-05-03 02:51:25 +00:00
|
|
|
/** At the low end we want to pick a delay large enough that anyone who just completed sending (some other node)
|
|
|
|
* has had enough time to switch their radio back into receive mode.
|
|
|
|
*/
|
|
|
|
#define MIN_TX_WAIT_MSEC 100
|
|
|
|
|
|
|
|
/**
|
|
|
|
* At the high end, this value is used to spread node attempts across time so when they are replying to a packet
|
|
|
|
* they don't both check that the airwaves are clear at the same moment. As long as they are off by some amount
|
|
|
|
* one of the two will be first to start transmitting and the other will see that. I bet 500ms is more than enough
|
|
|
|
* to guarantee this.
|
|
|
|
*/
|
|
|
|
#define MAX_TX_WAIT_MSEC 2000 // stress test would still fail occasionally with 1000
|
|
|
|
|
2020-05-02 15:29:51 +00:00
|
|
|
/** radio helper thread callback.
|
|
|
|
|
|
|
|
We never immediately transmit after any operation (either rx or tx). Instead we should start receiving and
|
|
|
|
wait a random delay of 50 to 200 ms to make sure we are not stomping on someone else. The 50ms delay at the beginning ensures all
|
|
|
|
possible listeners have had time to finish processing the previous packet and now have their radio in RX state. The up to 200ms
|
|
|
|
random delay gives a chance for all possible senders to have high odds of detecting that someone else started transmitting first
|
|
|
|
and then they will wait until that packet finishes.
|
|
|
|
|
|
|
|
NOTE: the large flood rebroadcast delay might still be needed even with this approach. Because we might not be able to hear other
|
|
|
|
transmitters that we are potentially stomping on. Requires further thought.
|
|
|
|
|
2020-05-03 02:51:25 +00:00
|
|
|
FIXME, the MIN_TX_WAIT_MSEC and MAX_TX_WAIT_MSEC values should be tuned via logic analyzer later.
|
2020-05-02 15:29:51 +00:00
|
|
|
*/
|
2020-04-30 16:44:16 +00:00
|
|
|
void RadioLibInterface::loop()
|
|
|
|
{
|
2020-05-01 19:11:04 +00:00
|
|
|
pending = ISR_NONE;
|
|
|
|
|
2020-05-02 15:29:51 +00:00
|
|
|
switch (notification) {
|
|
|
|
case ISR_TX:
|
2020-05-01 19:11:04 +00:00
|
|
|
handleTransmitInterrupt();
|
2020-05-02 15:29:51 +00:00
|
|
|
startReceive();
|
|
|
|
startTransmitTimer();
|
|
|
|
break;
|
|
|
|
case ISR_RX:
|
2020-05-01 19:11:04 +00:00
|
|
|
handleReceiveInterrupt();
|
2020-05-02 15:29:51 +00:00
|
|
|
startReceive();
|
|
|
|
startTransmitTimer();
|
|
|
|
break;
|
|
|
|
case TRANSMIT_DELAY_COMPLETED:
|
|
|
|
// If we are not currently in receive mode, then restart the timer and try again later (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.isEmpty()) {
|
|
|
|
if (!canSendImmediately()) {
|
|
|
|
startTransmitTimer(); // try again in a little while
|
|
|
|
} else {
|
|
|
|
// Send any outgoing packets we have ready
|
|
|
|
MeshPacket *txp = txQueue.dequeuePtr(0);
|
|
|
|
assert(txp);
|
|
|
|
startSend(txp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2020-05-01 19:11:04 +00:00
|
|
|
assert(0); // We expected to receive a valid notification from the ISR
|
2020-05-02 15:29:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-03 02:51:25 +00:00
|
|
|
#ifndef NO_ESP32
|
|
|
|
#define USE_HW_TIMER
|
2020-05-10 04:02:56 +00:00
|
|
|
#else
|
|
|
|
// Not needed on NRF52
|
|
|
|
#define IRAM_ATTR
|
2020-05-03 02:51:25 +00:00
|
|
|
#endif
|
2020-05-01 19:11:04 +00:00
|
|
|
|
2020-05-03 02:51:25 +00:00
|
|
|
void IRAM_ATTR RadioLibInterface::timerCallback(void *p1, uint32_t p2)
|
2020-05-02 15:29:51 +00:00
|
|
|
{
|
|
|
|
RadioLibInterface *t = (RadioLibInterface *)p1;
|
|
|
|
|
|
|
|
t->timerRunning = false;
|
|
|
|
|
|
|
|
// We use without overwrite, so that if there is already an interrupt pending to be handled, that gets handle properly (the
|
|
|
|
// ISR handler will restart our timer)
|
2020-05-03 02:51:25 +00:00
|
|
|
#ifndef USE_HW_TIMER
|
2020-05-02 15:29:51 +00:00
|
|
|
t->notify(TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite);
|
2020-05-03 02:51:25 +00:00
|
|
|
#else
|
|
|
|
BaseType_t xHigherPriorityTaskWoken;
|
|
|
|
instance->notifyFromISR(&xHigherPriorityTaskWoken, TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite);
|
|
|
|
|
|
|
|
/* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE.
|
|
|
|
The macro used to do this is dependent on the port and may be called
|
|
|
|
portEND_SWITCHING_ISR. */
|
|
|
|
YIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
|
|
|
#endif
|
2020-04-30 19:37:58 +00:00
|
|
|
}
|
|
|
|
|
2020-05-02 15:29:51 +00:00
|
|
|
void RadioLibInterface::startTransmitTimer(bool withDelay)
|
2020-04-30 19:37:58 +00:00
|
|
|
{
|
2020-05-02 15:29:51 +00:00
|
|
|
// If we have work to do and the timer wasn't already scheduled, schedule it now
|
|
|
|
if (!timerRunning && !txQueue.isEmpty()) {
|
|
|
|
timerRunning = true;
|
2020-05-03 02:51:25 +00:00
|
|
|
uint32_t delay =
|
|
|
|
!withDelay ? 0 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values
|
|
|
|
// DEBUG_MSG("xmit timer %d\n", delay);
|
|
|
|
#ifdef USE_HW_TIMER
|
|
|
|
bool okay = scheduleHWCallback(timerCallback, this, 0, delay);
|
|
|
|
#else
|
|
|
|
bool okay = scheduleOSCallback(timerCallback, this, 0, delay);
|
|
|
|
#endif
|
|
|
|
assert(okay);
|
2020-04-30 16:44:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RadioLibInterface::handleTransmitInterrupt()
|
|
|
|
{
|
2020-05-01 05:53:21 +00:00
|
|
|
// DEBUG_MSG("handling lora TX interrupt\n");
|
|
|
|
assert(sendingPacket); // Were we sending? - FIXME, this was null coming out of light sleep due to RF95 ISR!
|
2020-04-30 16:44:16 +00:00
|
|
|
|
2020-04-30 20:50:40 +00:00
|
|
|
completeSending();
|
|
|
|
}
|
2020-04-30 16:44:16 +00:00
|
|
|
|
2020-04-30 20:50:40 +00:00
|
|
|
void RadioLibInterface::completeSending()
|
|
|
|
{
|
|
|
|
if (sendingPacket) {
|
2020-04-30 22:43:41 +00:00
|
|
|
txGood++;
|
2020-05-01 05:53:21 +00:00
|
|
|
DEBUG_MSG("Completed sending to=0x%x, id=%u\n", sendingPacket->to, sendingPacket->id);
|
2020-04-30 22:43:41 +00:00
|
|
|
|
2020-04-30 20:50:40 +00:00
|
|
|
// We are done sending that packet, release it
|
|
|
|
packetPool.release(sendingPacket);
|
|
|
|
sendingPacket = NULL;
|
|
|
|
// DEBUG_MSG("Done with send\n");
|
|
|
|
}
|
2020-04-30 16:44:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void RadioLibInterface::handleReceiveInterrupt()
|
|
|
|
{
|
2020-04-30 20:50:40 +00:00
|
|
|
assert(isReceiving);
|
|
|
|
isReceiving = false;
|
|
|
|
|
2020-04-30 19:37:58 +00:00
|
|
|
// read the number of actually received bytes
|
2020-05-01 00:43:29 +00:00
|
|
|
size_t length = iface->getPacketLength();
|
2020-04-30 16:44:16 +00:00
|
|
|
|
2020-05-01 00:43:29 +00:00
|
|
|
int state = iface->readData(radiobuf, length);
|
2020-04-30 19:37:58 +00:00
|
|
|
if (state != ERR_NONE) {
|
|
|
|
DEBUG_MSG("ignoring received packet due to error=%d\n", state);
|
2020-04-30 22:43:41 +00:00
|
|
|
rxBad++;
|
2020-04-30 19:37:58 +00:00
|
|
|
} else {
|
|
|
|
// Skip the 4 headers that are at the beginning of the rxBuf
|
|
|
|
int32_t payloadLen = length - sizeof(PacketHeader);
|
|
|
|
const uint8_t *payload = radiobuf + sizeof(PacketHeader);
|
2020-04-30 16:44:16 +00:00
|
|
|
|
2020-04-30 23:36:59 +00:00
|
|
|
// check for short packets
|
|
|
|
if (payloadLen < 0) {
|
|
|
|
DEBUG_MSG("ignoring received packet too short\n");
|
|
|
|
rxBad++;
|
2020-04-30 19:37:58 +00:00
|
|
|
} else {
|
2020-04-30 23:36:59 +00:00
|
|
|
const PacketHeader *h = (PacketHeader *)radiobuf;
|
|
|
|
uint8_t ourAddr = nodeDB.getNodeNum();
|
|
|
|
|
2020-05-03 02:51:25 +00:00
|
|
|
rxGood++;
|
2020-04-30 23:36:59 +00:00
|
|
|
if (h->to != 255 && h->to != ourAddr) {
|
|
|
|
DEBUG_MSG("ignoring packet not sent to us\n");
|
|
|
|
} else {
|
|
|
|
MeshPacket *mp = packetPool.allocZeroed();
|
|
|
|
|
|
|
|
mp->from = h->from;
|
|
|
|
mp->to = h->to;
|
|
|
|
mp->id = h->id;
|
2020-05-01 02:58:10 +00:00
|
|
|
addReceiveMetadata(mp);
|
2020-04-30 23:36:59 +00:00
|
|
|
|
2020-05-10 00:51:20 +00:00
|
|
|
mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point
|
|
|
|
assert(payloadLen <= sizeof(mp->encrypted.bytes));
|
|
|
|
memcpy(mp->encrypted.bytes, payload, payloadLen);
|
|
|
|
mp->encrypted.size = payloadLen;
|
|
|
|
|
|
|
|
DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id);
|
|
|
|
|
|
|
|
deliverToReceiver(mp);
|
2020-04-30 23:36:59 +00:00
|
|
|
}
|
2020-04-30 16:44:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** start an immediate transmit */
|
|
|
|
void RadioLibInterface::startSend(MeshPacket *txp)
|
|
|
|
{
|
2020-05-03 02:51:25 +00:00
|
|
|
DEBUG_MSG("Starting low level send from=0x%x, id=%u!\n", txp->from, txp->id);
|
|
|
|
setStandby(); // Cancel any already in process receives
|
|
|
|
|
2020-04-30 16:44:16 +00:00
|
|
|
size_t numbytes = beginSending(txp);
|
|
|
|
|
2020-05-01 00:43:29 +00:00
|
|
|
int res = iface->startTransmit(radiobuf, numbytes);
|
2020-04-30 17:00:40 +00:00
|
|
|
assert(res == ERR_NONE);
|
2020-04-30 16:44:16 +00:00
|
|
|
|
|
|
|
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
|
|
|
|
enableInterrupt(isrTxLevel0);
|
2020-04-29 21:54:03 +00:00
|
|
|
}
|