#pragma once #include "MeshPacketQueue.h" #include "RadioInterface.h" #include "concurrency/NotifiedWorkerThread.h" #include #include // ESP32 has special rules about ISR code #ifdef ARDUINO_ARCH_ESP32 #define INTERRUPT_ATTR IRAM_ATTR #else #define INTERRUPT_ATTR #endif #define RADIOLIB_PIN_TYPE uint32_t // In addition to the default Rx flags, we need the PREAMBLE_DETECTED flag to detect whether we are actively receiving #define MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS (RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1 << RADIOLIB_IRQ_PREAMBLE_DETECTED)) #define AGC_RESET_INTERVAL_MS (60 * 1000) // 60 seconds /** * We need to override the RadioLib ArduinoHal class to add mutex protection for SPI bus access */ class LockingArduinoHal : public ArduinoHal { public: LockingArduinoHal(SPIClass &spi, SPISettings spiSettings) : ArduinoHal(spi, spiSettings){}; void spiBeginTransaction() override; void spiEndTransaction() override; #if ARCH_PORTDUINO void spiTransfer(uint8_t *out, size_t len, uint8_t *in) override; #endif }; #if defined(USE_STM32WLx) /** * A wrapper for the RadioLib STM32WLx_Module class, that doesn't connect any pins as they are virtual */ class STM32WLx_ModuleWrapper : public STM32WLx_Module { public: STM32WLx_ModuleWrapper(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) : STM32WLx_Module(){}; }; #endif class RadioLibInterface : public RadioInterface, protected concurrency::NotifiedWorkerThread { /// Used as our notification from the ISR enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED }; /** * Raw ISR handler that just calls our polymorphic method */ static void isrTxLevel0(), isrLevel0Common(PendingISR code); MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE); protected: ModemType_t modemType = RADIOLIB_MODEM_LORA; DataRate_t getDataRate() const { return {.lora = {.spreadingFactor = sf, .bandwidth = bw, .codingRate = cr}}; } PacketConfig_t getPacketConfig() const { return {.lora = {.preambleLength = preambleLength, .implicitHeader = false, .crcEnabled = true, // We use auto LDRO, meaning it is enabled if the symbol time is >= 16msec .ldrOptimize = (1 << sf) / bw >= 16}}; } /** * We use a meshtastic sync word, but hashed with the Channel name. For releases before 1.2 we used 0x12 (or for very old * loads 0x14) Note: do not use 0x34 - that is reserved for lorawan * * We now use 0x2b (so that someday we can possibly use NOT 2b - because that would be funny pun). We will be staying with * this code for a long time. */ const uint8_t syncWord = 0x2b; float currentLimit = 100; // 100mA OCP - Should be acceptable for RFM95/SX127x chipset. #if !defined(USE_STM32WLx) Module module; // The HW interface to the radio #else STM32WLx_ModuleWrapper module; #endif /** * provides lowest common denominator RadioLib API */ PhysicalLayer *iface; /// are _trying_ to receive a packet currently (note - we might just be waiting for one) bool isReceiving = false; protected: // Noise floor tracking - rolling window of samples. static const uint8_t NOISE_FLOOR_SAMPLES = 20; static const int32_t NOISE_FLOOR_DEFAULT = -120; static const int32_t NOISE_FLOOR_VALID_MIN = -127; static const int32_t NOISE_FLOOR_INVALID = -128; int32_t noiseFloorSamples[NOISE_FLOOR_SAMPLES]; uint8_t currentSampleIndex = 0; bool isNoiseFloorBufferFull = false; uint32_t lastNoiseFloorUpdate = 0; static const uint32_t NOISE_FLOOR_UPDATE_INTERVAL_MS = 5000; int32_t currentNoiseFloor = NOISE_FLOOR_DEFAULT; /** * Pure virtual hook for derived radio interfaces to provide instantaneous RSSI. * Implementations should return dBm, or an invalid value that updateNoiseFloor() * can reject. */ virtual int16_t getCurrentRSSI() = 0; public: /** Our ISR code currently needs this to find our active instance */ static RadioLibInterface *instance; /** Clear instance on destruction so stale pointer checks in loop() are safe */ virtual ~RadioLibInterface() { if (instance == this) instance = nullptr; } /** * Get the current calculated noise floor in dBm * Returns -120 dBm if not yet calibrated */ int32_t getNoiseFloor(); /** * Calculate the average noise floor from collected samples */ int32_t getAverageNoiseFloor(); /** * Glue functions called from ISR land */ virtual void disableInterrupt() = 0; /** * Enable a particular ISR callback glue function */ virtual void enableInterrupt(void (*)()) = 0; /** * Poll as a backup to catch missed edge-triggered interrupts. */ void pollMissedIrqs(); /** * Reset AGC by power-cycling the analog frontend. * Subclasses override with chip-specific calibration sequences. * Safe to call periodically — skips if currently sending or receiving. */ virtual void resetAGC(); /** * Debugging counts */ uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0; uint16_t txDrop = 0; public: RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, PhysicalLayer *iface = NULL); virtual ErrorCode send(meshtastic_MeshPacket *p) override; /** * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) * * This method must be used before putting the CPU into deep or light sleep. */ virtual bool canSleep() override; /** * Start waiting to receive a message * * External functions can call this method to wake the device from sleep. * Subclasses must override and call this base method */ virtual void startReceive(); /** can we detect a LoRa preamble on the current channel? */ virtual bool isChannelActive() = 0; /** are we actively receiving a packet (only called during receiving state) * This method is only public to facilitate debugging. Do not call. */ virtual bool isActivelyReceiving() = 0; /** Are we are currently sending a packet? * This method is public, intending to expose this information to other firmware components */ virtual bool isSending(); /** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */ virtual bool cancelSending(NodeNum from, PacketId id) override; /** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */ virtual bool findInTxQueue(NodeNum from, PacketId id) override; /** * Update the noise floor measurement by sampling RSSI from a slow path. * This should not be called from radio interrupt or TX/RX critical paths. */ void updateNoiseFloor(); /** * Check if we have collected any noise floor samples */ bool hasNoiseFloorSamples(); /** * Get the number of samples in the rolling window */ uint8_t getNoiseFloorSampleCount(); /** * Reset the noise floor calibration * Will automatically restart collection */ void resetNoiseFloor(); /** * Request randomness sourced from the LoRa modem, if supported by the active RadioLib interface. * @return true if len bytes were produced, false otherwise. */ bool randomBytes(uint8_t *buffer, size_t length); private: uint8_t getNoiseFloorSampleCountInternal() const; int32_t getAverageNoiseFloorInternal() const; /** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually * doing the transmit */ void setTransmitDelay(); /** * random timer with certain min. and max. settings * @return Timestamp after which the packet may be sent */ void startTransmitTimer(bool withDelay = true); /** * timer scaled to SNR of to be flooded packet * @return Timestamp after which the packet may be sent */ void startTransmitTimerRebroadcast(meshtastic_MeshPacket *p); void handleTransmitInterrupt(); void handleReceiveInterrupt(); static void timerCallback(void *p1, uint32_t p2); virtual void onNotify(uint32_t notification) override; /** start an immediate transmit * This method is virtual so subclasses can hook as needed, subclasses should not call directly * @return true if packet was sent */ virtual bool startSend(meshtastic_MeshPacket *txp); meshtastic_QueueStatus getQueueStatus(); protected: uint32_t activeReceiveStart = 0; bool receiveDetected(uint16_t irq, unsigned long syncWordHeaderValidFlag, unsigned long preambleDetectedFlag); /** Do any hardware setup needed on entry into send configuration for the radio. * Subclasses can customize, but must also call this base method */ virtual void configHardwareForSend(); /** Could we send right now (i.e. either not actively receiving or transmitting)? */ virtual bool canSendImmediately(); /** * Raw ISR handler that just calls our polymorphic method */ static void isrRxLevel0(); /** * If a send was in progress finish it and return the buffer to the pool */ void completeSending(); /** * Add SNR data to received messages */ virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) = 0; /** * Subclasses must override, implement and then call into this base class implementation */ virtual void setStandby(); /** * Derive packet time either for a received (using header info) or a transmitted packet */ template uint32_t computePacketTime(T &lora, uint32_t pl, bool received) { if (received) { // First get the actual coding rate and CRC status from the received packet uint8_t rxCR; bool hasCRC; lora.getLoRaRxHeaderInfo(&rxCR, &hasCRC); // Go from raw header value to denominator if (rxCR < 5) { rxCR += 4; } else if (rxCR == 7) { rxCR = 8; } // Received packet configuration must be the same as configured, except for coding rate and CRC DataRate_t dr = getDataRate(); dr.lora.codingRate = rxCR; PacketConfig_t pc = getPacketConfig(); pc.lora.crcEnabled = hasCRC; return lora.calculateTimeOnAir(modemType, dr, pc, pl) / 1000; } return lora.getTimeOnAir(pl) / 1000; } const char *radioLibErr = "RadioLib err="; /** * If the packet is not already in the late rebroadcast window, move it there */ void clampToLateRebroadcastWindow(NodeNum from, PacketId id); /** * If there is a packet pending TX in the queue with a worse hop limit, remove it pending replacement with a better version * @return Whether a pending packet was removed */ bool removePendingTXPacket(NodeNum from, PacketId id, uint32_t hop_limit_lt) override; void checkRxDoneIrqFlag(); void checkTxDoneIrqFlag(); };