diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 5861a01fc..dff195be1 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -3,8 +3,8 @@ #include "NodeDB.h" #include "SPILock.h" #include "configuration.h" -#include "main.h" #include "error.h" +#include "main.h" #include "mesh-pb-constants.h" #include #include @@ -134,29 +134,29 @@ ErrorCode RadioLibInterface::send(MeshPacket *p) #endif - // Sometimes when testing it is useful to be able to never turn on the xmitter + // Sometimes when testing it is useful to be able to never turn on the xmitter #ifndef LORA_DISABLE_SENDING - printPacket("enqueuing for send", p); + printPacket("enqueuing for send", p); - LOG_DEBUG("txGood=%d,rxGood=%d,rxBad=%d\n", txGood, rxGood, rxBad); - ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN; - - 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_DEBUG("Set random delay before transmitting.\n"); - setTransmitDelay(); + LOG_DEBUG("txGood=%d,rxGood=%d,rxBad=%d\n", txGood, rxGood, rxBad); + ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN; + 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_DEBUG("Set random delay before transmitting.\n"); + setTransmitDelay(); + + return res; #else packetPool.release(p); return ERRNO_DISABLED; #endif - } +} QueueStatus RadioLibInterface::getQueueStatus() { @@ -169,246 +169,247 @@ QueueStatus RadioLibInterface::getQueueStatus() return qs; } - bool RadioLibInterface::canSleep() - { - bool res = txQueue.empty(); - if (!res) // only print debug messages if we are vetoing sleep - LOG_DEBUG("radio wait to sleep, txEmpty=%d\n", res); +bool RadioLibInterface::canSleep() +{ + bool res = txQueue.empty(); + if (!res) // only print debug messages if we are vetoing sleep + LOG_DEBUG("radio wait to sleep, txEmpty=%d\n", res); - return res; - } + return res; +} - /** 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 +/** 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 - bool result = (p != NULL); - LOG_DEBUG("cancelSending id=0x%x, removed=%d\n", id, result); - return result; - } + bool result = (p != NULL); + LOG_DEBUG("cancelSending id=0x%x, removed=%d\n", id, result); + return result; +} - /** 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. - */ - void RadioLibInterface::onNotify(uint32_t notification) - { - switch (notification) { - case ISR_TX: - handleTransmitInterrupt(); - startReceive(); - // LOG_DEBUG("tx complete - starting timer\n"); - startTransmitTimer(); - break; - case ISR_RX: - handleReceiveInterrupt(); - startReceive(); - // LOG_DEBUG("rx complete - starting timer\n"); - startTransmitTimer(); - break; - case TRANSMIT_DELAY_COMPLETED: - // LOG_DEBUG("delay done\n"); +/** 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. +*/ +void RadioLibInterface::onNotify(uint32_t notification) +{ + switch (notification) { + case ISR_TX: + handleTransmitInterrupt(); + startReceive(); + // LOG_DEBUG("tx complete - starting timer\n"); + startTransmitTimer(); + break; + case ISR_RX: + handleReceiveInterrupt(); + startReceive(); + // LOG_DEBUG("rx complete - starting timer\n"); + startTransmitTimer(); + break; + case TRANSMIT_DELAY_COMPLETED: + // LOG_DEBUG("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()) { - // LOG_DEBUG("Currently Rx/Tx-ing: set random delay\n"); - setTransmitDelay(); // currently Rx/Tx-ing: reset random delay + // 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\n"); + 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\n"); + setTransmitDelay(); // 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\n"); - setTransmitDelay(); // reset random delay - } else { - // Send any outgoing packets we have ready - MeshPacket *txp = txQueue.dequeue(); - assert(txp); - startSend(txp); + // 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); - } + // Packet has been sent, count it toward our TX airtime utilization. + uint32_t xmitMsec = getPacketTime(txp); + airTime->logAirtime(TX_LOG, xmitMsec); } - } else { - // LOG_DEBUG("done with txqueue\n"); } - break; - default: - assert(0); // We expected to receive a valid notification from the ISR - } - } - - 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. - LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f\n", p->hop_limit, p->rx_snr); - startTransmitTimerSNR(p->rx_snr); + // LOG_DEBUG("done with txqueue\n"); } + break; + default: + assert(0); // We expected to receive a valid notification from the ISR + } +} + +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. + LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f\n", p->hop_limit, p->rx_snr); + startTransmitTimerSNR(p->rx_snr); + } +} + +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(); + // LOG_DEBUG("xmit timer %d\n", delay); + notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable + } +} + +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); + // LOG_DEBUG("xmit timer %d\n", delay); + notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable + } +} + +void RadioLibInterface::handleTransmitInterrupt() +{ + // LOG_DEBUG("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(); +} + +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; + + if (p) { + txGood++; + printPacket("Completed sending", p); + + // We are done sending that packet, release it + packetPool.release(p); + // LOG_DEBUG("Done with send\n"); + } +} + +void RadioLibInterface::handleReceiveInterrupt() +{ + uint32_t xmitMsec; + + // when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race + // Condition? + if (!isReceiving) { + LOG_DEBUG("*** WAS_ASSERT *** handleReceiveInterrupt called when not in receive mode\n"); + return; } - 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(); - // LOG_DEBUG("xmit timer %d\n", delay); - notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable - } - } + isReceiving = false; - 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); - // LOG_DEBUG("xmit timer %d\n", delay); - notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable - } - } + // read the number of actually received bytes + size_t length = iface->getPacketLength(); - void RadioLibInterface::handleTransmitInterrupt() - { - // LOG_DEBUG("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(); - } + xmitMsec = getPacketTime(length); - 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; + int state = iface->readData(radiobuf, length); + if (state != RADIOLIB_ERR_NONE) { + LOG_ERROR("ignoring received packet due to error=%d\n", state); + rxBad++; - if (p) { - txGood++; - printPacket("Completed sending", p); + airTime->logAirtime(RX_ALL_LOG, xmitMsec); - // We are done sending that packet, release it - packetPool.release(p); - // LOG_DEBUG("Done with send\n"); - } - } + } 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); - void RadioLibInterface::handleReceiveInterrupt() - { - uint32_t xmitMsec; - - // when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race Condition? - if (!isReceiving) { - LOG_DEBUG("*** WAS_ASSERT *** handleReceiveInterrupt called when not in receive mode\n"); - return; - } - - isReceiving = false; - - // read the number of actually received bytes - size_t length = iface->getPacketLength(); - - xmitMsec = getPacketTime(length); - - int state = iface->readData(radiobuf, length); - if (state != RADIOLIB_ERR_NONE) { - LOG_ERROR("ignoring received packet due to error=%d\n", state); + // check for short packets + if (payloadLen < 0) { + LOG_WARN("ignoring received packet too short\n"); rxBad++; - airTime->logAirtime(RX_ALL_LOG, xmitMsec); - } 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); - - // check for short packets - if (payloadLen < 0) { - LOG_WARN("ignoring received packet too short\n"); - rxBad++; - airTime->logAirtime(RX_ALL_LOG, xmitMsec); - } else { - const PacketHeader *h = (PacketHeader *)radiobuf; - rxGood++; - // altered packet with "from == 0" can do Remote Node Administration without permission - if (h->from == 0) { - LOG_WARN("ignoring received packet without sender\n"); - return; - } - - // 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(); - - 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); - - addReceiveMetadata(mp); - - mp->which_payload_variant = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point - assert(((uint32_t)payloadLen) <= sizeof(mp->encrypted.bytes)); - memcpy(mp->encrypted.bytes, payload, payloadLen); - mp->encrypted.size = payloadLen; - - printPacket("Lora RX", mp); - - airTime->logAirtime(RX_LOG, xmitMsec); - - deliverToReceiver(mp); - } - } - } - - /** start an immediate transmit */ - void RadioLibInterface::startSend(MeshPacket * txp) - { - printPacket("Starting low level send", txp); - if (disabled || !config.lora.tx_enabled) { - LOG_WARN("startSend is dropping tx packet because we are disabled\n"); - packetPool.release(txp); - } else { - setStandby(); // Cancel any already in process receives - - configHardwareForSend(); // must be after setStandby - - size_t numbytes = beginSending(txp); - - int res = iface->startTransmit(radiobuf, numbytes); - if (res != RADIOLIB_ERR_NONE) { - LOG_ERROR("startTransmit failed, error=%d\n", res); - RECORD_CRITICALERROR(CriticalErrorCode_RADIO_SPI_BUG); - - // This send failed, but make sure to 'complete' it properly - completeSending(); - startReceive(); // Restart receive mode (because startTransmit failed to put us in xmit mode) + const PacketHeader *h = (PacketHeader *)radiobuf; + rxGood++; + // altered packet with "from == 0" can do Remote Node Administration without permission + if (h->from == 0) { + LOG_WARN("ignoring received packet without sender\n"); + return; } - // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register - // bits - enableInterrupt(isrTxLevel0); + // 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(); + + 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); + + addReceiveMetadata(mp); + + mp->which_payload_variant = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point + assert(((uint32_t)payloadLen) <= sizeof(mp->encrypted.bytes)); + memcpy(mp->encrypted.bytes, payload, payloadLen); + mp->encrypted.size = payloadLen; + + printPacket("Lora RX", mp); + + airTime->logAirtime(RX_LOG, xmitMsec); + + deliverToReceiver(mp); } } +} + +/** start an immediate transmit */ +void RadioLibInterface::startSend(MeshPacket *txp) +{ + printPacket("Starting low level send", txp); + if (disabled || !config.lora.tx_enabled) { + LOG_WARN("startSend is dropping tx packet because we are disabled\n"); + packetPool.release(txp); + } else { + setStandby(); // Cancel any already in process receives + + configHardwareForSend(); // must be after setStandby + + size_t numbytes = beginSending(txp); + + int res = iface->startTransmit(radiobuf, numbytes); + if (res != RADIOLIB_ERR_NONE) { + LOG_ERROR("startTransmit failed, error=%d\n", res); + RECORD_CRITICALERROR(CriticalErrorCode_RADIO_SPI_BUG); + + // This send failed, but make sure to 'complete' it properly + completeSending(); + startReceive(); // Restart receive mode (because startTransmit failed to put us in xmit mode) + } + + // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register + // bits + enableInterrupt(isrTxLevel0); + } +}