From e66d9d0add214e515261c2c7da7a89ac17479905 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sat, 1 Oct 2022 11:59:20 +0200 Subject: [PATCH] Add content to SimRadio, similar to RadioInterface --- src/mesh/SimRadio.cpp | 250 ++++++++++++++++++++++++++++++++++++++++++ src/mesh/SimRadio.h | 87 +++++++++++++++ 2 files changed, 337 insertions(+) diff --git a/src/mesh/SimRadio.cpp b/src/mesh/SimRadio.cpp index e69de29bb..ccfaa3b03 100644 --- a/src/mesh/SimRadio.cpp +++ b/src/mesh/SimRadio.cpp @@ -0,0 +1,250 @@ +#include "SimRadio.h" +#include "MeshService.h" +#include "Router.h" + +SimRadio::SimRadio() +{ + instance = this; +} + +SimRadio *SimRadio::instance; + +ErrorCode SimRadio::send(MeshPacket *p) +{ + printPacket("enqueuing for send", p); + + 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(); + return res; +} + +void SimRadio::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 SimRadio::startTransmitTimer(bool withDelay) +{ + // If we have work to do and the timer wasn't already scheduled, schedule it now + if (!txQueue.empty()) { + uint32_t delayMsec = !withDelay ? 1 : getTxDelayMsec(); + // DEBUG_MSG("xmit timer %d\n", delay); + delay(delayMsec); + onNotify(TRANSMIT_DELAY_COMPLETED); + } else { + DEBUG_MSG("TX QUEUE EMPTY!\n"); + } +} + +void SimRadio::startTransmitTimerSNR(float snr) +{ + // If we have work to do and the timer wasn't already scheduled, schedule it now + if (!txQueue.empty()) { + uint32_t delayMsec = getTxDelayMsecWeighted(snr); + // DEBUG_MSG("xmit timer %d\n", delay); + delay(delayMsec); + onNotify(TRANSMIT_DELAY_COMPLETED); + } +} + +void SimRadio::handleTransmitInterrupt() +{ + // This can be null if we forced the device to enter standby mode. In that case + // ignore the transmit interrupt + if (sendingPacket) + completeSending(); +} + +void SimRadio::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"); + } +} + + +/** Could we send right now (i.e. either not actively receving or transmitting)? */ +bool SimRadio::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(); + + 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; +} + +bool SimRadio::isActivelyReceiving() +{ + return false; // TODO check how this should be simulated +} + +bool SimRadio::isChannelActive() +{ + return false; // TODO ask simulator +} + +/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */ +bool SimRadio::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; +} + + +void SimRadio::onNotify(uint32_t notification) +{ + switch (notification) { + case ISR_TX: + handleTransmitInterrupt(); + DEBUG_MSG("tx complete - starting timer\n"); + startTransmitTimer(); + break; + case ISR_RX: + DEBUG_MSG("rx complete - starting timer\n"); + 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 + } 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); + completeSending(); + } + } + } else { + // DEBUG_MSG("done with txqueue\n"); + } + break; + default: + assert(0); // We expected to receive a valid notification from the ISR + } +} + +/** start an immediate transmit */ +void SimRadio::startSend(MeshPacket * txp) +{ + printPacket("Starting low level send", txp); + size_t numbytes = beginSending(txp); + MeshPacket* p = packetPool.allocCopy(*txp); + perhapsDecode(p); + Compressed c = Compressed_init_default; + c.portnum = p->decoded.portnum; + // DEBUG_MSG("Sending back to simulator with portNum %d\n", p->decoded.portnum); + if (p->decoded.payload.size <= sizeof(c.data.bytes)) { + memcpy(&c.data.bytes, p->decoded.payload.bytes, p->decoded.payload.size); + c.data.size = p->decoded.payload.size; + } else { + DEBUG_MSG("Payload size is larger than compressed message allows! Sending empty payload.\n"); + } + p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), Compressed_fields, &c); + p->decoded.portnum = PortNum_SIMULATOR_APP; + service.sendToPhone(p); // Sending back to simulator +} + + +void SimRadio::startReceive(MeshPacket *p) { + isReceiving = true; + handleReceiveInterrupt(p); +} + + +void SimRadio::handleReceiveInterrupt(MeshPacket *p) +{ + DEBUG_MSG("HANDLE RECEIVE INTERRUPT\n"); + uint32_t xmitMsec; + assert(isReceiving); + isReceiving = false; + + // read the number of actually received bytes + size_t length = getPacketLength(p); + xmitMsec = getPacketTime(length); + // DEBUG_MSG("Payload size %d vs length (includes header) %d\n", p->decoded.payload.size, length); + + MeshPacket *mp = packetPool.allocCopy(*p); // keep a copy in packtPool + mp->which_payload_variant = MeshPacket_decoded_tag; // Mark that the payload is already decoded + + printPacket("Lora RX", mp); + + airTime->logAirtime(RX_LOG, xmitMsec); + + deliverToReceiver(mp); +} + +size_t SimRadio::getPacketLength(MeshPacket *mp) { + auto &p = mp->decoded; + return (size_t)p.payload.size+sizeof(PacketHeader); +} + +int16_t SimRadio::readData(uint8_t* data, size_t len) { + int16_t state = RADIOLIB_ERR_NONE; + + if(state == RADIOLIB_ERR_NONE) { + // add null terminator + data[len] = 0; + } + + return state; +} \ No newline at end of file diff --git a/src/mesh/SimRadio.h b/src/mesh/SimRadio.h index e69de29bb..dad419c62 100644 --- a/src/mesh/SimRadio.h +++ b/src/mesh/SimRadio.h @@ -0,0 +1,87 @@ +#pragma once + +#include "RadioInterface.h" +#include "MeshPacketQueue.h" +#include "wifi/WiFiServerAPI.h" + +#define RADIOLIB_EXCLUDE_HTTP +#include + +class SimRadio : public RadioInterface +{ + enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED }; + + /** + * Debugging counts + */ + uint32_t rxBad = 0, rxGood = 0, txGood = 0; + + MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE); + + public: + SimRadio(); + + /** MeshService needs this to find our active instance + */ + static SimRadio *instance; + + + virtual ErrorCode send(MeshPacket *p) override; + + /** can we detect a LoRa preamble on the current channel? */ + virtual bool isChannelActive(); + + /** 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(); + + /** 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; + + /** + * Start waiting to receive a message + * + * External functions can call this method to wake the device from sleep. + */ + virtual void startReceive(MeshPacket *p); + + protected: + /// are _trying_ to receive a packet currently (note - we might just be waiting for one) + bool isReceiving = false; + + private: + + void setTransmitDelay(); + + /** random timer with certain min. and max. settings */ + void startTransmitTimer(bool withDelay = true); + + /** timer scaled to SNR of to be flooded packet */ + void startTransmitTimerSNR(float snr); + + void handleTransmitInterrupt(); + void handleReceiveInterrupt(MeshPacket *p); + + void onNotify(uint32_t notification); + + // start an immediate transmit + virtual void startSend(MeshPacket *txp); + + // derive packet length + size_t getPacketLength(MeshPacket *p); + + int16_t readData(uint8_t* str, size_t len); + + protected: + /** Could we send right now (i.e. either not actively receiving or transmitting)? */ + virtual bool canSendImmediately(); + + + /** + * If a send was in progress finish it and return the buffer to the pool */ + void completeSending(); + +}; + +extern SimRadio *simRadio; \ No newline at end of file