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/protobufs b/protobufs
index 0fed5c770..7960241cc 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 0fed5c7709d2cec043a0f6daefa86ff11de0b946
+Subproject commit 7960241ccdd6b262a11b79523857037f755ab847
diff --git a/src/configuration.h b/src/configuration.h
index 8d0c99ff0..8643e922c 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
// -----------------------------------------------------------------------------
// SENSOR
diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp
index a9d70edaa..41ba8257e 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};
- return firstOfOrNONE(4, types);
+ ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004, MPR121KB};
+ return firstOfOrNONE(5, types);
}
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 07db3fd57..b2c482c98 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -61,7 +61,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 4640a7db9..de790f55b 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -7,7 +7,8 @@
#include "linux/LinuxHardwareI2C.h"
#endif
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
-#include "main.h" // atecc
+#include "main.h" // atecc
+#include "meshUtils.h" // vformat
#endif
// AXP192 and AXP2101 have the same device address, we just need to identify it in Power.cpp
@@ -243,6 +244,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");
#ifdef HAS_NCP5623
SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623 RGB LED found");
@@ -408,7 +410,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/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 59ae16866..7ad2eb2ac 100644
--- a/src/input/cardKbI2cImpl.cpp
+++ b/src/input/cardKbI2cImpl.cpp
@@ -12,8 +12,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
@@ -39,12 +39,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 07167cb3a..a4159e11a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -544,6 +544,10 @@ void setup()
// 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);
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/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 8da3009f2..3720b23a6 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -34,7 +34,11 @@
#endif
#include "modules/StoreForwardModule.h"
#include
+#include
+#include
#include
+#include
+#include
#endif
#ifdef ARCH_PORTDUINO
@@ -110,6 +114,43 @@ NodeDB::NodeDB()
uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile));
int saveWhat = 0;
+ bool hasUniqueId = false;
+ // Get device unique id
+#if defined(ARCH_ESP32) && defined(ESP_EFUSE_OPTIONAL_UNIQUE_ID)
+ uint32_t unique_id[4];
+ // ESP32 factory burns a unique id in efuse for S2+ series and evidently C3+ series
+ // This is used for HMACs in the esp-rainmaker AIOT platform and seems to be a good choice for us
+ esp_err_t err = esp_efuse_read_field_blob(ESP_EFUSE_OPTIONAL_UNIQUE_ID, unique_id, sizeof(unique_id) * 8);
+ if (err == ESP_OK) {
+ memcpy(myNodeInfo.device_id.bytes, unique_id, sizeof(unique_id));
+ myNodeInfo.device_id.size = 16;
+ hasUniqueId = true;
+ } else {
+ LOG_WARN("Failed to read unique id from efuse");
+ }
+#elif defined(ARCH_NRF52)
+ // Nordic applies a FIPS compliant Random ID to each chip at the factory
+ // We concatenate the device address to the Random ID to create a unique ID for now
+ // This will likely utilize a crypto module in the future
+ uint64_t device_id_start = ((uint64_t)NRF_FICR->DEVICEID[1] << 32) | NRF_FICR->DEVICEID[0];
+ uint64_t device_id_end = ((uint64_t)NRF_FICR->DEVICEADDR[1] << 32) | NRF_FICR->DEVICEADDR[0];
+ memcpy(myNodeInfo.device_id.bytes, &device_id_start, sizeof(device_id_start));
+ memcpy(myNodeInfo.device_id.bytes + sizeof(device_id_start), &device_id_end, sizeof(device_id_end));
+ myNodeInfo.device_id.size = 16;
+ hasUniqueId = true;
+#else
+ // FIXME - implement for other platforms
+#endif
+ // Uncomment below to print the device id
+ // if (hasUniqueId) {
+ // std::string deviceIdHex;
+ // for (size_t i = 0; i < myNodeInfo.device_id.size; ++i) {
+ // char buf[3];
+ // snprintf(buf, sizeof(buf), "%02X", myNodeInfo.device_id.bytes[i]);
+ // deviceIdHex += buf;
+ // }
+ // LOG_DEBUG("Device ID (HEX): %s", deviceIdHex.c_str());
+ // }
// likewise - we always want the app requirements to come from the running appload
myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00
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 97bf802c1..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. */
@@ -779,7 +780,7 @@ typedef struct _meshtastic_MyNodeInfo {
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 */
- uint64_t device_id;
+ meshtastic_MyNodeInfo_device_id_t device_id;
} meshtastic_MyNodeInfo;
/* Debug output from the device.
@@ -1114,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, 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}}
@@ -1139,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, 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}}
@@ -1450,7 +1451,7 @@ X(a, STATIC, SINGULAR, BOOL, is_favorite, 10)
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, UINT64, device_id, 12)
+X(a, STATIC, SINGULAR, BYTES, device_id, 12)
#define meshtastic_MyNodeInfo_CALLBACK NULL
#define meshtastic_MyNodeInfo_DEFAULT NULL
@@ -1673,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 29
+#define meshtastic_MyNodeInfo_size 36
#define meshtastic_NeighborInfo_size 258
#define meshtastic_Neighbor_size 22
#define meshtastic_NodeInfo_size 317
diff --git a/src/mesh/generated/meshtastic/rtttl.pb.h b/src/mesh/generated/meshtastic/rtttl.pb.h
index 2b7e35f11..0572265f7 100644
--- a/src/mesh/generated/meshtastic/rtttl.pb.h
+++ b/src/mesh/generated/meshtastic/rtttl.pb.h
@@ -13,7 +13,7 @@
/* Canned message module configuration. */
typedef struct _meshtastic_RTTTLConfig {
/* Ringtone for PWM Buzzer in RTTTL Format. */
- char ringtone[230];
+ char ringtone[231];
} meshtastic_RTTTLConfig;
@@ -41,7 +41,7 @@ extern const pb_msgdesc_t meshtastic_RTTTLConfig_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_RTTTL_PB_H_MAX_SIZE meshtastic_RTTTLConfig_size
-#define meshtastic_RTTTLConfig_size 232
+#define meshtastic_RTTTLConfig_size 233
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/motion/LIS3DHSensor.cpp b/src/motion/LIS3DHSensor.cpp
index 0d608670c..d06b46b50 100755
--- a/src/motion/LIS3DHSensor.cpp
+++ b/src/motion/LIS3DHSensor.cpp
@@ -1,4 +1,5 @@
#include "LIS3DHSensor.h"
+#include "NodeDB.h"
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
diff --git a/src/motion/LSM6DS3Sensor.cpp b/src/motion/LSM6DS3Sensor.cpp
index b3b1a13ec..3b25c3872 100755
--- a/src/motion/LSM6DS3Sensor.cpp
+++ b/src/motion/LSM6DS3Sensor.cpp
@@ -1,4 +1,5 @@
#include "LSM6DS3Sensor.h"
+#include "NodeDB.h"
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
diff --git a/src/motion/MotionSensor.h b/src/motion/MotionSensor.h
index 4da23cc70..78eec54ce 100755
--- a/src/motion/MotionSensor.h
+++ b/src/motion/MotionSensor.h
@@ -14,6 +14,7 @@
#include "../graphics/Screen.h"
#include "../graphics/ScreenFonts.h"
#include "../power.h"
+#include "Wire.h"
// Base class for motion processing
class MotionSensor
diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index 3ad397beb..2a2018395 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -746,7 +746,8 @@ bool MQTT::isPrivateIpAddress(const char address[])
}
// 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"