diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 96dcb948d..c665ea350 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -93,24 +93,25 @@ bool RadioLibInterface::canSendImmediately() /// bluetooth comms code. If the txmit queue is empty it might return an error ErrorCode RadioLibInterface::send(MeshPacket *p) { - + #ifndef DISABLE_WELCOME_UNSET - + if (config.lora.region != Config_LoRaConfig_RegionCode_Unset) { if (disabled || config.lora.tx_disabled) { + if (config.lora.region != Config_LoRaConfig_RegionCode_Unset) { + if (disabled || config.lora.tx_disabled) { + DEBUG_MSG("send - lora_tx_disabled\n"); + packetPool.release(p); + return ERRNO_DISABLED; + } - if (radioConfig.preferences.region != RegionCode_Unset) { - if (disabled || config.lora.tx_disabled) { - DEBUG_MSG("send - lora_tx_disabled\n"); - packetPool.release(p); - return ERRNO_DISABLED; + } else { + DEBUG_MSG("send - lora_tx_disabled because RegionCode_Unset\n"); + packetPool.release(p); + return ERRNO_DISABLED; + } } - - } else { - DEBUG_MSG("send - lora_tx_disabled because RegionCode_Unset\n"); - packetPool.release(p); - return ERRNO_DISABLED; } #else @@ -123,265 +124,266 @@ 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); - DEBUG_MSG("txGood=%d,rxGood=%d,rxBad=%d\n", txGood, rxGood, rxBad); - ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN; + DEBUG_MSG("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 + // DEBUG_MSG("Set random delay before transmitting.\n"); + setTransmitDelay(); - 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 - // DEBUG_MSG("Set random delay before transmitting.\n"); - setTransmitDelay(); - - return res; #else packetPool.release(p); return ERRNO_DISABLED; #endif -} + } -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); + 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); - 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); - DEBUG_MSG("cancelSending id=0x%x, removed=%d\n", id, result); - return result; -} + bool result = (p != NULL); + DEBUG_MSG("cancelSending id=0x%x, removed=%d\n", id, result); + return result; + } -/** radio helper thread callback. + /** 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 100ms to 100ms+shortPacketMsec to make sure we are not stomping on someone else. The 100ms 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 100ms+shortPacketMsec 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. + We never immediately transmit after any operation (either rx or tx). Instead we should start receiving and + wait a random delay of 100ms to 100ms+shortPacketMsec to make sure we are not stomping on someone else. The 100ms 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 100ms+shortPacketMsec 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. + 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. -FIXME, the MIN_TX_WAIT_MSEC and MAX_TX_WAIT_MSEC values should be tuned via logic analyzer later. -*/ -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"); + FIXME, the MIN_TX_WAIT_MSEC and MAX_TX_WAIT_MSEC values should be tuned via logic analyzer later. + */ + 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 { - 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 + // 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 { - // Send any outgoing packets we have ready - MeshPacket *txp = txQueue.dequeue(); - assert(txp); - startSend(txp); + 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); + // Packet has been sent, count it toward our TX airtime utilization. + uint32_t xmitMsec = getPacketTime(txp); + airTime->logAirtime(TX_LOG, xmitMsec); + } } + } else { + // DEBUG_MSG("done with txqueue\n"); } - } else { - // DEBUG_MSG("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. - DEBUG_MSG("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(); - // DEBUG_MSG("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); - // DEBUG_MSG("xmit timer %d\n", delay); - notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable - } -} - -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(); -} - -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); - // DEBUG_MSG("Done with send\n"); - } -} - -void RadioLibInterface::handleReceiveInterrupt() -{ - uint32_t xmitMsec; - assert(isReceiving); - 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 != ERR_NONE) { - DEBUG_MSG("ignoring received packet due to error=%d\n", state); - 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) { - DEBUG_MSG("ignoring received packet too short\n"); - rxBad++; - airTime->logAirtime(RX_ALL_LOG, xmitMsec); - } else { - const PacketHeader *h = (PacketHeader *)radiobuf; - - rxGood++; - - // 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_payloadVariant = 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); - - // xmitMsec = getPacketTime(mp); - airTime->logAirtime(RX_LOG, xmitMsec); - - deliverToReceiver(mp); + break; + default: + assert(0); // We expected to receive a valid notification from the ISR } } -} -/** start an immediate transmit */ -void RadioLibInterface::startSend(MeshPacket *txp) -{ - printPacket("Starting low level send", txp); - if (disabled || config.lora.tx_disabled) { - DEBUG_MSG("startSend is dropping tx packet because we are disabled\n"); - packetPool.release(txp); - } else { - setStandby(); // Cancel any already in process receives + 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. - configHardwareForSend(); // must be after setStandby + /* 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); + } + } - size_t numbytes = beginSending(txp); + 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 + } + } - int res = iface->startTransmit(radiobuf, numbytes); - if (res != ERR_NONE) { - RECORD_CRITICALERROR(CriticalErrorCode_RadioSpiBug); + 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 + } + } - // This send failed, but make sure to 'complete' it properly + 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(); - 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); } -} + + 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); + // DEBUG_MSG("Done with send\n"); + } + } + + void RadioLibInterface::handleReceiveInterrupt() + { + uint32_t xmitMsec; + assert(isReceiving); + 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 != ERR_NONE) { + DEBUG_MSG("ignoring received packet due to error=%d\n", state); + 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) { + DEBUG_MSG("ignoring received packet too short\n"); + rxBad++; + airTime->logAirtime(RX_ALL_LOG, xmitMsec); + } else { + const PacketHeader *h = (PacketHeader *)radiobuf; + + rxGood++; + + // 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_payloadVariant = 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); + + // xmitMsec = getPacketTime(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_disabled) { + DEBUG_MSG("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 != ERR_NONE) { + RECORD_CRITICALERROR(CriticalErrorCode_RadioSpiBug); + + // 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); + } + }