Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions src/Ducks/DuckLink.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,13 @@ class DuckLink : public Duck<WifiCapability, RadioType> {
// min = Teensy power-off threshold, max = power-on/recovery
// threshold, both in volts. Store as float under the same
// keys seeded in Duck::setupWithDefaults.
this->eeprom_preferences.putFloat("teensy_off", battery_min);
this->eeprom_preferences.putFloat("teensy_on", battery_max);
const size_t offBytes = this->eeprom_preferences.putFloat("teensy_off", battery_min);
const size_t onBytes = this->eeprom_preferences.putFloat("teensy_on", battery_max);
if (offBytes != sizeof(float) || onBytes != sizeof(float)) {
logerr_ln("Failed to persist battery sleep thresholds to Preferences");
err = DUCK_ERR_EEPROM_WRITE;
break;
}
}

err = this->broadcastPacket(rxPacket);
Expand Down Expand Up @@ -168,6 +173,10 @@ class DuckLink : public Duck<WifiCapability, RadioType> {
switch(rxPacket.topic) {
case reservedTopic::rreq: {
RouteJSON rreqDoc = RouteJSON(rxPacket.data);
if (!rreqDoc.isValid()) {
logerr_ln("handleReceivedPacket: dropping malformed RREQ");
break;
}
if(!relay) {
loginfo_ln("handleReceivedPacket: Sending RREP");
std::optional<Duid> last = rreqDoc.getlastInPath();
Expand All @@ -185,12 +194,16 @@ class DuckLink : public Duck<WifiCapability, RadioType> {
case reservedTopic::rrep: {
//we still need to recieve rreps in case of ttl expiry
RouteJSON rrepDoc = RouteJSON(rxPacket.data);
if (!rrepDoc.isValid()) {
logerr_ln("handleReceivedPacket: dropping malformed RREP");
break;
}
std::string sourceDuid(rxPacket.sduid.begin(), rxPacket.sduid.end());
loginfo_ln("Received Route Response from DUID: %s", sourceDuid.c_str());
//destination = sender of the rrep -> the last hop to current duck
std::optional<Duid> last = rrepDoc.getlastInPath();
Duid lastInPath = last.value();
Duid lastInPath = last.has_value() ? last.value() : rxPacket.sduid;

this->router.insertIntoRoutingTable(rrepDoc.getOrigin(), lastInPath, this->getSignalScore());
}
break;
Expand Down
33 changes: 23 additions & 10 deletions src/Ducks/MamaDuck.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ private :
this->router.getFilter().bloom_add(rxPacket.muid.data(), MUID_LENGTH);
}

void ifBroadcast(CdpPacket rxPacket) {
int err;
void ifBroadcast(CdpPacket rxPacket) {
int err = DUCK_ERR_NONE;
switch(rxPacket.topic) {
case reservedTopic::rreq: {
if(rxPacket.hopCount <= 0){
Expand Down Expand Up @@ -100,14 +100,19 @@ private :
break;
}

if (err == DUCK_ERR_NONE){
// min = Teensy power-off threshold, max = power-on/recovery
// threshold, both in volts. Store as float under the same keys
// seeded in Duck::setupWithDefaults so the firmware reads one
// source of truth.
this->eeprom_preferences.putFloat("teensy_off", battery_min);
this->eeprom_preferences.putFloat("teensy_on", battery_max);
}
if (err == DUCK_ERR_NONE){
// min = Teensy power-off threshold, max = power-on/recovery
// threshold, both in volts. Store as float under the same keys
// seeded in Duck::setupWithDefaults so the firmware reads one
// source of truth.
const size_t offBytes = this->eeprom_preferences.putFloat("teensy_off", battery_min);
const size_t onBytes = this->eeprom_preferences.putFloat("teensy_on", battery_max);
if (offBytes != sizeof(float) || onBytes != sizeof(float)) {
logerr_ln("Failed to persist battery sleep thresholds to Preferences");
err = DUCK_ERR_EEPROM_WRITE;
break;
}
}

err = this->broadcastPacket(rxPacket);
if (err != DUCK_ERR_NONE) {
Expand Down Expand Up @@ -190,6 +195,10 @@ private :
switch(rxPacket.topic) {
case reservedTopic::rreq: {
RouteJSON rreqDoc = RouteJSON(rxPacket.data);
if (!rreqDoc.isValid()) {
logerr_ln("handleReceivedPacket: dropping malformed RREQ");
break;
}
//route requests are just forwarded so we can use the sduid as the origin
std::optional<Duid> last = rreqDoc.getlastInPath();
Duid lastInPath = last.has_value() ? last.value() : rxPacket.sduid;
Expand All @@ -212,6 +221,10 @@ private :
case reservedTopic::rrep: {
//we still need to recieve rreps in case of ttl expiry
RouteJSON rrepDoc = RouteJSON(rxPacket.data);
if (!rrepDoc.isValid()) {
logerr_ln("handleReceivedPacket: dropping malformed RREP");
break;
}
std::optional<Duid> last = rrepDoc.getlastInPath();
Duid lastInPath = last.has_value() ? last.value() : rxPacket.sduid;
std::string sourceDuid(rxPacket.sduid.begin(), rxPacket.sduid.end());
Expand Down
12 changes: 10 additions & 2 deletions src/Ducks/PapaDuck.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ void ifNotBroadcast(CdpPacket rxPacket, bool relay = false) {
switch(rxPacket.topic) {
case reservedTopic::rreq: {
RouteJSON rreqDoc = RouteJSON(rxPacket.data);
if (!rreqDoc.isValid()) {
logerr_ln("handleReceivedPacket: dropping malformed RREQ");
break;
}
//route requests are just forwarded so we can use the sduid as the origin
std::optional<Duid> last = rreqDoc.getlastInPath();
Duid lastInPath = last.has_value() ? last.value() : rxPacket.sduid;
Expand All @@ -130,10 +134,14 @@ void ifNotBroadcast(CdpPacket rxPacket, bool relay = false) {
case reservedTopic::rrep: {
//we still need to recieve rreps in case of ttl expiry
RouteJSON rrepDoc = RouteJSON(rxPacket.data);
if (!rrepDoc.isValid()) {
logerr_ln("handleReceivedPacket: dropping malformed RREP");
break;
}
std::optional<Duid> last = rrepDoc.getlastInPath();
Duid lastInPath = last.has_value() ? last.value() : rxPacket.sduid;
std::string sourceDuid(rxPacket.sduid.begin(), rxPacket.sduid.end());
loginfo_ln("Received Route Response from DUID: %s", sourceDuid.c_str());
std::string sourceDuid(rxPacket.sduid.begin(), rxPacket.sduid.end());
loginfo_ln("Received Route Response from DUID: %s", sourceDuid.c_str());

std::optional<Duid> nextHop = this->router.getBestNextHop(rrepDoc.getDestination());
if((rrepDoc.getDestination() != this->duid) && (nextHop.has_value()) && (nextHop.value() != rxPacket.sduid)){ //and not papaduck_duid?
Expand Down
69 changes: 44 additions & 25 deletions src/radio/DuckLoRa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ CDPCFG_LORA_CLASS lora = new Module(CDPCFG_PIN_LORA_CS, CDPCFG_PIN_LORA_DIO0,
CDPCFG_PIN_LORA_RST, CDPCFG_PIN_LORA_DIO1);
#endif

volatile uint16_t DuckLoRa::interruptFlags = 0;
volatile bool DuckLoRa::interruptPending = false;
volatile bool DuckLoRa::receivedFlag = false;

const LoRaConfigParams DuckLoRa::defaultRadioParams = {
Expand Down Expand Up @@ -159,10 +159,10 @@ std::optional<std::vector<uint8_t>> DuckLoRa::readReceivedData() { //return a st

packet_length = lora.getPacketLength();

if (packet_length < MIN_PACKET_LENGTH) {
if (packet_length < MIN_PACKET_LENGTH || packet_length > PACKET_LENGTH) {
logerr_ln("ERROR handlePacket rx data size invalid: %d", packet_length);

rxState = goToReceiveMode(true); // go back to receive mode and reset the receive flag
goToReceiveMode(true); // go back to receive mode and reset the receive flag
return std::nullopt;
}

loginfo_ln("readReceivedData() - packet length returns: %d", packet_length);
Expand Down Expand Up @@ -314,79 +314,98 @@ int DuckLoRa::sleep()
}

void DuckLoRa::serviceInterruptFlags() {
if (DuckLoRa::interruptFlags != 0) {
if (!DuckLoRa::interruptPending) {
return;
}

// Clear the pending latch BEFORE reading the flags off the radio. If DIO1
// fires again while we are servicing, the ISR re-sets interruptPending and
// we pick the new event up on the next loop instead of silently dropping it
// (the read-then-clear race that could leave the node parked in standby).
DuckLoRa::interruptPending = false;

// Safe to touch SPI here: this runs in loop context, not the ISR.
uint16_t flags = lora.getIrqFlags();
if (flags == 0) {
return;
}

#ifdef CDPCFG_RADIO_SX1262
// SX1262 flags
if (DuckLoRa::interruptFlags & RADIOLIB_SX126X_CMD_CLEAR_IRQ_STATUS) {
if (flags & RADIOLIB_SX126X_CMD_CLEAR_IRQ_STATUS) {
logdbg_ln("SX1262 Interrupt flag was set: clear IRQ status");
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX126X_CMD_CLEAR_DEVICE_ERRORS) {
if (flags & RADIOLIB_SX126X_CMD_CLEAR_DEVICE_ERRORS) {
logdbg_ln("SX1262 Interrupt flag was set: clear device errors");
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX126X_IRQ_CRC_ERR ) {
if (flags & RADIOLIB_SX126X_IRQ_CRC_ERR ) {
logdbg_ln("SX1262 Interrupt flag was set: payload CRC error");
// goToReceiveMode() re-arms the radio (startReceive clears the IRQ
// status). Do NOT call lora.standby() afterwards: that immediately
// knocks the radio back out of receive and the node goes deaf.
goToReceiveMode(false);
lora.standby();
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX126X_IRQ_HEADER_ERR ) {
if (flags & RADIOLIB_SX126X_IRQ_HEADER_ERR ) {
logdbg_ln("SX1262 Interrupt flag was set: header CRC error");
goToReceiveMode(false);
lora.standby();
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX126X_IRQ_RX_DONE ) {
if (flags & RADIOLIB_SX126X_IRQ_RX_DONE ) {
logdbg_ln("SX1262 Interrupt flag was set: packet reception complete");
setReceiveFlag(true);
lora.standby(); // we are done receiving, go to standby. We can't sleep because read buffer is not empty
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX126X_IRQ_TX_DONE ) {
if (flags & RADIOLIB_SX126X_IRQ_TX_DONE ) {
logdbg_ln("SX1262 Interrupt flag was set: payload transmission complete");
lora.finishTransmit();
goToReceiveMode(false);
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX126X_IRQ_TIMEOUT ) {
if (flags & RADIOLIB_SX126X_IRQ_TIMEOUT ) {
logdbg_ln("SX1262 Interrupt flag was set: timeout");
goToReceiveMode(false);
}
#else
// SX127X flags
if (DuckLoRa::interruptFlags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_RX_TIMEOUT) {
if (flags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_RX_TIMEOUT) {
goToReceiveMode(true); // go back to receive mode and reset the receive flag
logdbg_ln("SX127x Interrupt flag was set: timeout");
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_RX_DONE) {
if (flags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_RX_DONE) {
logdbg_ln("SX127x Interrupt flag was set: packet reception complete");
setReceiveFlag(true); // set the receive flag and we stay in receive mode
lora.standby(); // we are done receiving, go to standby. We can't sleep because read buffer is not empty
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_PAYLOAD_CRC_ERROR) {
if (flags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_PAYLOAD_CRC_ERROR) {
goToReceiveMode(true); // go back to receive mode and reset the receive flag
logdbg_ln("SX127x Interrupt flag was set: payload CRC error");
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_VALID_HEADER) {
if (flags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_VALID_HEADER) {
logdbg_ln("SX127x Interrupt flag was set: valid header received");
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_TX_DONE) {
if (flags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_TX_DONE) {
logdbg_ln("SX127x Interrupt flag was set: payload transmission complete");
goToReceiveMode(false); // go back to receive mode and reset the receive flag
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DONE) {
if (flags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DONE) {
logdbg_ln("SX127x Interrupt flag was set: CAD complete");
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_FHSS_CHANGE_CHANNEL) {
if (flags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_FHSS_CHANGE_CHANNEL) {
logdbg_ln("SX127x Interrupt flag was set: FHSS change channel");
}
if (DuckLoRa::interruptFlags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DETECTED) {
if (flags & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DETECTED) {
logdbg_ln("SX127x Interrupt flag was set: valid LoRa signal detected during CAD operation");
}
#endif
DuckLoRa::interruptFlags = 0;
}
}

// IMPORTANT: this function MUST be 'void' type and MUST NOT have any arguments!
void DuckLoRa::onInterrupt(void) {
interruptFlags = lora.getIrqFlags();
// ISR context: do NOT touch the SPI bus here. lora.getIrqFlags() issues a
// full SPI transaction (including a BUSY-line wait and the arduino-esp32 SPI
// HAL lock) which can deadlock or corrupt the bus if DIO1 fires while the
// main loop is mid-transfer (startTransmit / readData / startReceive). The
// documented RadioLib pattern is for the ISR to only latch a flag; the flags
// are then read from loop context in serviceInterruptFlags().
interruptPending = true;
}

int DuckLoRa::startTransmitData(uint8_t* data, int length) {
Expand Down
4 changes: 3 additions & 1 deletion src/radio/DuckLoRa.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ class DuckLoRa {
float getSNR();

private:
static volatile uint16_t interruptFlags;
// Set to true by onInterrupt() (ISR context) only. The actual IRQ flags
// are read from the radio in loop context by serviceInterruptFlags().
static volatile bool interruptPending;
static volatile bool receivedFlag;
volatile bool isSetup = false;
unsigned long lastReceiveTime = 0L;
Expand Down
Loading
Loading