diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml index 3a470b7bb..a755954b1 100644 --- a/bin/config-dist.yaml +++ b/bin/config-dist.yaml @@ -20,6 +20,7 @@ Lora: # CS: 0 # IRQ: 10 # Busy: 11 +# DIO2_AS_RF_SWITCH: true # spidev: spidev0.1 # Module: RF95 # Adafruit RFM9x @@ -154,4 +155,4 @@ Webserver: General: MaxNodes: 200 - MaxMessageQueue: 100 \ No newline at end of file + MaxMessageQueue: 100 diff --git a/bin/meshtasticd.service b/bin/meshtasticd.service index f15fdc871..1e8ee98b8 100644 --- a/bin/meshtasticd.service +++ b/bin/meshtasticd.service @@ -1,12 +1,16 @@ [Unit] Description=Meshtastic Native Daemon After=network-online.target +StartLimitInterval=200 +StartLimitBurst=5 [Service] User=root Group=root Type=simple ExecStart=/usr/sbin/meshtasticd +Restart=always +RestartSec=3 [Install] -WantedBy=multi-user.target \ No newline at end of file +WantedBy=multi-user.target diff --git a/bin/native-install.sh b/bin/native-install.sh index ba71c4f46..a8fcc29a6 100755 --- a/bin/native-install.sh +++ b/bin/native-install.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash cp "release/meshtasticd_linux_$(uname -m)" /usr/sbin/meshtasticd -mkdir /etc/meshtasticd +mkdir -p /etc/meshtasticd if [[ -f "/etc/meshtasticd/config.yaml" ]]; then cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml else diff --git a/protobufs b/protobufs index e22381a3c..8686d049c 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit e22381a3c6bbdd428f127ed8c0aa0a37789c3907 +Subproject commit 8686d049c22c232f57121e66dfb29e7be65010f0 diff --git a/src/buzz/buzz.cpp b/src/buzz/buzz.cpp index e42a9c203..8db9602bc 100644 --- a/src/buzz/buzz.cpp +++ b/src/buzz/buzz.cpp @@ -55,6 +55,18 @@ void playBeep() playTones(melody, sizeof(melody) / sizeof(ToneDuration)); } +void playGPSEnableBeep() +{ + ToneDuration melody[] = {{NOTE_C3, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}, {NOTE_CS4, DURATION_1_4}}; + playTones(melody, sizeof(melody) / sizeof(ToneDuration)); +} + +void playGPSDisableBeep() +{ + ToneDuration melody[] = {{NOTE_CS4, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}, {NOTE_C3, DURATION_1_4}}; + playTones(melody, sizeof(melody) / sizeof(ToneDuration)); +} + void playStartMelody() { ToneDuration melody[] = {{NOTE_FS3, DURATION_1_8}, {NOTE_AS3, DURATION_1_8}, {NOTE_CS4, DURATION_1_4}}; diff --git a/src/buzz/buzz.h b/src/buzz/buzz.h index 3883bd057..c52c3020c 100644 --- a/src/buzz/buzz.h +++ b/src/buzz/buzz.h @@ -3,3 +3,5 @@ void playBeep(); void playStartMelody(); void playShutdownMelody(); +void playGPSEnableBeep(); +void playGPSDisableBeep(); \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 955c2ea19..cedb1cdbc 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -114,6 +114,7 @@ along with this program. If not, see . #define CARDKB_ADDR 0x5F #define TDECK_KB_ADDR 0x55 #define BBQ10_KB_ADDR 0x1F +#define MPR121_KB_ADDR 0x5A #define PCF8574A_ADDRESS 0x20 // ----------------------------------------------------------------------------- diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 3daf6f191..b4f2e2e2c 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -31,8 +31,8 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::FoundDevice ScanI2C::firstKeyboard() const { - ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004, PCF8574A}; - return firstOfOrNONE(5, types); + ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004, PCF8574A, MPR121KB}; + return firstOfOrNONE(6, types); } ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 18a7bdbcd..4f7f4599b 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -62,7 +62,8 @@ class ScanI2C STK8BAXX, ICM20948, MAX30102, - TPS65233 + TPS65233, + MPR121KB } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index b01271a34..00617777b 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -243,6 +243,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(TDECK_KB_ADDR, TDECKKB, "T-Deck keyboard found"); SCAN_SIMPLE_CASE(BBQ10_KB_ADDR, BBQ10KB, "BB Q10 keyboard found"); + SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "st7567 display found"); #ifndef HAS_TCA9535 SCAN_SIMPLE_CASE(PCF8574A_ADDRESS, PCF8574A, "PCF8574A based keyboard found\n"); @@ -413,7 +414,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) #ifdef HAS_TPS65233 SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233 BIAS-T found"); #endif - SCAN_SIMPLE_CASE(MLX90614_ADDR_DEF, MLX90614, "MLX90614 IR temp sensor found"); + + case MLX90614_ADDR_DEF: + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0e), 1); + if (registerValue == 0x5a) { + type = MLX90614; + LOG_INFO("MLX90614 IR temp sensor found"); + } else { + type = MPR121KB; + LOG_INFO("MPR121KB keyboard found"); + } + break; case ICM20948_ADDR: // same as BMX160_ADDR case ICM20948_ADDR_ALT: // same as MPU6050_ADDR diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 18feb6daa..3a9516c84 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -7,6 +7,7 @@ #include "PowerMon.h" #include "RTC.h" #include "Throttle.h" +#include "buzz.h" #include "meshUtils.h" #include "main.h" // pmu_found @@ -266,6 +267,9 @@ GPS_RESPONSE GPS::getACK(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis) uint32_t startTime = millis(); const char frame_errors[] = "More than 100 frame errors"; int sCounter = 0; +#ifdef GPS_DEBUG + std::string debugmsg = ""; +#endif for (int j = 2; j < 6; j++) { buf[8] += buf[j]; @@ -291,20 +295,24 @@ GPS_RESPONSE GPS::getACK(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis) if (b == frame_errors[sCounter]) { sCounter++; if (sCounter == 26) { +#ifdef GPS_DEBUG + + LOG_DEBUG(debugmsg.c_str()); +#endif return GNSS_RESPONSE_FRAME_ERRORS; } } else { sCounter = 0; } #ifdef GPS_DEBUG - LOG_DEBUG("%02X", b); + debugmsg += vformat("%02X", b); #endif if (b == buf[ack]) { ack++; } else { if (ack == 3 && b == 0x00) { // UBX-ACK-NAK message #ifdef GPS_DEBUG - LOG_DEBUG(""); + LOG_DEBUG(debugmsg.c_str()); #endif LOG_WARN("Got NAK for class %02X message %02X", class_id, msg_id); return GNSS_RESPONSE_NAK; // NAK received @@ -314,7 +322,7 @@ GPS_RESPONSE GPS::getACK(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis) } } #ifdef GPS_DEBUG - LOG_DEBUG(""); + LOG_DEBUG(debugmsg.c_str()); LOG_WARN("No response for class %02X message %02X", class_id, msg_id); #endif return GNSS_RESPONSE_NONE; // No response received within timeout @@ -1623,6 +1631,9 @@ bool GPS::whileActive() { unsigned int charsInBuf = 0; bool isValid = false; +#ifdef GPS_DEBUG + std::string debugmsg = ""; +#endif if (powerState != GPS_ACTIVE) { clearBuffer(); return false; @@ -1640,7 +1651,7 @@ bool GPS::whileActive() int c = _serial_gps->read(); UBXscratch[charsInBuf] = c; #ifdef GPS_DEBUG - LOG_DEBUG("%c", c); + debugmsg += vformat("%c", (c >= 32 && c <= 126) ? c : '.'); #endif isValid |= reader.encode(c); if (charsInBuf > sizeof(UBXscratch) - 10 || c == '\r') { @@ -1652,6 +1663,9 @@ bool GPS::whileActive() charsInBuf++; } } +#ifdef GPS_DEBUG + LOG_DEBUG(debugmsg.c_str()); +#endif return isValid; } void GPS::enable() @@ -1680,6 +1694,7 @@ void GPS::toggleGpsMode() if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; LOG_INFO("User toggled GpsMode. Now DISABLED."); + playGPSDisableBeep(); #ifdef GNSS_AIROHA if (powerState == GPS_ACTIVE) { LOG_DEBUG("User power Off GPS"); @@ -1690,6 +1705,7 @@ void GPS::toggleGpsMode() } else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) { config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; LOG_INFO("User toggled GpsMode. Now ENABLED"); + playGPSEnableBeep(); enable(); } } diff --git a/src/gps/GPS.h b/src/gps/GPS.h index bc95613b3..8b1982cf7 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -75,7 +75,7 @@ class GPS : private concurrency::OSThread uint8_t fixType = 0; // fix type from GPGSA #endif private: - const int serialSpeeds[6] = {9600, 4800, 38400, 57600, 115200, 9600}; + const int serialSpeeds[6] = {9600, 115200, 38400, 4800, 57600, 9600}; uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastFixStartMsec = 0; uint32_t rx_gpio = 0; uint32_t tx_gpio = 0; diff --git a/src/input/MPR121Keyboard.cpp b/src/input/MPR121Keyboard.cpp new file mode 100644 index 000000000..e1b32aa54 --- /dev/null +++ b/src/input/MPR121Keyboard.cpp @@ -0,0 +1,430 @@ +// Based on the BBQ10 Keyboard + +#include "MPR121Keyboard.h" +#include "configuration.h" +#include + +#define _MPR121_REG_KEY 0x5a + +#define _MPR121_REG_TOUCH_STATUS 0x00 +#define _MPR121_REG_ELECTRODE_FILTERED_DATA +#define _MPR121_REG_BASELINE_VALUE 0x1E + +// Baseline filters +#define _MPR121_REG_MAX_HALF_DELTA_RISING 0x2B +#define _MPR121_REG_NOISE_HALF_DELTA_RISING 0x2C +#define _MPR121_REG_NOISE_COUNT_LIMIT_RISING 0x2D +#define _MPR121_REG_FILTER_DELAY_COUNT_RISING 0x2E +#define _MPR121_REG_MAX_HALF_DELTA_FALLING 0x2F +#define _MPR121_REG_NOISE_HALF_DELTA_FALLING 0x30 +#define _MPR121_REG_NOISE_COUNT_LIMIT_FALLING 0x31 +#define _MPR121_REG_FILTER_DELAY_COUNT_FALLING 0x32 +#define _MPR121_REG_NOISE_HALF_DELTA_TOUCHED 0x33 +#define _MPR121_REG_NOISE_COUNT_LIMIT_TOUCHED 0x34 +#define _MPR121_REG_FILTER_DELAY_COUNT_TOUCHED 0x35 + +#define _MPR121_REG_TOUCH_THRESHOLD 0x41 // First input, +2 for subsequent +#define _MPR121_REG_RELEASE_THRESHOLD 0x42 // First input, +2 for subsequent +#define _MPR121_REG_DEBOUNCE 0x5B +#define _MPR121_REG_CONFIG1 0x5C +#define _MPR121_REG_CONFIG2 0x5D +#define _MPR121_REG_ELECTRODE_CONFIG 0x5E +#define _MPR121_REG_SOFT_RESET 0x80 + +#define _KEY_MASK 0x0FFF // Key mask for the first 12 bits +#define _NUM_KEYS 12 + +#define ECR_CALIBRATION_TRACK_FROM_ZERO (0 << 6) +#define ECR_CALIBRATION_LOCK (1 << 6) +#define ECR_CALIBRATION_TRACK_FROM_PARTIAL_FILTER (2 << 6) // Recommended Typical Mode +#define ECR_CALIBRATION_TRACK_FROM_FULL_FILTER (3 << 6) +#define ECR_PROXIMITY_DETECTION_OFF (0 << 0) // Not using proximity detection +#define ECR_TOUCH_DETECTION_12CH (12 << 0) // Using all 12 channels + +#define MPR121_NONE 0x00 +#define MPR121_REBOOT 0x90 +#define MPR121_LEFT 0xb4 +#define MPR121_UP 0xb5 +#define MPR121_DOWN 0xb6 +#define MPR121_RIGHT 0xb7 +#define MPR121_ESC 0x1b +#define MPR121_BSP 0x08 +#define MPR121_SELECT 0x0d + +#define MPR121_FN_ON 0xf1 +#define MPR121_FN_OFF 0xf2 + +#define LONG_PRESS_THRESHOLD 2000 +#define MULTI_TAP_THRESHOLD 2000 + +uint8_t TapMod[12] = {1, 2, 1, 13, 7, 7, 7, 7, 7, 9, 7, 9}; // Num chars per key, Modulus for rotating through characters + +unsigned char MPR121_TapMap[12][13] = {{MPR121_BSP}, + {'0', ' '}, + {MPR121_SELECT}, + {'1', '.', ',', '?', '!', ':', ';', '-', '_', '\\', '/', '(', ')'}, + {'2', 'a', 'b', 'c', 'A', 'B', 'C'}, + {'3', 'd', 'e', 'f', 'D', 'E', 'F'}, + {'4', 'g', 'h', 'i', 'G', 'H', 'I'}, + {'5', 'j', 'k', 'l', 'J', 'K', 'L'}, + {'6', 'm', 'n', 'o', 'M', 'N', 'O'}, + {'7', 'p', 'q', 'r', 's', 'P', 'Q', 'R', 'S'}, + {'8', 't', 'u', 'v', 'T', 'U', 'V'}, + {'9', 'w', 'x', 'y', 'z', 'W', 'X', 'Y', 'Z'}}; + +unsigned char MPR121_LongPressMap[12] = {MPR121_ESC, ' ', MPR121_NONE, MPR121_NONE, MPR121_UP, MPR121_NONE, + MPR121_LEFT, MPR121_NONE, MPR121_RIGHT, MPR121_NONE, MPR121_DOWN, MPR121_NONE}; + +// Translation map from left to right, top to bottom layout to a more convenient layout to manufacture, matching the +// https://www.amazon.com.au/Capacitive-Sensitive-Sensitivity-Replacement-Traditional/dp/B0CTJD5KW9/ref=pd_ci_mcx_mh_mcx_views_0_title?th=1 +/*uint8_t MPR121_KeyMap[12] = { + 9, 6, 3, 0, + 10, 7, 4, 1, + 11, 8, 5, 2 +};*/ +// Rotated Layout +uint8_t MPR121_KeyMap[12] = {2, 5, 8, 11, 1, 4, 7, 10, 0, 3, 6, 9}; + +MPR121Keyboard::MPR121Keyboard() : m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr) +{ + // LOG_DEBUG("MPR121 @ %02x\n", m_addr); + state = Init; + last_key = -1; + last_tap = 0L; + char_idx = 0; + queue = ""; +} + +void MPR121Keyboard::begin(uint8_t addr, TwoWire *wire) +{ + m_addr = addr; + m_wire = wire; + + m_wire->begin(); + + reset(); +} + +void MPR121Keyboard::begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr) +{ + m_addr = addr; + m_wire = nullptr; + writeCallback = w; + readCallback = r; + reset(); +} + +void MPR121Keyboard::reset() +{ + LOG_DEBUG("MPR121 Resetting..."); + // Trigger a MPR121 Soft Reset + if (m_wire) { + m_wire->beginTransmission(m_addr); + m_wire->write(_MPR121_REG_SOFT_RESET); + m_wire->endTransmission(); + } + if (writeCallback) { + uint8_t data = 0; + writeCallback(m_addr, _MPR121_REG_SOFT_RESET, &data, 0); + } + delay(100); + // Reset Electrode Configuration to 0x00, Stop Mode + writeRegister(_MPR121_REG_ELECTRODE_CONFIG, 0x00); + delay(100); + + LOG_DEBUG("MPR121 Configuring"); + // Set touch release thresholds + for (uint8_t i = 0; i < 12; i++) { + // Set touch threshold + writeRegister(_MPR121_REG_TOUCH_THRESHOLD + (i * 2), 15); + delay(20); + // Set release threshold + writeRegister(_MPR121_REG_RELEASE_THRESHOLD + (i * 2), 7); + delay(20); + } + // Configure filtering and baseline registers + writeRegister(_MPR121_REG_MAX_HALF_DELTA_RISING, 0x01); + delay(20); + writeRegister(_MPR121_REG_MAX_HALF_DELTA_FALLING, 0x01); + delay(20); + writeRegister(_MPR121_REG_NOISE_HALF_DELTA_RISING, 0x01); + delay(20); + writeRegister(_MPR121_REG_NOISE_HALF_DELTA_FALLING, 0x05); + delay(20); + writeRegister(_MPR121_REG_NOISE_HALF_DELTA_TOUCHED, 0x00); + delay(20); + writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_RISING, 0x0e); + delay(20); + writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_FALLING, 0x01); + delay(20); + writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_TOUCHED, 0x00); + delay(20); + writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_RISING, 0x00); + delay(20); + writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_FALLING, 0x00); + delay(20); + writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_TOUCHED, 0x00); + delay(20); + // Set Debounce to 0x02 + writeRegister(_MPR121_REG_DEBOUNCE, 0x00); + delay(20); + // Set Filter1 itterations and discharge current 6x and 16uA respectively (0x10) + writeRegister(_MPR121_REG_CONFIG1, 0x10); + delay(20); + // Set CDT to 0.5us, Filter2 itterations to 4x, and Sample interval = 0 (0x20) + writeRegister(_MPR121_REG_CONFIG2, 0x20); + delay(20); + // Enter run mode by Seting partial filter calibration tracking, disable proximity detection, enable 12 channels + writeRegister(_MPR121_REG_ELECTRODE_CONFIG, + ECR_CALIBRATION_TRACK_FROM_PARTIAL_FILTER | ECR_PROXIMITY_DETECTION_OFF | ECR_TOUCH_DETECTION_12CH); + delay(100); + LOG_DEBUG("MPR121 Running"); + state = Idle; +} + +void MPR121Keyboard::attachInterrupt(uint8_t pin, void (*func)(void)) const +{ + pinMode(pin, INPUT_PULLUP); + ::attachInterrupt(digitalPinToInterrupt(pin), func, RISING); +} + +void MPR121Keyboard::detachInterrupt(uint8_t pin) const +{ + ::detachInterrupt(pin); +} + +uint8_t MPR121Keyboard::status() const +{ + return readRegister16(_MPR121_REG_KEY); +} + +uint8_t MPR121Keyboard::keyCount() const +{ + // Read the key register + uint16_t keyRegister = readRegister16(_MPR121_REG_KEY); + return keyCount(keyRegister); +} + +uint8_t MPR121Keyboard::keyCount(uint16_t value) const +{ + // Mask the first 12 bits + uint16_t buttonState = value & _KEY_MASK; + + // Count how many bits are set to 1 (i.e., how many buttons are pressed) + uint8_t numButtonsPressed = 0; + for (uint8_t i = 0; i < 12; ++i) { + if (buttonState & (1 << i)) { + numButtonsPressed++; + } + } + + return numButtonsPressed; +} + +bool MPR121Keyboard::hasEvent() +{ + return queue.length() > 0; +} + +void MPR121Keyboard::queueEvent(char next) +{ + if (next == MPR121_NONE) { + return; + } + queue.concat(next); +} + +char MPR121Keyboard::dequeueEvent() +{ + if (queue.length() < 1) { + return MPR121_NONE; + } + char next = queue.charAt(0); + queue.remove(0, 1); + return next; +} + +void MPR121Keyboard::trigger() +{ + // Intended to fire in response to an interrupt from the MPR121 or a longpress callback + // Only functional if not in Init state + if (state != Init) { + // Read the key register + uint16_t keyRegister = readRegister16(_MPR121_REG_KEY); + uint8_t keysPressed = keyCount(keyRegister); + if (keysPressed == 0) { + // No buttons pressed + if (state == Held) + released(); + state = Idle; + return; + } + if (keysPressed == 1) { + // No buttons pressed + if (state == Held || state == HeldLong) + held(keyRegister); + if (state == Idle) + pressed(keyRegister); + return; + } + if (keysPressed > 1) { + // Multipress + state = Busy; + return; + } + } else { + reset(); + } +} + +void MPR121Keyboard::pressed(uint16_t keyRegister) +{ + if (state == Init || state == Busy) { + return; + } + if (keyCount(keyRegister) != 1) { + LOG_DEBUG("Multipress"); + return; + } else { + LOG_DEBUG("Pressed"); + } + uint16_t buttonState = keyRegister & _KEY_MASK; + uint8_t next_pin = 0; + for (uint8_t i = 0; i < 12; ++i) { + if (buttonState & (1 << i)) { + next_pin = i; + } + } + uint8_t next_key = MPR121_KeyMap[next_pin]; + LOG_DEBUG("MPR121 Pin: %i Key: %i", next_pin, next_key); + uint32_t now = millis(); + int32_t tap_interval = now - last_tap; + if (tap_interval < 0) { + // long running, millis has overflowed. + last_tap = 0; + state = Busy; + return; + } + if (next_key != last_key || tap_interval > MULTI_TAP_THRESHOLD) { + char_idx = 0; + } else { + char_idx += 1; + } + last_key = next_key; + last_tap = now; + state = Held; + return; +} + +void MPR121Keyboard::held(uint16_t keyRegister) +{ + if (state == Init || state == Busy) { + return; + } + if (keyCount(keyRegister) != 1) { + return; + } + LOG_DEBUG("Held"); + uint16_t buttonState = keyRegister & _KEY_MASK; + uint8_t next_key = 0; + for (uint8_t i = 0; i < 12; ++i) { + if (buttonState & (1 << i)) { + next_key = MPR121_KeyMap[i]; + } + } + uint32_t now = millis(); + int32_t held_interval = now - last_tap; + if (held_interval < 0 || next_key != last_key) { + // long running, millis has overflowed, or a key has been switched quickly... + last_tap = 0; + state = Busy; + return; + } + if (held_interval > LONG_PRESS_THRESHOLD) { + // Set state to heldlong, send a longpress, and reset the timer... + state = HeldLong; // heldlong will allow this function to still fire, but prevent a "release" + queueEvent(MPR121_LongPressMap[last_key]); + last_tap = now; + LOG_DEBUG("Long Press Key: %i Map: %i", last_key, MPR121_LongPressMap[last_key]); + } + return; +} + +void MPR121Keyboard::released() +{ + if (state != Held) { + return; + } + // would clear longpress callback... later. + if (last_key < 0 || last_key > _NUM_KEYS) { // reset to idle if last_key out of bounds + last_key = -1; + state = Idle; + return; + } + LOG_DEBUG("Released"); + if (char_idx > 0 && TapMod[last_key] > 1) { + queueEvent(MPR121_BSP); + LOG_DEBUG("Multi Press, Backspace"); + } + queueEvent(MPR121_TapMap[last_key][(char_idx % TapMod[last_key])]); + LOG_DEBUG("Key Press: %i Index:%i if %i Map: %i", last_key, char_idx, TapMod[last_key], + MPR121_TapMap[last_key][(char_idx % TapMod[last_key])]); +} + +uint8_t MPR121Keyboard::readRegister8(uint8_t reg) const +{ + if (m_wire) { + m_wire->beginTransmission(m_addr); + m_wire->write(reg); + m_wire->endTransmission(); + + m_wire->requestFrom(m_addr, (uint8_t)1); + if (m_wire->available() < 1) + return 0; + + return m_wire->read(); + } + if (readCallback) { + uint8_t data; + readCallback(m_addr, reg, &data, 1); + return data; + } + return 0; +} + +uint16_t MPR121Keyboard::readRegister16(uint8_t reg) const +{ + uint8_t data[2] = {0}; + // uint8_t low = 0, high = 0; + if (m_wire) { + m_wire->beginTransmission(m_addr); + m_wire->write(reg); + m_wire->endTransmission(); + + m_wire->requestFrom(m_addr, (uint8_t)2); + if (m_wire->available() < 2) + return 0; + data[0] = m_wire->read(); + data[1] = m_wire->read(); + } + if (readCallback) { + readCallback(m_addr, reg, data, 2); + } + return (data[1] << 8) | data[0]; +} + +void MPR121Keyboard::writeRegister(uint8_t reg, uint8_t value) +{ + uint8_t data[2]; + data[0] = reg; + data[1] = value; + + if (m_wire) { + m_wire->beginTransmission(m_addr); + m_wire->write(data, sizeof(uint8_t) * 2); + m_wire->endTransmission(); + } + if (writeCallback) { + writeCallback(m_addr, data[0], &(data[1]), 1); + } +} diff --git a/src/input/MPR121Keyboard.h b/src/input/MPR121Keyboard.h new file mode 100644 index 000000000..6349750ce --- /dev/null +++ b/src/input/MPR121Keyboard.h @@ -0,0 +1,56 @@ +// Based on the BBQ10 Keyboard + +#include "concurrency/NotifiedWorkerThread.h" +#include "configuration.h" +#include +#include + +class MPR121Keyboard +{ + public: + typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len); + + enum MPR121States { Init = 0, Idle, Held, HeldLong, Busy }; + + MPR121States state; + + int8_t last_key; + uint32_t last_tap; + uint8_t char_idx; + + String queue; + + MPR121Keyboard(); + + void begin(uint8_t addr = MPR121_KB_ADDR, TwoWire *wire = &Wire); + + void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = MPR121_KB_ADDR); + + void reset(void); + + void attachInterrupt(uint8_t pin, void (*func)(void)) const; + void detachInterrupt(uint8_t pin) const; + + void trigger(void); + void pressed(uint16_t value); + void held(uint16_t value); + void released(void); + + uint8_t status(void) const; + uint8_t keyCount(void) const; + uint8_t keyCount(uint16_t value) const; + + bool hasEvent(void); + char dequeueEvent(void); + void queueEvent(char); + + uint8_t readRegister8(uint8_t reg) const; + uint16_t readRegister16(uint8_t reg) const; + void writeRegister(uint8_t reg, uint8_t value); + + private: + TwoWire *m_wire; + uint8_t m_addr; + i2c_com_fptr_t readCallback; + i2c_com_fptr_t writeCallback; +}; \ No newline at end of file diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index 3c23aa410..b8ff6fb36 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -16,8 +16,8 @@ void CardKbI2cImpl::init() #if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) if (cardkb_found.address == 0x00) { LOG_DEBUG("Rescanning for I2C keyboard"); - uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR}; - uint8_t i2caddr_asize = 3; + uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR}; + uint8_t i2caddr_asize = 4; auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); #if WIRE_INTERFACES_COUNT == 2 @@ -43,12 +43,17 @@ void CardKbI2cImpl::init() // assign an arbitrary value to distinguish from other models kb_model = 0x11; break; + case ScanI2C::DeviceType::MPR121KB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x37; + break; default: // use this as default since it's also just zero LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00", kb_info.type); kb_model = 0x00; } } + LOG_DEBUG("Keyboard Type: 0x%02x Model: 0x%02x Address: 0x%02x\n", kb_info.type, kb_model, cardkb_found.address); if (cardkb_found.address == 0x00) { disable(); return; diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 30188bb92..d0f36c386 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -40,6 +40,9 @@ int32_t KbI2cBase::runOnce() Q10keyboard.begin(BBQ10_KB_ADDR, &Wire1); Q10keyboard.setBacklight(0); } + if (cardkb_found.address == MPR121_KB_ADDR) { + MPRkeyboard.begin(MPR121_KB_ADDR, &Wire1); + } break; #endif case ScanI2C::WIRE: @@ -49,6 +52,9 @@ int32_t KbI2cBase::runOnce() Q10keyboard.begin(BBQ10_KB_ADDR, &Wire); Q10keyboard.setBacklight(0); } + if (cardkb_found.address == MPR121_KB_ADDR) { + MPRkeyboard.begin(MPR121_KB_ADDR, &Wire); + } break; case ScanI2C::NO_I2C: default: @@ -157,6 +163,69 @@ int32_t KbI2cBase::runOnce() } break; } + case 0x37: { // MPR121 + MPRkeyboard.trigger(); + InputEvent e; + + while (MPRkeyboard.hasEvent()) { + char nextEvent = MPRkeyboard.dequeueEvent(); + e.inputEvent = ANYKEY; + e.kbchar = 0x00; + e.source = this->_originName; + switch (nextEvent) { + case 0x00: // MPR121_NONE + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + e.kbchar = 0x00; + break; + case 0x90: // MPR121_REBOOT + e.inputEvent = ANYKEY; + e.kbchar = INPUT_BROKER_MSG_REBOOT; + break; + case 0xb4: // MPR121_LEFT + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT; + e.kbchar = 0x00; + break; + case 0xb5: // MPR121_UP + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; + e.kbchar = 0x00; + break; + case 0xb6: // MPR121_DOWN + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN; + e.kbchar = 0x00; + break; + case 0xb7: // MPR121_RIGHT + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; + e.kbchar = 0x00; + break; + case 0x1b: // MPR121_ESC + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL; + e.kbchar = 0x1b; + break; + case 0x08: // MPR121_BSP + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK; + e.kbchar = 0x08; + break; + case 0x0d: // MPR121_SELECT + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; + e.kbchar = 0x0d; + break; + default: + if (nextEvent > 127) { + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + e.kbchar = 0x00; + break; + } + e.inputEvent = ANYKEY; + e.kbchar = nextEvent; + break; + } + if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) { + LOG_DEBUG("MP121 Notifying: %i Char: %i", e.inputEvent, e.kbchar); + this->notifyObservers(&e); + } + } + break; + } case 0x02: { // RAK14004 uint8_t rDataBuf[8] = {0}; diff --git a/src/input/kbI2cBase.h b/src/input/kbI2cBase.h index 35b9b0901..dc2414fc0 100644 --- a/src/input/kbI2cBase.h +++ b/src/input/kbI2cBase.h @@ -2,6 +2,7 @@ #include "BBQ10Keyboard.h" #include "InputBroker.h" +#include "MPR121Keyboard.h" #include "Wire.h" #include "concurrency/OSThread.h" @@ -19,5 +20,6 @@ class KbI2cBase : public Observable, public concurrency::OST TwoWire *i2cBus = 0; BBQ10Keyboard Q10keyboard; + MPR121Keyboard MPRkeyboard; bool is_sym = false; -}; +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f99063730..028f94b6a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -526,6 +526,10 @@ void setup() case ScanI2C::DeviceType::PCF8574A: kb_model = 0x12; break; + case ScanI2C::DeviceType::MPR121KB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x37; + break; default: // use this as default since it's also just zero LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00", kb_info.type); diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp index 211d9afd4..b9fe95678 100644 --- a/src/mesh/Channels.cpp +++ b/src/mesh/Channels.cpp @@ -117,13 +117,18 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) static const uint8_t defaultpsk0[] = USERPREFS_CHANNEL_0_PSK; memcpy(channelSettings.psk.bytes, defaultpsk0, sizeof(defaultpsk0)); channelSettings.psk.size = sizeof(defaultpsk0); - #endif #ifdef USERPREFS_CHANNEL_0_NAME strcpy(channelSettings.name, USERPREFS_CHANNEL_0_NAME); #endif #ifdef USERPREFS_CHANNEL_0_PRECISION channelSettings.module_settings.position_precision = USERPREFS_CHANNEL_0_PRECISION; +#endif +#ifdef USERPREFS_CHANNEL_0_UPLINK_ENABLED + channelSettings.uplink_enabled = USERPREFS_CHANNEL_0_UPLINK_ENABLED; +#endif +#ifdef USERPREFS_CHANNEL_0_DOWNLINK_ENABLED + channelSettings.downlink_enabled = USERPREFS_CHANNEL_0_DOWNLINK_ENABLED; #endif break; case 1: @@ -131,13 +136,18 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) static const uint8_t defaultpsk1[] = USERPREFS_CHANNEL_1_PSK; memcpy(channelSettings.psk.bytes, defaultpsk1, sizeof(defaultpsk1)); channelSettings.psk.size = sizeof(defaultpsk1); - #endif #ifdef USERPREFS_CHANNEL_1_NAME strcpy(channelSettings.name, USERPREFS_CHANNEL_1_NAME); #endif #ifdef USERPREFS_CHANNEL_1_PRECISION channelSettings.module_settings.position_precision = USERPREFS_CHANNEL_1_PRECISION; +#endif +#ifdef USERPREFS_CHANNEL_1_UPLINK_ENABLED + channelSettings.uplink_enabled = USERPREFS_CHANNEL_1_UPLINK_ENABLED; +#endif +#ifdef USERPREFS_CHANNEL_1_DOWNLINK_ENABLED + channelSettings.downlink_enabled = USERPREFS_CHANNEL_1_DOWNLINK_ENABLED; #endif break; case 2: @@ -145,13 +155,18 @@ void Channels::initDefaultChannel(ChannelIndex chIndex) static const uint8_t defaultpsk2[] = USERPREFS_CHANNEL_2_PSK; memcpy(channelSettings.psk.bytes, defaultpsk2, sizeof(defaultpsk2)); channelSettings.psk.size = sizeof(defaultpsk2); - #endif #ifdef USERPREFS_CHANNEL_2_NAME strcpy(channelSettings.name, USERPREFS_CHANNEL_2_NAME); #endif #ifdef USERPREFS_CHANNEL_2_PRECISION channelSettings.module_settings.position_precision = USERPREFS_CHANNEL_2_PRECISION; +#endif +#ifdef USERPREFS_CHANNEL_2_UPLINK_ENABLED + channelSettings.uplink_enabled = USERPREFS_CHANNEL_2_UPLINK_ENABLED; +#endif +#ifdef USERPREFS_CHANNEL_2_DOWNLINK_ENABLED + channelSettings.downlink_enabled = USERPREFS_CHANNEL_2_DOWNLINK_ENABLED; #endif break; default: diff --git a/src/mesh/Default.cpp b/src/mesh/Default.cpp index 0bedcfc91..653528b60 100644 --- a/src/mesh/Default.cpp +++ b/src/mesh/Default.cpp @@ -43,6 +43,15 @@ uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t d return getConfiguredOrDefaultMs(configured, defaultValue) * congestionScalingCoefficient(numOnlineNodes); } +uint32_t Default::getConfiguredOrMinimumValue(uint32_t configured, uint32_t minValue) +{ + // If zero, intervals should be coalesced later by getConfiguredOrDefault... methods + if (configured == 0) + return configured; + + return configured < minValue ? minValue : configured; +} + uint8_t Default::getConfiguredOrDefaultHopLimit(uint8_t configured) { #if USERPREFS_EVENT_MODE diff --git a/src/mesh/Default.h b/src/mesh/Default.h index 5641b5d25..2406dafc5 100644 --- a/src/mesh/Default.h +++ b/src/mesh/Default.h @@ -6,8 +6,9 @@ #define THIRTY_SECONDS_MS 30 * 1000 #define FIVE_SECONDS_MS 5 * 1000 +#define min_default_telemetry_interval_secs 30 * 60 #define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60) -#define default_telemetry_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 30 * 60) +#define default_telemetry_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 60 * 60) #define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60) #define default_wait_bluetooth_secs IF_ROUTER(1, 60) #define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep @@ -35,6 +36,7 @@ class Default static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue); static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes); static uint8_t getConfiguredOrDefaultHopLimit(uint8_t configured); + static uint32_t getConfiguredOrMinimumValue(uint32_t configured, uint32_t minValue); private: static float congestionScalingCoefficient(int numOnlineNodes) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 268fee2b0..6760d70eb 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -44,7 +44,7 @@ bool FloodingRouter::isRebroadcaster() void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) { bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0); - if (isAckorReply && !isToUs(p) && p->to != NODENUM_BROADCAST) { + if (isAckorReply && !isToUs(p) && !isBroadcast(p->to)) { // do not flood direct message that is ACKed or replied to LOG_DEBUG("Rxd an ACK/reply not for me, cancel rebroadcast."); Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp index dd6d97317..d0c1a1fbc 100644 --- a/src/mesh/LR11x0Interface.cpp +++ b/src/mesh/LR11x0Interface.cpp @@ -67,8 +67,10 @@ template bool LR11x0Interface::init() power = LR1110_MAX_POWER; if ((power > LR1120_MAX_POWER) && - (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) // clamp again if wide freq range + (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // clamp again if wide freq range power = LR1120_MAX_POWER; + preambleLength = 12; // 12 is the default for operation above 2GHz + } limitPower(); diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 48c3ee47d..a8de540eb 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -86,7 +86,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets auto ourNodeNum = nodeDB->getNodeNum(); - bool toUs = mp.to == NODENUM_BROADCAST || isToUs(&mp); + bool toUs = isBroadcast(mp.to) || isToUs(&mp); for (auto i = modules->begin(); i != modules->end(); ++i) { auto &pi = **i; diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 27d100fbe..7a3d785dd 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -57,4 +57,6 @@ bool isFromUs(const meshtastic_MeshPacket *p); bool isToUs(const meshtastic_MeshPacket *p); /* Some clients might not properly set priority, therefore we fix it here. */ -void fixPriority(meshtastic_MeshPacket *p); \ No newline at end of file +void fixPriority(meshtastic_MeshPacket *p); + +bool isBroadcast(uint32_t dest); \ No newline at end of file diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 558c5b825..78ccdd85a 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -171,6 +171,22 @@ NodeDB::NodeDB() resetRadioConfig(); // If bogus settings got saved, then fix them // nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d", config.lora.region, myNodeInfo.my_node_num, numMeshNodes); + // If we are setup to broadcast on the default channel, ensure that the telemetry intervals are coerced to the minimum value + // of 30 minutes or more + if (channels.isDefaultChannel(channels.getPrimaryIndex())) { + LOG_DEBUG("Coercing telemetry to min of 30 minutes on defaults"); + moduleConfig.telemetry.device_update_interval = Default::getConfiguredOrMinimumValue( + moduleConfig.telemetry.device_update_interval, min_default_telemetry_interval_secs); + moduleConfig.telemetry.environment_update_interval = Default::getConfiguredOrMinimumValue( + moduleConfig.telemetry.environment_update_interval, min_default_telemetry_interval_secs); + moduleConfig.telemetry.air_quality_interval = Default::getConfiguredOrMinimumValue( + moduleConfig.telemetry.air_quality_interval, min_default_telemetry_interval_secs); + moduleConfig.telemetry.power_update_interval = Default::getConfiguredOrMinimumValue( + moduleConfig.telemetry.power_update_interval, min_default_telemetry_interval_secs); + moduleConfig.telemetry.health_update_interval = Default::getConfiguredOrMinimumValue( + moduleConfig.telemetry.health_update_interval, min_default_telemetry_interval_secs); + } + if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate))) saveWhat |= SEGMENT_DEVICESTATE; if (configCRC != crc32Buffer(&config, sizeof(config))) @@ -206,6 +222,11 @@ bool isToUs(const meshtastic_MeshPacket *p) return p->to == nodeDB->getNodeNum(); } +bool isBroadcast(uint32_t dest) +{ + return dest == NODENUM_BROADCAST || dest == NODENUM_BROADCAST_NO_LORA; +} + bool NodeDB::resetRadioConfig(bool factory_reset) { bool didFactoryReset = false; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 1ec3b9077..f44d50d36 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -467,7 +467,10 @@ void RadioLibInterface::setStandby() void RadioLibInterface::startSend(meshtastic_MeshPacket *txp) { printPacket("Starting low level send", txp); - if (disabled || !config.lora.tx_enabled) { + if (txp->to == NODENUM_BROADCAST_NO_LORA) { + LOG_DEBUG("Drop Tx packet because dest is broadcast no-lora"); + packetPool.release(txp); + } else if (disabled || !config.lora.tx_enabled) { LOG_WARN("Drop Tx packet because LoRa Tx disabled"); packetPool.release(txp); } else { diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index b7296f959..84ae7bbb0 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -181,7 +181,7 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src) } else { // If we are sending a broadcast, we also treat it as if we just received it ourself // this allows local apps (and PCs) to see broadcasts sourced locally - if (p->to == NODENUM_BROADCAST) { + if (isBroadcast(p->to)) { handleReceived(p, src); } @@ -240,7 +240,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p) // assert // Never set the want_ack flag on broadcast packets sent over the air. - if (p->to == NODENUM_BROADCAST) + if (isBroadcast(p->to)) p->want_ack = false; // Up until this point we might have been using 0 for the from address (if it started with the phone), but when we send over @@ -328,7 +328,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p) memcpy(ScratchEncrypted, p->encrypted.bytes, rawSize); #if !(MESHTASTIC_EXCLUDE_PKI) // Attempt PKI decryption first - if (p->channel == 0 && isToUs(p) && p->to > 0 && p->to != NODENUM_BROADCAST && nodeDB->getMeshNode(p->from) != nullptr && + if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && nodeDB->getMeshNode(p->from) != nullptr && nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && nodeDB->getMeshNode(p->to)->user.public_key.size > 0 && rawSize > MESHTASTIC_PKC_OVERHEAD) { LOG_DEBUG("Attempting PKI decryption"); @@ -493,7 +493,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) // Don't use PKC if it's not explicitly requested and a non-primary channel is requested !(p->pki_encrypted != true && p->channel > 0) && // Check for valid keys and single node destination - config.security.private_key.size == 32 && p->to != NODENUM_BROADCAST && node != nullptr && + config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr && // Check for a known public key for the destination (node->user.public_key.size == 32) && // Some portnums either make no sense to send with PKC @@ -615,7 +615,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) #if !MESHTASTIC_EXCLUDE_MQTT // Mark as pki_encrypted if it is not yet decoded and MQTT encryption is also enabled, hash matches and it's a DM not to // us (because we would be able to decrypt it) - if (!decoded && moduleConfig.mqtt.encryption_enabled && p->channel == 0x00 && p->to != NODENUM_BROADCAST && !isToUs(p)) + if (!decoded && moduleConfig.mqtt.encryption_enabled && p->channel == 0x00 && !isBroadcast(p->to) && !isToUs(p)) p_encrypted->pki_encrypted = true; // After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet if ((decoded || p_encrypted->pki_encrypted) && moduleConfig.mqtt.enabled && !isFromUs(p) && mqtt) diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h index 72a89fcdf..4c76e74cb 100644 --- a/src/mesh/generated/meshtastic/config.pb.h +++ b/src/mesh/generated/meshtastic/config.pb.h @@ -75,7 +75,10 @@ typedef enum _meshtastic_Config_DeviceConfig_RebroadcastMode { but takes it step further by also ignoring messages from nodenums not in the node's known list (NodeDB) */ meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY = 3, /* Only permitted for SENSOR, TRACKER and TAK_TRACKER roles, this will inhibit all rebroadcasts, not unlike CLIENT_MUTE role. */ - meshtastic_Config_DeviceConfig_RebroadcastMode_NONE = 4 + meshtastic_Config_DeviceConfig_RebroadcastMode_NONE = 4, + /* Ignores packets from non-standard portnums such as: TAK, RangeTest, PaxCounter, etc. + Only rebroadcasts packets with standard portnums: NodeInfo, Text, Position, Telemetry, and Routing. */ + meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY = 5 } meshtastic_Config_DeviceConfig_RebroadcastMode; /* Bit field of boolean configuration options, indicating which optional @@ -587,8 +590,8 @@ extern "C" { #define _meshtastic_Config_DeviceConfig_Role_ARRAYSIZE ((meshtastic_Config_DeviceConfig_Role)(meshtastic_Config_DeviceConfig_Role_TAK_TRACKER+1)) #define _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN meshtastic_Config_DeviceConfig_RebroadcastMode_ALL -#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_NONE -#define _meshtastic_Config_DeviceConfig_RebroadcastMode_ARRAYSIZE ((meshtastic_Config_DeviceConfig_RebroadcastMode)(meshtastic_Config_DeviceConfig_RebroadcastMode_NONE+1)) +#define _meshtastic_Config_DeviceConfig_RebroadcastMode_MAX meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY +#define _meshtastic_Config_DeviceConfig_RebroadcastMode_ARRAYSIZE ((meshtastic_Config_DeviceConfig_RebroadcastMode)(meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY+1)) #define _meshtastic_Config_PositionConfig_PositionFlags_MIN meshtastic_Config_PositionConfig_PositionFlags_UNSET #define _meshtastic_Config_PositionConfig_PositionFlags_MAX meshtastic_Config_PositionConfig_PositionFlags_SPEED diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 313719d9b..17254354c 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -765,6 +765,7 @@ typedef struct _meshtastic_NodeInfo { bool is_favorite; } meshtastic_NodeInfo; +typedef PB_BYTES_ARRAY_T(16) meshtastic_MyNodeInfo_device_id_t; /* Unique local debugging info for this node Note: we don't include position or the user info, because that will come in the Sent to the phone in response to WantNodes. */ @@ -778,6 +779,8 @@ typedef struct _meshtastic_MyNodeInfo { /* The minimum app version that can talk to this device. Phone/PC apps should compare this to their build number and if too low tell the user they must update their app */ uint32_t min_app_version; + /* Unique hardware identifier for this device */ + meshtastic_MyNodeInfo_device_id_t device_id; } meshtastic_MyNodeInfo; /* Debug output from the device. @@ -1112,7 +1115,7 @@ extern "C" { #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0} #define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0} -#define meshtastic_MyNodeInfo_init_default {0, 0, 0} +#define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}} #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} #define meshtastic_FromRadio_init_default {0, 0, {meshtastic_MeshPacket_init_default}} @@ -1137,7 +1140,7 @@ extern "C" { #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0} #define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0} -#define meshtastic_MyNodeInfo_init_zero {0, 0, 0} +#define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}} #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} #define meshtastic_FromRadio_init_zero {0, 0, {meshtastic_MeshPacket_init_zero}} @@ -1244,6 +1247,7 @@ extern "C" { #define meshtastic_MyNodeInfo_my_node_num_tag 1 #define meshtastic_MyNodeInfo_reboot_count_tag 8 #define meshtastic_MyNodeInfo_min_app_version_tag 11 +#define meshtastic_MyNodeInfo_device_id_tag 12 #define meshtastic_LogRecord_message_tag 1 #define meshtastic_LogRecord_time_tag 2 #define meshtastic_LogRecord_source_tag 3 @@ -1446,7 +1450,8 @@ X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) #define meshtastic_MyNodeInfo_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, my_node_num, 1) \ X(a, STATIC, SINGULAR, UINT32, reboot_count, 8) \ -X(a, STATIC, SINGULAR, UINT32, min_app_version, 11) +X(a, STATIC, SINGULAR, UINT32, min_app_version, 11) \ +X(a, STATIC, SINGULAR, BYTES, device_id, 12) #define meshtastic_MyNodeInfo_CALLBACK NULL #define meshtastic_MyNodeInfo_DEFAULT NULL @@ -1669,7 +1674,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_LogRecord_size 426 #define meshtastic_MeshPacket_size 367 #define meshtastic_MqttClientProxyMessage_size 501 -#define meshtastic_MyNodeInfo_size 18 +#define meshtastic_MyNodeInfo_size 36 #define meshtastic_NeighborInfo_size 258 #define meshtastic_Neighbor_size 22 #define meshtastic_NodeInfo_size 317 diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index 85552a6bf..cbf63e9af 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -122,7 +122,7 @@ Will be used for broadcast. int32_t NeighborInfoModule::runOnce() { if (airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) { - sendNeighborInfo(NODENUM_BROADCAST, false); + sendNeighborInfo(NODENUM_BROADCAST_NO_LORA, false); } return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_neighbor_info_broadcast_secs); } diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 89f6ed3c4..843711807 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -16,7 +16,7 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes bool hasChanged = nodeDB->updateUser(getFrom(&mp), p, mp.channel); - bool wasBroadcast = mp.to == NODENUM_BROADCAST; + bool wasBroadcast = isBroadcast(mp.to); // Show new nodes on LCD screen if (wasBroadcast) { diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index 3b7be1ab7..f11a9a542 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -10,11 +10,7 @@ RoutingModule *routingModule; bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Routing *r) { - printPacket("Routing sniffing", &mp); - router->sniffReceived(&mp, r); - - bool maybePKI = - mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag && mp.channel == 0 && mp.to != NODENUM_BROADCAST; + bool maybePKI = mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag && mp.channel == 0 && !isBroadcast(mp.to); // Beginning of logic whether to drop the packet based on Rebroadcast mode if (mp.which_payload_variant == meshtastic_MeshPacket_encrypted_tag && (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_LOCAL_ONLY || @@ -26,9 +22,12 @@ bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mesh return false; } + printPacket("Routing sniffing", &mp); + router->sniffReceived(&mp, r); + // FIXME - move this to a non promsicious PhoneAPI module? // Note: we are careful not to send back packets that started with the phone back to the phone - if ((mp.to == NODENUM_BROADCAST || isToUs(&mp)) && (mp.from != 0)) { + if ((isBroadcast(mp.to) || isToUs(&mp)) && (mp.from != 0)) { printPacket("Delivering rx packet", &mp); service->handleFromRadio(&mp); } diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 45518153e..2a2018395 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -706,22 +706,48 @@ bool MQTT::isValidJsonEnvelope(JSONObject &json) bool MQTT::isPrivateIpAddress(const char address[]) { - // Min. length like 10.0.0.0, max like 192.168.255.255 + // Min. length like 10.0.0.0 (8), max like 192.168.255.255:65535 (21) size_t length = strlen(address); - if (length < 8 || length > 15) { + if (length < 8 || length > 21) { return false; } - // Ensure the address contains only digits and dots. + // Ensure the address contains only digits and dots and maybe a colon. + // Some limited validation is done. // Even if it's not a valid IP address, we will know it's not a domain. + bool hasColon = false; + int numDots = 0; for (int i = 0; i < length; i++) { - if (!isdigit(address[i]) && address[i] != '.') { + if (!isdigit(address[i]) && address[i] != '.' && address[i] != ':') { return false; } + + // Dots can't be the first character, immediately follow another dot, + // occur more than 3 times, or occur after a colon. + if (address[i] == '.') { + if (++numDots > 3 || i == 0 || address[i - 1] == '.' || hasColon) { + return false; + } + } + // There can only be a single colon, and it can only occur after 3 dots + else if (address[i] == ':') { + if (hasColon || numDots < 3) { + return false; + } + + hasColon = true; + } + } + + // Final validation for IPv4 address and port format. + // Note that the values of octets haven't been tested, only the address format. + if (numDots != 3) { + return false; } // Check the easy ones first. - if (strcmp(address, "127.0.0.1") == 0 || strncmp(address, "10.", 3) == 0 || strncmp(address, "192.168", 7) == 0) { + if (strcmp(address, "127.0.0.1") == 0 || strncmp(address, "10.", 3) == 0 || strncmp(address, "192.168", 7) == 0 || + strncmp(address, "169.254", 7) == 0) { return true; } diff --git a/userPrefs.h b/userPrefs.h index ed622bcfb..58a44fef0 100644 --- a/userPrefs.h +++ b/userPrefs.h @@ -26,6 +26,8 @@ */ // #define USERPREFS_CHANNEL_0_NAME "DEFCONnect" // #define USERPREFS_CHANNEL_0_PRECISION 14 +// #define USERPREFS_CHANNEL_0_UPLINK_ENABLED true +// #define USERPREFS_CHANNEL_0_DOWNLINK_ENABLED true /* #define USERPREFS_CHANNEL_1_PSK \ { \ @@ -35,6 +37,8 @@ */ // #define USERPREFS_CHANNEL_1_NAME "REPLACEME" // #define USERPREFS_CHANNEL_1_PRECISION 14 +// #define USERPREFS_CHANNEL_1_UPLINK_ENABLED true +// #define USERPREFS_CHANNEL_1_DOWNLINK_ENABLED true /* #define USERPREFS_CHANNEL_2_PSK \ { \ @@ -44,6 +48,8 @@ */ // #define USERPREFS_CHANNEL_2_NAME "REPLACEME" // #define USERPREFS_CHANNEL_2_PRECISION 14 +// #define USERPREFS_CHANNEL_2_UPLINK_ENABLED true +// #define USERPREFS_CHANNEL_2_DOWNLINK_ENABLED true // #define USERPREFS_CONFIG_OWNER_LONG_NAME "My Long Name" // #define USERPREFS_CONFIG_OWNER_SHORT_NAME "MLN" diff --git a/variants/heltec_v2.1/platformio.ini b/variants/heltec_v2.1/platformio.ini index 5aa04fc58..ea2281911 100644 --- a/variants/heltec_v2.1/platformio.ini +++ b/variants/heltec_v2.1/platformio.ini @@ -1,4 +1,5 @@ [env:heltec-v2_1] +board_level = extra ;build_type = debug ; to make it possible to step through our jtag debugger extends = esp32_base board = heltec_wifi_lora_32_V2 diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index cee1537d0..c81bca8ba 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -1,5 +1,6 @@ [env:heltec-v2_0] -;build_type = debug ; to make it possible to step through our jtag debugger +;build_type = debug ; to make it possible to step through our jtag debugger +board_level = extra extends = esp32_base board = heltec_wifi_lora_32_V2 build_flags = diff --git a/variants/rp2040-lora/variant.h b/variants/rp2040-lora/variant.h index c93105f0e..f1826605f 100644 --- a/variants/rp2040-lora/variant.h +++ b/variants/rp2040-lora/variant.h @@ -54,7 +54,7 @@ #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY LORA_BUSY #define SX126X_RESET LORA_RESET -#define SX126X_DIO2_AS_RF_SWITCH // Antenna switch CTRL -#define SX126X_POWER_EN LORA_DIO4 // Antenna switch !CTRL via GPIO17 +#define SX126X_DIO2_AS_RF_SWITCH // Antenna switch CTRL +#define SX126X_RXEN LORA_DIO4 // Antenna switch !CTRL via GPIO17 // #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -#endif +#endif \ No newline at end of file diff --git a/variants/tlora_v1/platformio.ini b/variants/tlora_v1/platformio.ini index c90daed90..65ec4bcdc 100644 --- a/variants/tlora_v1/platformio.ini +++ b/variants/tlora_v1/platformio.ini @@ -1,4 +1,5 @@ [env:tlora-v1] +board_level = extra extends = esp32_base board = ttgo-lora32-v1 build_flags = diff --git a/variants/tlora_v1_3/platformio.ini b/variants/tlora_v1_3/platformio.ini index 9d9f41a7c..99df28e56 100644 --- a/variants/tlora_v1_3/platformio.ini +++ b/variants/tlora_v1_3/platformio.ini @@ -1,4 +1,5 @@ [env:tlora_v1_3] +board_level = extra extends = esp32_base board = ttgo-lora32-v1 build_flags = diff --git a/variants/tlora_v2/platformio.ini b/variants/tlora_v2/platformio.ini index 8710068af..8087a30e3 100644 --- a/variants/tlora_v2/platformio.ini +++ b/variants/tlora_v2/platformio.ini @@ -1,4 +1,5 @@ [env:tlora-v2] +board_level = extra extends = esp32_base board = ttgo-lora32-v1 build_flags = diff --git a/version.properties b/version.properties index be97dacb5..327f29443 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 5 -build = 7 +build = 8