Skip to content

Commit

Permalink
SimRadio: clean-up and emulate collisions (meshtastic#5487)
Browse files Browse the repository at this point in the history
* Clean up SimRadio and don't let it use PKC

* Add collision emulation for SimRadio

* Add stats from SimRadio to LocalStats

* Make emulating collisions optional
  • Loading branch information
GUVWAF authored Dec 3, 2024
1 parent d00e0f6 commit 57ea6a2
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 57 deletions.
4 changes: 2 additions & 2 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
#include "STM32WLE5JCInterface.h"
#endif

#if !HAS_RADIO && defined(ARCH_PORTDUINO)
#if defined(ARCH_PORTDUINO)
#include "platform/portduino/SimRadio.h"
#endif

Expand Down Expand Up @@ -896,7 +896,7 @@ void setup()
}
#endif

#if !HAS_RADIO && defined(ARCH_PORTDUINO)
#if defined(ARCH_PORTDUINO)
if (!rIf) {
rIf = new SimRadio;
if (!rIf->init()) {
Expand Down
25 changes: 4 additions & 21 deletions src/mesh/MeshService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,27 +166,10 @@ NodeNum MeshService::getNodenumFromRequestId(uint32_t request_id)
*/
void MeshService::handleToRadio(meshtastic_MeshPacket &p)
{
#if defined(ARCH_PORTDUINO) && !HAS_RADIO
// Simulates device received a packet via the LoRa chip
if (p.decoded.portnum == meshtastic_PortNum_SIMULATOR_APP) {
// Simulator packet (=Compressed packet) is encapsulated in a MeshPacket, so need to unwrap first
meshtastic_Compressed scratch;
meshtastic_Compressed *decoded = NULL;
if (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
memset(&scratch, 0, sizeof(scratch));
p.decoded.payload.size =
pb_decode_from_bytes(p.decoded.payload.bytes, p.decoded.payload.size, &meshtastic_Compressed_msg, &scratch);
if (p.decoded.payload.size) {
decoded = &scratch;
// Extract the original payload and replace
memcpy(&p.decoded.payload, &decoded->data, sizeof(decoded->data));
// Switch the port from PortNum_SIMULATOR_APP back to the original PortNum
p.decoded.portnum = decoded->portnum;
} else
LOG_ERROR("Error decoding proto for simulator message!");
}
// Let SimRadio receive as if it did via its LoRa chip
SimRadio::instance->startReceive(&p);
#if defined(ARCH_PORTDUINO)
if (SimRadio::instance && p.decoded.portnum == meshtastic_PortNum_SIMULATOR_APP) {
// Simulates device received a packet via the LoRa chip
SimRadio::instance->unpackAndReceive(p);
return;
}
#endif
Expand Down
4 changes: 2 additions & 2 deletions src/mesh/MeshService.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include "MeshTypes.h"
#include "Observer.h"
#include "PointerQueue.h"
#if defined(ARCH_PORTDUINO) && !HAS_RADIO
#if defined(ARCH_PORTDUINO)
#include "../platform/portduino/SimRadio.h"
#endif
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
Expand Down Expand Up @@ -165,4 +165,4 @@ class MeshService
friend class RoutingModule;
};

extern MeshService *service;
extern MeshService *service;
15 changes: 8 additions & 7 deletions src/mesh/PhoneAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -613,13 +613,14 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
{
printPacket("PACKET FROM PHONE", &p);

// For use with the simulator, we should not ignore duplicate packets
#if !(defined(ARCH_PORTDUINO) && !HAS_RADIO)
if (p.id > 0 && wasSeenRecently(p.id)) {
LOG_DEBUG("Ignore packet from phone, already seen recently");
return false;
}
#if defined(ARCH_PORTDUINO)
// For use with the simulator, we should not ignore duplicate packets from the phone
if (SimRadio::instance == nullptr)
#endif
if (p.id > 0 && wasSeenRecently(p.id)) {
LOG_DEBUG("Ignore packet from phone, already seen recently");
return false;
}

if (p.decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP && lastPortNumToRadio[p.decoded.portnum] &&
Throttle::isWithinTimespanMs(lastPortNumToRadio[p.decoded.portnum], THIRTY_SECONDS_MS)) {
Expand Down Expand Up @@ -656,4 +657,4 @@ int PhoneAPI::onNotify(uint32_t newValue)
}

return timeout ? -1 : 0; // If we timed out, MeshService should stop iterating through observers as we just removed one
}
}
3 changes: 3 additions & 0 deletions src/mesh/Router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "NodeDB.h"
#include "RTC.h"
#include "configuration.h"
#include "detect/LoRaRadioType.h"
#include "main.h"
#include "mesh-pb-constants.h"
#include "meshUtils.h"
Expand Down Expand Up @@ -491,6 +492,8 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
// is not in the local nodedb
// First, only PKC encrypt packets we are originating
if (isFromUs(p) &&
// Don't use PKC with simulator
radioType != SIM_RADIO &&
// Don't use PKC with Ham mode
!owner.is_licensed &&
// Don't use PKC if it's not explicitly requested and a non-primary channel is requested
Expand Down
8 changes: 8 additions & 0 deletions src/modules/Telemetry/DeviceTelemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry()
telemetry.variant.local_stats.num_packets_rx_bad = RadioLibInterface::instance->rxBad;
telemetry.variant.local_stats.num_tx_relay = RadioLibInterface::instance->txRelay;
}
#ifdef ARCH_PORTDUINO
if (SimRadio::instance) {
telemetry.variant.local_stats.num_packets_tx = SimRadio::instance->txGood;
telemetry.variant.local_stats.num_packets_rx = SimRadio::instance->rxGood + SimRadio::instance->rxBad;
telemetry.variant.local_stats.num_packets_rx_bad = SimRadio::instance->rxBad;
telemetry.variant.local_stats.num_tx_relay = SimRadio::instance->txRelay;
}
#endif
if (router) {
telemetry.variant.local_stats.num_rx_dupe = router->rxDupe;
telemetry.variant.local_stats.num_tx_relay_canceled = router->txRelayCanceled;
Expand Down
97 changes: 79 additions & 18 deletions src/platform/portduino/SimRadio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ void SimRadio::handleTransmitInterrupt()
// ignore the transmit interrupt
if (sendingPacket)
completeSending();

isReceiving = true;
if (receivingPacket) // This happens when we don't consider something a collision if we weren't sending long enough
handleReceiveInterrupt();
}

void SimRadio::completeSending()
Expand All @@ -84,6 +88,8 @@ void SimRadio::completeSending()

if (p) {
txGood++;
if (!isFromUs(p))
txRelay++;
printPacket("Completed sending", p);

// We are done sending that packet, release it
Expand Down Expand Up @@ -113,12 +119,12 @@ bool SimRadio::canSendImmediately()

bool SimRadio::isActivelyReceiving()
{
return false; // TODO check how this should be simulated
return receivingPacket != nullptr;
}

bool SimRadio::isChannelActive()
{
return false; // TODO ask simulator
return receivingPacket != nullptr;
}

/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
Expand All @@ -142,10 +148,16 @@ void SimRadio::onNotify(uint32_t notification)
startTransmitTimer();
break;
case ISR_RX:
handleReceiveInterrupt();
// LOG_DEBUG("rx complete - starting timer");
startTransmitTimer();
break;
case TRANSMIT_DELAY_COMPLETED:
if (receivingPacket) { // This happens when we had a timer pending and we started receiving
handleReceiveInterrupt();
startTransmitTimer();
break;
}
LOG_DEBUG("delay done");

// If we are not currently in receive mode, then restart the random delay (this can happen if the main thread
Expand Down Expand Up @@ -183,6 +195,7 @@ void SimRadio::onNotify(uint32_t notification)
void SimRadio::startSend(meshtastic_MeshPacket *txp)
{
printPacket("Start low level send", txp);
isReceiving = false;
size_t numbytes = beginSending(txp);
meshtastic_MeshPacket *p = packetPool.allocCopy(*txp);
perhapsDecode(p);
Expand All @@ -201,15 +214,64 @@ void SimRadio::startSend(meshtastic_MeshPacket *txp)

service->sendQueueStatusToPhone(router->getQueueStatus(), 0, p->id);
service->sendToPhone(p); // Sending back to simulator
service->loop(); // Process the send immediately
}

// Simulates device received a packet via the LoRa chip
void SimRadio::unpackAndReceive(meshtastic_MeshPacket &p)
{
// Simulator packet (=Compressed packet) is encapsulated in a MeshPacket, so need to unwrap first
meshtastic_Compressed scratch;
meshtastic_Compressed *decoded = NULL;
if (p.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
memset(&scratch, 0, sizeof(scratch));
p.decoded.payload.size =
pb_decode_from_bytes(p.decoded.payload.bytes, p.decoded.payload.size, &meshtastic_Compressed_msg, &scratch);
if (p.decoded.payload.size) {
decoded = &scratch;
// Extract the original payload and replace
memcpy(&p.decoded.payload, &decoded->data, sizeof(decoded->data));
// Switch the port from PortNum_SIMULATOR_APP back to the original PortNum
p.decoded.portnum = decoded->portnum;
} else
LOG_ERROR("Error decoding proto for simulator message!");
}
// Let SimRadio receive as if it did via its LoRa chip
startReceive(&p);
}

void SimRadio::startReceive(meshtastic_MeshPacket *p)
{
#ifdef USERPREFS_SIMRADIO_EMULATE_COLLISIONS
if (isActivelyReceiving()) {
LOG_WARN("Collision detected, dropping current and previous packet!");
rxBad++;
airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket));
packetPool.release(receivingPacket);
receivingPacket = nullptr;
return;
} else if (sendingPacket) {
uint32_t airtimeLeft = tillRun(millis());
if (airtimeLeft <= 0) {
LOG_WARN("Transmitting packet was already done");
handleTransmitInterrupt(); // Finish sending first
} else if ((interval - airtimeLeft) > preambleTimeMsec) {
// Only if transmitting for longer than preamble there is a collision
// (channel should actually be detected as active otherwise)
LOG_WARN("Collision detected during transmission!");
return;
}
}
isReceiving = true;
receivingPacket = packetPool.allocCopy(*p);
uint32_t airtimeMsec = getPacketTime(p);
notifyLater(airtimeMsec, ISR_RX, false); // Model the time it is busy receiving
#else
isReceiving = true;
size_t length = getPacketLength(p);
uint32_t xmitMsec = getPacketTime(length);
delay(xmitMsec); // Model the time it is busy receiving
handleReceiveInterrupt(p);
receivingPacket = packetPool.allocCopy(*p);
handleReceiveInterrupt(); // Simulate receiving the packet immediately
startTransmitTimer();
#endif
}

meshtastic_QueueStatus SimRadio::getQueueStatus()
Expand All @@ -223,28 +285,27 @@ meshtastic_QueueStatus SimRadio::getQueueStatus()
return qs;
}

void SimRadio::handleReceiveInterrupt(meshtastic_MeshPacket *p)
void SimRadio::handleReceiveInterrupt()
{
LOG_DEBUG("HANDLE RECEIVE INTERRUPT");
uint32_t xmitMsec;
if (receivingPacket == nullptr) {
return;
}

if (!isReceiving) {
LOG_DEBUG("*** WAS_ASSERT *** handleReceiveInterrupt called when not in receive mode");
return;
}

isReceiving = false;

// read the number of actually received bytes
size_t length = getPacketLength(p);
xmitMsec = getPacketTime(length);
// LOG_DEBUG("Payload size %d vs length (includes header) %d", p->decoded.payload.size, length);
LOG_DEBUG("HANDLE RECEIVE INTERRUPT");
rxGood++;

meshtastic_MeshPacket *mp = packetPool.allocCopy(*p); // keep a copy in packetPool
meshtastic_MeshPacket *mp = packetPool.allocCopy(*receivingPacket); // keep a copy in packetPool
packetPool.release(receivingPacket); // release the original
receivingPacket = nullptr;

printPacket("Lora RX", mp);

airTime->logAirtime(RX_LOG, xmitMsec);
airTime->logAirtime(RX_LOG, getPacketTime(mp));

deliverToReceiver(mp);
}
Expand All @@ -265,4 +326,4 @@ int16_t SimRadio::readData(uint8_t *data, size_t len)
}

return state;
}
}
19 changes: 12 additions & 7 deletions src/platform/portduino/SimRadio.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
{
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:
Expand Down Expand Up @@ -47,9 +42,17 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr

meshtastic_QueueStatus getQueueStatus() override;

// Convert Compressed_msg to normal msg and receive it
void unpackAndReceive(meshtastic_MeshPacket &p);

/**
* Debugging counts
*/
uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0;

protected:
/// are _trying_ to receive a packet currently (note - we might just be waiting for one)
bool isReceiving = false;
bool isReceiving = true;

private:
void setTransmitDelay();
Expand All @@ -61,7 +64,7 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
void startTransmitTimerSNR(float snr);

void handleTransmitInterrupt();
void handleReceiveInterrupt(meshtastic_MeshPacket *p);
void handleReceiveInterrupt();

void onNotify(uint32_t notification);

Expand All @@ -73,6 +76,8 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr

int16_t readData(uint8_t *str, size_t len);

meshtastic_MeshPacket *receivingPacket = nullptr; // The packet we are currently receiving

protected:
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
virtual bool canSendImmediately();
Expand Down
3 changes: 3 additions & 0 deletions src/platform/portduino/architecture.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
#ifndef HAS_WIFI
#define HAS_WIFI 1
#endif
#ifndef HAS_RADIO
#define HAS_RADIO 1
#endif
#ifndef HAS_RTC
#define HAS_RTC 1
#endif
Expand Down

0 comments on commit 57ea6a2

Please sign in to comment.