diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index b19f96bb5..79cd91af5 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,7 +8,7 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - trufflehog@3.82.4 + - trufflehog@3.82.5 - yamllint@1.35.1 - bandit@1.7.10 - checkov@3.2.255 diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index 72f4d2aaf..fa0d031e9 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -9,6 +9,7 @@ Lora: # IRQ: 16 # Busy: 20 # Reset: 18 +# SX126X_ANT_SW: 6 # Module: sx1262 # Waveshare SX1302 LISTEN ONLY AT THIS TIME! # CS: 7 @@ -176,4 +177,4 @@ Webserver: General: MaxNodes: 200 - MaxMessageQueue: 100 + MaxMessageQueue: 100 \ No newline at end of file diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index c3ebf3c6e..1be61759a 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -8,6 +8,7 @@ #include "MeshTypes.h" #include "NodeStatus.h" +#include "configuration.h" #include "mesh-pb-constants.h" #include "mesh/generated/meshtastic/mesh.pb.h" // For CriticalErrorCode diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 103572990..ecc5effe9 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -8,6 +8,7 @@ #include "FSCommon.h" #include "MeshService.h" #include "NodeDB.h" +#include "PacketHistory.h" #include "PhoneAPI.h" #include "PowerFSM.h" #include "RadioInterface.h" @@ -31,6 +32,7 @@ PhoneAPI::PhoneAPI() { lastContactMsec = millis(); + std::fill(std::begin(recentToRadioPacketIds), std::end(recentToRadioPacketIds), 0); } PhoneAPI::~PhoneAPI() @@ -109,8 +111,6 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // As long as the phone keeps talking to us, don't let the radio go to sleep lastContactMsec = millis(); - // return (lastContactMsec != 0) && - memset(&toRadioScratch, 0, sizeof(toRadioScratch)); if (pb_decode_from_bytes(buf, bufLength, &meshtastic_ToRadio_msg, &toRadioScratch)) { switch (toRadioScratch.which_payload_variant) { @@ -572,12 +572,35 @@ void PhoneAPI::sendNotification(meshtastic_LogRecord_Level level, uint32_t reply service->sendClientNotification(cn); } +bool PhoneAPI::wasSeenRecently(uint32_t id) +{ + for (int i = 0; i < 20; i++) { + if (recentToRadioPacketIds[i] == id) { + return true; + } + if (recentToRadioPacketIds[i] == 0) { + recentToRadioPacketIds[i] = id; + return false; + } + } + // If the array is full, shift all elements to the left and add the new id at the end + memmove(recentToRadioPacketIds, recentToRadioPacketIds + 1, (19) * sizeof(uint32_t)); + recentToRadioPacketIds[19] = id; + return false; +} + /** * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool */ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p) { printPacket("PACKET FROM PHONE", &p); + + if (p.id > 0 && wasSeenRecently(p.id)) { + LOG_DEBUG("Ignoring packet from phone, already seen recently\n"); + return false; + } + if (p.decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP && lastPortNumToRadio[p.decoded.portnum] && Throttle::isWithinTimespanMs(lastPortNumToRadio[p.decoded.portnum], THIRTY_SECONDS_MS)) { LOG_WARN("Rate limiting portnum %d\n", p.decoded.portnum); @@ -586,7 +609,8 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p) } else if (p.decoded.portnum == meshtastic_PortNum_POSITION_APP && lastPortNumToRadio[p.decoded.portnum] && Throttle::isWithinTimespanMs(lastPortNumToRadio[p.decoded.portnum], FIVE_SECONDS_MS)) { LOG_WARN("Rate limiting portnum %d\n", p.decoded.portnum); - sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Position can only be sent once every 5 seconds"); + // FIXME: Figure out why this continues to happen + // sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Position can only be sent once every 5 seconds"); return false; } lastPortNumToRadio[p.decoded.portnum] = millis(); diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 17fd98beb..2b145176a 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -52,6 +52,7 @@ class PhoneAPI // Hashmap of timestamps for last time we received a packet on the API per portnum std::unordered_map lastPortNumToRadio; + uint32_t recentToRadioPacketIds[20]; // Last 20 ToRadio MeshPacket IDs we have seen /** * Each packet sent to the phone has an incrementing count @@ -159,6 +160,8 @@ class PhoneAPI void releaseClientNotification(); + bool wasSeenRecently(uint32_t packetId); + /** * Handle a packet that the phone wants us to send. We can write to it but can not keep a reference to it * @return true true if a packet was queued for sending diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index ad1ceeeeb..0ca5ef984 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -52,6 +52,10 @@ template bool SX126xInterface::init() float tcxoVoltage = 0; if (settingsMap[dio3_tcxo_voltage]) tcxoVoltage = 1.8; + if (settingsMap[sx126x_ant_sw] != RADIOLIB_NC) { + digitalWrite(settingsMap[sx126x_ant_sw], HIGH); + pinMode(settingsMap[sx126x_ant_sw], OUTPUT); + } // FIXME: correct logic to default to not using TCXO if no voltage is specified for SX126X_DIO3_TCXO_VOLTAGE #elif !defined(SX126X_DIO3_TCXO_VOLTAGE) float tcxoVoltage = diff --git a/src/modules/RemoteHardwareModule.cpp b/src/modules/RemoteHardwareModule.cpp index f6b8b2e90..43612e450 100644 --- a/src/modules/RemoteHardwareModule.cpp +++ b/src/modules/RemoteHardwareModule.cpp @@ -15,26 +15,44 @@ // a max of one change per 30 seconds #define WATCH_INTERVAL_MSEC (30 * 1000) +// Tests for access to read from or write to a specified GPIO pin +static bool pinAccessAllowed(uint64_t mask, uint8_t pin) +{ + // If undefined pin access is allowed, don't check the pin and just return true + if (moduleConfig.remote_hardware.allow_undefined_pin_access) { + return true; + } + + // Test to see if the pin is in the list of allowed pins and return true if found + if (mask & (1ULL << pin)) { + return true; + } + + return false; +} + /// Set pin modes for every set bit in a mask -static void pinModes(uint64_t mask, uint8_t mode) +static void pinModes(uint64_t mask, uint8_t mode, uint64_t maskAvailable) { for (uint64_t i = 0; i < NUM_GPIOS; i++) { if (mask & (1ULL << i)) { - pinMode(i, mode); + if (pinAccessAllowed(maskAvailable, i)) { + pinMode(i, mode); + } } } } /// Read all the pins mentioned in a mask -static uint64_t digitalReads(uint64_t mask) +static uint64_t digitalReads(uint64_t mask, uint64_t maskAvailable) { uint64_t res = 0; - pinModes(mask, INPUT_PULLUP); + pinModes(mask, INPUT_PULLUP, maskAvailable); for (uint64_t i = 0; i < NUM_GPIOS; i++) { uint64_t m = 1ULL << i; - if (mask & m) { + if (mask & m && pinAccessAllowed(maskAvailable, i)) { if (digitalRead(i)) { res |= m; } @@ -50,6 +68,11 @@ RemoteHardwareModule::RemoteHardwareModule() { // restrict to the gpio channel for rx boundChannel = Channels::gpioChannel; + + // Pull available pin allowlist from config and build a bitmask out of it for fast comparisons later + for (uint8_t i = 0; i < 4; i++) { + availablePins += 1ULL << moduleConfig.remote_hardware.available_pins[i].gpio_pin; + } } bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &req, meshtastic_HardwareMessage *pptr) @@ -63,10 +86,10 @@ bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &r // Print notification to LCD screen screen->print("Write GPIOs\n"); - pinModes(p.gpio_mask, OUTPUT); + pinModes(p.gpio_mask, OUTPUT, availablePins); for (uint8_t i = 0; i < NUM_GPIOS; i++) { uint64_t mask = 1ULL << i; - if (p.gpio_mask & mask) { + if (p.gpio_mask & mask && pinAccessAllowed(availablePins, i)) { digitalWrite(i, (p.gpio_value & mask) ? 1 : 0); } } @@ -79,7 +102,7 @@ bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &r if (screen) screen->print("Read GPIOs\n"); - uint64_t res = digitalReads(p.gpio_mask); + uint64_t res = digitalReads(p.gpio_mask, availablePins); // Send the reply meshtastic_HardwareMessage r = meshtastic_HardwareMessage_init_default; @@ -121,7 +144,7 @@ int32_t RemoteHardwareModule::runOnce() if (moduleConfig.remote_hardware.enabled && watchGpios) { if (!Throttle::isWithinTimespanMs(lastWatchMsec, WATCH_INTERVAL_MSEC)) { - uint64_t curVal = digitalReads(watchGpios); + uint64_t curVal = digitalReads(watchGpios, availablePins); lastWatchMsec = millis(); if (curVal != previousWatch) { diff --git a/src/modules/RemoteHardwareModule.h b/src/modules/RemoteHardwareModule.h index dd39f5b69..4dc31d405 100644 --- a/src/modules/RemoteHardwareModule.h +++ b/src/modules/RemoteHardwareModule.h @@ -17,6 +17,9 @@ class RemoteHardwareModule : public ProtobufModule, /// The timestamp of our last watch event (we throttle watches to 1 change every 30 seconds) uint32_t lastWatchMsec = 0; + /// A bitmask of GPIOs that are exposed to the mesh if undefined access is not enabled + uint64_t availablePins = 0; + public: /** Constructor * name is for debugging output diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 03fa80415..eedfe1a71 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -44,6 +44,9 @@ static BluetoothPhoneAPI *bluetoothPhoneAPI; * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) */ +// Last ToRadio value received from the phone +static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; + class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks { virtual void onWrite(NimBLECharacteristic *pCharacteristic) @@ -51,7 +54,13 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks LOG_INFO("To Radio onwrite\n"); auto val = pCharacteristic->getValue(); - bluetoothPhoneAPI->handleToRadio(val.data(), val.length()); + if (memcmp(lastToRadio, val.data(), val.length()) != 0) { + LOG_DEBUG("New ToRadio packet\n"); + memcpy(lastToRadio, val.data(), val.length()); + bluetoothPhoneAPI->handleToRadio(val.data(), val.length()); + } else { + LOG_DEBUG("Dropping duplicate ToRadio packet we just saw\n"); + } } }; diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index ec3ff3e8d..fbc0728d7 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -141,10 +141,19 @@ void onFromRadioAuthorize(uint16_t conn_hdl, BLECharacteristic *chr, ble_gatts_e } authorizeRead(conn_hdl); } +// Last ToRadio value received from the phone +static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE]; + void onToRadioWrite(uint16_t conn_hdl, BLECharacteristic *chr, uint8_t *data, uint16_t len) { LOG_INFO("toRadioWriteCb data %p, len %u\n", data, len); - bluetoothPhoneAPI->handleToRadio(data, len); + if (memcmp(lastToRadio, data, len) != 0) { + LOG_DEBUG("New ToRadio packet\n"); + memcpy(lastToRadio, data, len); + bluetoothPhoneAPI->handleToRadio(data, len); + } else { + LOG_DEBUG("Dropping duplicate ToRadio packet we just saw\n"); + } } void setupMeshService(void) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 67ce021ab..9cf06ede8 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -80,6 +80,7 @@ void portduinoSetup() irq, busy, reset, + sx126x_ant_sw, txen, rxen, displayDC, @@ -181,6 +182,7 @@ void portduinoSetup() settingsMap[reset] = yamlConfig["Lora"]["Reset"].as(RADIOLIB_NC); settingsMap[txen] = yamlConfig["Lora"]["TXen"].as(RADIOLIB_NC); settingsMap[rxen] = yamlConfig["Lora"]["RXen"].as(RADIOLIB_NC); + settingsMap[sx126x_ant_sw] = yamlConfig["Lora"]["SX126X_ANT_SW"].as(RADIOLIB_NC); settingsMap[gpiochip] = yamlConfig["Lora"]["gpiochip"].as(0); settingsMap[ch341Quirk] = yamlConfig["Lora"]["ch341_quirk"].as(false); settingsMap[spiSpeed] = yamlConfig["Lora"]["spiSpeed"].as(2000000); @@ -309,6 +311,8 @@ void portduinoSetup() gpioInit(max_GPIO + 1); // Done here so we can inform Portduino how many GPIOs we need. // Need to bind all the configured GPIO pins so they're not simulated + // TODO: Can we do this in the for loop above? + // TODO: If one of these fails, we should log and terminate if (settingsMap.count(cs) > 0 && settingsMap[cs] != RADIOLIB_NC) { if (initGPIOPin(settingsMap[cs], gpioChipName) != ERRNO_OK) { settingsMap[cs] = RADIOLIB_NC; @@ -329,6 +333,11 @@ void portduinoSetup() settingsMap[reset] = RADIOLIB_NC; } } + if (settingsMap.count(sx126x_ant_sw) > 0 && settingsMap[sx126x_ant_sw] != RADIOLIB_NC) { + if (initGPIOPin(settingsMap[sx126x_ant_sw], gpioChipName) != ERRNO_OK) { + settingsMap[sx126x_ant_sw] = RADIOLIB_NC; + } + } if (settingsMap.count(user) > 0 && settingsMap[user] != RADIOLIB_NC) { if (initGPIOPin(settingsMap[user], gpioChipName) != ERRNO_OK) { settingsMap[user] = RADIOLIB_NC; @@ -395,4 +404,4 @@ int initGPIOPin(int pinNum, const std::string gpioChipName) #else return ERRNO_OK; #endif -} +} \ No newline at end of file diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 9d8e0120c..b64bb4f38 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -8,6 +8,7 @@ enum configNames { irq, busy, reset, + sx126x_ant_sw, txen, rxen, dio2_as_rf_switch, @@ -64,4 +65,4 @@ enum { level_error, level_warn, level_info, level_debug, level_trace }; extern std::map settingsMap; extern std::map settingsStrings; extern std::ofstream traceFile; -int initGPIOPin(int pinNum, std::string gpioChipname); +int initGPIOPin(int pinNum, std::string gpioChipname); \ No newline at end of file