firmware/src/mesh/RadioLibInterface.cpp

408 lines
14 KiB
C++
Raw Normal View History

#include "RadioLibInterface.h"
2020-04-30 19:37:58 +00:00
#include "MeshTypes.h"
#include "NodeDB.h"
#include "SPILock.h"
2022-04-10 05:42:43 +00:00
#include "configuration.h"
#include "error.h"
#include "mesh-pb-constants.h"
2020-04-30 19:37:58 +00:00
#include <pb_decode.h>
#include <pb_encode.h>
// 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);
#ifdef ARCH_PORTDUINO
2022-06-16 10:22:01 +00:00
void LockingModule::SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint8_t *dataIn, uint8_t numBytes)
{
concurrency::LockGuard g(spiLock);
Module::SPItransfer(cmd, reg, dataOut, dataIn, numBytes);
}
#else
2022-06-13 14:10:16 +00:00
void LockingModule::SPIbeginTransaction()
{
2022-06-13 14:10:16 +00:00
spiLock->lock();
2022-06-13 14:10:16 +00:00
Module::SPIbeginTransaction();
}
void LockingModule::SPIendTransaction()
{
spiLock->unlock();
Module::SPIendTransaction();
}
2022-06-16 10:22:01 +00:00
#endif
RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
SPIClass &spi, PhysicalLayer *_iface)
2020-10-11 01:18:47 +00:00
: NotifiedWorkerThread("RadioIf"), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
{
instance = this;
#if defined(ARCH_STM32WL) && defined(USE_SX1262)
module.setCb_digitalWrite(stm32wl_emulate_digitalWrite);
module.setCb_digitalRead(stm32wl_emulate_digitalRead);
#endif
}
#ifdef ARCH_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
void INTERRUPT_ATTR RadioLibInterface::isrLevel0Common(PendingISR cause)
{
instance->disableInterrupt();
BaseType_t xHigherPriorityTaskWoken;
2020-10-10 01:57:57 +00:00
instance->notifyFromISR(&xHigherPriorityTaskWoken, cause, true);
/* 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);
}
void INTERRUPT_ATTR RadioLibInterface::isrRxLevel0()
{
isrLevel0Common(ISR_RX);
}
void INTERRUPT_ATTR RadioLibInterface::isrTxLevel0()
{
isrLevel0Common(ISR_TX);
}
/** Our ISR code currently needs this to find our active instance
*/
RadioLibInterface *RadioLibInterface::instance;
/** 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 we've been trying to send the same packet more than one minute and we haven't gotten a
// TX IRQ from the radio, the radio is probably broken.
if (busyTx && (millis() - lastTxStart > 60000)) {
DEBUG_MSG("Hardware Failure! busyTx for more than 60s\n");
RECORD_CRITICALERROR(CriticalErrorCode_TRANSMIT_FAILED);
#ifdef ARCH_ESP32
if (busyTx && (millis() - lastTxStart > 65000)) // After 5s more, reboot
ESP.restart();
#endif
}
2020-05-03 02:51:25 +00:00
if (busyRx)
DEBUG_MSG("Can not send yet, busyRx\n");
return false;
} else
return true;
}
/// 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
ErrorCode RadioLibInterface::send(MeshPacket *p)
{
2022-05-25 01:06:53 +00:00
2022-04-25 15:02:51 +00:00
#ifndef DISABLE_WELCOME_UNSET
2022-05-25 01:06:53 +00:00
if (config.lora.region != Config_LoRaConfig_RegionCode_UNSET) {
if (disabled || !config.lora.tx_enabled) {
if (config.lora.region != Config_LoRaConfig_RegionCode_UNSET) {
if (disabled || !config.lora.tx_enabled) {
2022-05-25 01:06:53 +00:00
DEBUG_MSG("send - lora_tx_disabled\n");
packetPool.release(p);
return ERRNO_DISABLED;
}
2022-04-25 15:02:51 +00:00
2022-05-25 01:06:53 +00:00
} else {
DEBUG_MSG("send - lora_tx_disabled because RegionCode_Unset\n");
packetPool.release(p);
return ERRNO_DISABLED;
}
2022-04-10 05:42:43 +00:00
}
}
2022-04-25 15:02:51 +00:00
#else
if (disabled || config.lora.tx_disabled) {
2022-04-25 15:02:51 +00:00
DEBUG_MSG("send - lora_tx_disabled\n");
packetPool.release(p);
return ERRNO_DISABLED;
}
#endif
2022-05-25 01:06:53 +00:00
// Sometimes when testing it is useful to be able to never turn on the xmitter
#ifndef LORA_DISABLE_SENDING
2022-05-25 01:06:53 +00:00
printPacket("enqueuing for send", p);
2022-05-25 01:06:53 +00:00
DEBUG_MSG("txGood=%d,rxGood=%d,rxBad=%d\n", txGood, rxGood, rxBad);
ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN;
2022-05-25 01:06:53 +00:00
if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks
packetPool.release(p);
return res;
}
2022-05-25 01:06:53 +00:00
// set (random) transmit delay to let others reconfigure their radio,
// to avoid collisions and implement timing-based flooding
// DEBUG_MSG("Set random delay before transmitting.\n");
setTransmitDelay();
2022-05-25 01:06:53 +00:00
return res;
#else
packetPool.release(p);
return ERRNO_DISABLED;
#endif
2022-05-25 01:06:53 +00:00
}
2022-05-25 01:06:53 +00:00
bool RadioLibInterface::canSleep()
{
bool res = txQueue.empty();
if (!res) // only print debug messages if we are vetoing sleep
DEBUG_MSG("radio wait to sleep, txEmpty=%d\n", res);
2022-05-25 01:06:53 +00:00
return res;
}
2022-05-25 01:06:53 +00:00
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
bool RadioLibInterface::cancelSending(NodeNum from, PacketId id)
{
auto p = txQueue.remove(from, id);
if (p)
packetPool.release(p); // free the packet we just removed
2022-05-25 01:06:53 +00:00
bool result = (p != NULL);
DEBUG_MSG("cancelSending id=0x%x, removed=%d\n", id, result);
return result;
}
2022-05-25 01:06:53 +00:00
/** radio helper thread callback.
We never immediately transmit after any operation (either Rx or Tx). Instead we should wait a random multiple of
'slotTimes' (see definition in RadioInterface.h) taken from a contention window (CW) to lower the chance of collision.
The CW size is determined by setTransmitDelay() and depends either on the current channel utilization or SNR in case
of a flooding message. After this, we perform channel activity detection (CAD) and reset the transmit delay if it is
currently active.
2022-05-25 01:06:53 +00:00
*/
void RadioLibInterface::onNotify(uint32_t notification)
{
switch (notification) {
case ISR_TX:
handleTransmitInterrupt();
startReceive();
// DEBUG_MSG("tx complete - starting timer\n");
startTransmitTimer();
break;
case ISR_RX:
handleReceiveInterrupt();
startReceive();
// DEBUG_MSG("rx complete - starting timer\n");
startTransmitTimer();
break;
case TRANSMIT_DELAY_COMPLETED:
// DEBUG_MSG("delay done\n");
// 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()) {
// DEBUG_MSG("Currently Rx/Tx-ing: set random delay\n");
setTransmitDelay(); // currently Rx/Tx-ing: reset random delay
} else {
2022-05-25 01:06:53 +00:00
if (isChannelActive()) { // check if there is currently a LoRa packet on the channel
// DEBUG_MSG("Channel is active: set random delay\n");
setTransmitDelay(); // reset random delay
} else {
// Send any outgoing packets we have ready
MeshPacket *txp = txQueue.dequeue();
assert(txp);
startSend(txp);
// Packet has been sent, count it toward our TX airtime utilization.
uint32_t xmitMsec = getPacketTime(txp);
airTime->logAirtime(TX_LOG, xmitMsec);
}
}
2022-05-25 01:06:53 +00:00
} else {
// DEBUG_MSG("done with txqueue\n");
}
2022-05-25 01:06:53 +00:00
break;
default:
assert(0); // We expected to receive a valid notification from the ISR
}
}
2022-05-25 01:06:53 +00:00
void RadioLibInterface::setTransmitDelay()
{
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.
DEBUG_MSG("rx_snr found. hop_limit:%d rx_snr:%f\n", p->hop_limit, p->rx_snr);
startTransmitTimerSNR(p->rx_snr);
}
}
2022-05-25 01:06:53 +00:00
void RadioLibInterface::startTransmitTimer(bool withDelay)
{
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (!txQueue.empty()) {
uint32_t delay = !withDelay ? 1 : getTxDelayMsec();
// DEBUG_MSG("xmit timer %d\n", delay);
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
}
}
2022-05-25 01:06:53 +00:00
void RadioLibInterface::startTransmitTimerSNR(float snr)
{
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (!txQueue.empty()) {
uint32_t delay = getTxDelayMsecWeighted(snr);
// DEBUG_MSG("xmit timer %d\n", delay);
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
}
}
2022-05-25 01:06:53 +00:00
void RadioLibInterface::handleTransmitInterrupt()
{
// DEBUG_MSG("handling lora TX interrupt\n");
// This can be null if we forced the device to enter standby mode. In that case
// ignore the transmit interrupt
if (sendingPacket)
completeSending();
2020-04-30 20:50:40 +00:00
}
2022-05-25 01:06:53 +00:00
void RadioLibInterface::completeSending()
{
// We are careful to clear sending packet before calling printPacket because
// that can take a long time
auto p = sendingPacket;
sendingPacket = NULL;
2020-04-30 20:50:40 +00:00
2022-05-25 01:06:53 +00:00
if (p) {
txGood++;
printPacket("Completed sending", p);
2022-05-25 01:06:53 +00:00
// We are done sending that packet, release it
packetPool.release(p);
// DEBUG_MSG("Done with send\n");
}
}
2020-12-27 17:29:48 +00:00
2022-05-25 01:06:53 +00:00
void RadioLibInterface::handleReceiveInterrupt()
{
uint32_t xmitMsec;
assert(isReceiving);
isReceiving = false;
2022-05-25 01:06:53 +00:00
// read the number of actually received bytes
size_t length = iface->getPacketLength();
2022-05-25 01:06:53 +00:00
xmitMsec = getPacketTime(length);
2022-05-25 01:06:53 +00:00
int state = iface->readData(radiobuf, length);
if (state != RADIOLIB_ERR_NONE) {
2022-05-25 01:06:53 +00:00
DEBUG_MSG("ignoring received packet due to error=%d\n", state);
2020-04-30 23:36:59 +00:00
rxBad++;
2022-05-25 01:06:53 +00:00
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
2022-05-25 01:06:53 +00:00
2020-04-30 19:37:58 +00:00
} else {
2022-05-25 01:06:53 +00:00
// 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);
// check for short packets
if (payloadLen < 0) {
DEBUG_MSG("ignoring received packet too short\n");
rxBad++;
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
} else {
const PacketHeader *h = (PacketHeader *)radiobuf;
2020-04-30 23:36:59 +00:00
2022-05-25 01:06:53 +00:00
rxGood++;
2020-04-30 23:36:59 +00:00
2022-05-25 01:06:53 +00:00
// Note: we deliver _all_ packets to our router (i.e. our interface is intentionally promiscuous).
// This allows the router and other apps on our node to sniff packets (usually routing) between other
// nodes.
MeshPacket *mp = packetPool.allocZeroed();
2022-05-25 01:06:53 +00:00
mp->from = h->from;
mp->to = h->to;
mp->id = h->id;
mp->channel = h->channel;
assert(HOP_MAX <= PACKET_FLAGS_HOP_MASK); // If hopmax changes, carefully check this code
mp->hop_limit = h->flags & PACKET_FLAGS_HOP_MASK;
mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK);
2020-04-30 23:36:59 +00:00
2022-05-25 01:06:53 +00:00
addReceiveMetadata(mp);
mp->which_payload_variant = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point
2022-05-25 01:06:53 +00:00
assert(((uint32_t)payloadLen) <= sizeof(mp->encrypted.bytes));
memcpy(mp->encrypted.bytes, payload, payloadLen);
mp->encrypted.size = payloadLen;
2022-05-25 01:06:53 +00:00
printPacket("Lora RX", mp);
2022-05-25 01:06:53 +00:00
// xmitMsec = getPacketTime(mp);
airTime->logAirtime(RX_LOG, xmitMsec);
2020-12-27 17:29:48 +00:00
2022-05-25 01:06:53 +00:00
deliverToReceiver(mp);
}
}
}
2020-12-27 17:29:48 +00:00
2022-05-25 01:06:53 +00:00
/** start an immediate transmit */
void RadioLibInterface::startSend(MeshPacket * txp)
{
printPacket("Starting low level send", txp);
if (disabled || !config.lora.tx_enabled) {
2022-05-25 01:06:53 +00:00
DEBUG_MSG("startSend is dropping tx packet because we are disabled\n");
packetPool.release(txp);
} else {
setStandby(); // Cancel any already in process receives
2020-05-03 02:51:25 +00:00
2022-05-25 01:06:53 +00:00
configHardwareForSend(); // must be after setStandby
2020-06-16 13:26:21 +00:00
2022-05-25 01:06:53 +00:00
size_t numbytes = beginSending(txp);
2022-05-25 01:06:53 +00:00
int res = iface->startTransmit(radiobuf, numbytes);
if (res != RADIOLIB_ERR_NONE) {
RECORD_CRITICALERROR(CriticalErrorCode_RADIO_SPI_BUG);
2021-05-01 03:27:37 +00:00
2022-05-25 01:06:53 +00:00
// This send failed, but make sure to 'complete' it properly
completeSending();
startReceive(); // Restart receive mode (because startTransmit failed to put us in xmit mode)
}
2022-05-25 01:06:53 +00:00
// Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register
// bits
enableInterrupt(isrTxLevel0);
}
}