Merge branch 'develop' of https://github.com/meshtastic/firmware into 8139-root-topic

This commit is contained in:
ford-jones 2025-10-03 15:21:47 +13:00
commit 17863e96e2
51 changed files with 761 additions and 284 deletions

View File

@ -8,15 +8,15 @@ plugins:
uri: https://github.com/trunk-io/plugins uri: https://github.com/trunk-io/plugins
lint: lint:
enabled: enabled:
- checkov@3.2.471 - checkov@3.2.473
- renovate@41.130.1 - renovate@41.132.5
- prettier@3.6.2 - prettier@3.6.2
- trufflehog@3.90.8 - trufflehog@3.90.8
- yamllint@1.37.1 - yamllint@1.37.1
- bandit@1.8.6 - bandit@1.8.6
- trivy@0.66.0 - trivy@0.67.0
- taplo@0.10.0 - taplo@0.10.0
- ruff@0.13.1 - ruff@0.13.2
- isort@6.0.1 - isort@6.0.1
- markdownlint@0.45.0 - markdownlint@0.45.0
- oxipng@9.1.5 - oxipng@9.1.5

52
boards/r1-neo.json Normal file
View File

@ -0,0 +1,52 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "Muzi R1 Neo",
"mcu": "nrf52840",
"variant": "r1-neo",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino", "freertos"],
"name": "WisCore RAK4631 Board",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://muzi.works/",
"vendor": "Muzi Works"
}

@ -1 +1 @@
Subproject commit 394268b02ebbc7797de31b09fe72fe2a7bdbbcab Subproject commit 60c3e6600a2f4e6f49e45aeb47aafd8291a0015c

View File

@ -11,6 +11,11 @@
#include <AudioOutputI2S.h> #include <AudioOutputI2S.h>
#include <ESP8266SAM.h> #include <ESP8266SAM.h>
#ifdef USE_XL9555
#include "ExtensionIOXL9555.hpp"
extern ExtensionIOXL9555 io;
#endif
#define AUDIO_THREAD_INTERVAL_MS 100 #define AUDIO_THREAD_INTERVAL_MS 100
class AudioThread : public concurrency::OSThread class AudioThread : public concurrency::OSThread
@ -20,6 +25,9 @@ class AudioThread : public concurrency::OSThread
void beginRttl(const void *data, uint32_t len) void beginRttl(const void *data, uint32_t len)
{ {
#ifdef T_LORA_PAGER
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
#endif
setCPUFast(true); setCPUFast(true);
rtttlFile = new AudioFileSourcePROGMEM(data, len); rtttlFile = new AudioFileSourcePROGMEM(data, len);
i2sRtttl = new AudioGeneratorRTTTL(); i2sRtttl = new AudioGeneratorRTTTL();
@ -46,6 +54,9 @@ class AudioThread : public concurrency::OSThread
rtttlFile = nullptr; rtttlFile = nullptr;
setCPUFast(false); setCPUFast(false);
#ifdef T_LORA_PAGER
io.digitalWrite(EXPANDS_AMP_EN, LOW);
#endif
} }
void readAloud(const char *text) void readAloud(const char *text)
@ -56,10 +67,16 @@ class AudioThread : public concurrency::OSThread
i2sRtttl = nullptr; i2sRtttl = nullptr;
} }
#ifdef T_LORA_PAGER
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
#endif
ESP8266SAM *sam = new ESP8266SAM; ESP8266SAM *sam = new ESP8266SAM;
sam->Say(audioOut, text); sam->Say(audioOut, text);
delete sam; delete sam;
setCPUFast(false); setCPUFast(false);
#ifdef T_LORA_PAGER
io.digitalWrite(EXPANDS_AMP_EN, LOW);
#endif
} }
protected: protected:

View File

@ -76,9 +76,6 @@ const char *DisplayFormatters::getDeviceRole(meshtastic_Config_DeviceConfig_Role
case meshtastic_Config_DeviceConfig_Role_ROUTER_LATE: case meshtastic_Config_DeviceConfig_Role_ROUTER_LATE:
return "Router Late"; return "Router Late";
break; break;
case meshtastic_Config_DeviceConfig_Role_REPEATER:
return "Repeater";
break;
default: default:
return "Unknown"; return "Unknown";
break; break;

View File

@ -25,8 +25,8 @@ ScanI2C::FoundDevice ScanI2C::firstScreen() const
ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::FoundDevice ScanI2C::firstRTC() const
{ {
ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563}; ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563, RTC_RX8130CE};
return firstOfOrNONE(2, types); return firstOfOrNONE(3, types);
} }
ScanI2C::FoundDevice ScanI2C::firstKeyboard() const ScanI2C::FoundDevice ScanI2C::firstKeyboard() const

View File

@ -14,6 +14,7 @@ class ScanI2C
SCREEN_ST7567, SCREEN_ST7567,
RTC_RV3028, RTC_RV3028,
RTC_PCF8563, RTC_PCF8563,
RTC_RX8130CE,
CARDKB, CARDKB,
TDECKKB, TDECKKB,
BBQ10KB, BBQ10KB,

View File

@ -197,6 +197,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#ifdef PCF8563_RTC #ifdef PCF8563_RTC
SCAN_SIMPLE_CASE(PCF8563_RTC, RTC_PCF8563, "PCF8563", (uint8_t)addr.address) SCAN_SIMPLE_CASE(PCF8563_RTC, RTC_PCF8563, "PCF8563", (uint8_t)addr.address)
#endif #endif
#ifdef RX8130CE_RTC
SCAN_SIMPLE_CASE(RX8130CE_RTC, RTC_RX8130CE, "RX8130CE", (uint8_t)addr.address)
#endif
case CARDKB_ADDR: case CARDKB_ADDR:
// Do we have the RAK14006 instead? // Do we have the RAK14006 instead?

View File

@ -1104,11 +1104,6 @@ int32_t GPS::runOnce()
publishUpdate(); publishUpdate();
} }
// Repeaters have no need for GPS
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
return disable();
}
if (whileActive()) { if (whileActive()) {
// if we have received valid NMEA claim we are connected // if we have received valid NMEA claim we are connected
setConnected(); setConnected();

View File

@ -109,6 +109,35 @@ RTCSetResult readFromRTC()
} }
return RTCSetResultSuccess; return RTCSetResultSuccess;
} }
#elif defined(RX8130CE_RTC)
if (rtc_found.address == RX8130CE_RTC) {
uint32_t now = millis();
ArtronShop_RX8130CE rtc(&Wire);
tm t;
if (rtc.getTime(&t)) {
tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0;
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
LOG_DEBUG("Read RTC time from RX8130CE getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900,
t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime;
}
#endif
if (currentQuality == RTCQualityNone) {
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
currentQuality = RTCQualityDevice;
}
return RTCSetResultSuccess;
}
}
#else #else
if (!gettimeofday(&tv, NULL)) { if (!gettimeofday(&tv, NULL)) {
uint32_t now = millis(); uint32_t now = millis();
@ -214,6 +243,17 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec, printableEpoch); t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
} }
#elif defined(RX8130CE_RTC)
if (rtc_found.address == RX8130CE_RTC) {
ArtronShop_RX8130CE rtc(&Wire);
tm *t = gmtime(&tv->tv_sec);
if (rtc.setTime(*t)) {
LOG_DEBUG("RX8130CE setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1,
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
} else {
LOG_WARN("Failed to set time for RX8130CE");
}
}
#elif defined(ARCH_ESP32) #elif defined(ARCH_ESP32)
settimeofday(tv, NULL); settimeofday(tv, NULL);
#endif #endif

View File

@ -4,6 +4,10 @@
#include "sys/time.h" #include "sys/time.h"
#include <Arduino.h> #include <Arduino.h>
#ifdef RX8130CE_RTC
#include <ArtronShop_RX8130CE.h>
#endif
enum RTCQuality { enum RTCQuality {
/// We haven't had our RTC set yet /// We haven't had our RTC set yet

View File

@ -100,7 +100,7 @@ namespace graphics
#define NUM_EXTRA_FRAMES 3 // text message and debug frame #define NUM_EXTRA_FRAMES 3 // text message and debug frame
// if defined a pixel will blink to show redraws // if defined a pixel will blink to show redraws
// #define SHOW_REDRAWS // #define SHOW_REDRAWS
#define ASCII_BELL '\x07'
// A text message frame + debug frame + all the node infos // A text message frame + debug frame + all the node infos
FrameCallback *normalFrames; FrameCallback *normalFrames;
static uint32_t targetFramerate = IDLE_FRAMERATE; static uint32_t targetFramerate = IDLE_FRAMERATE;
@ -1458,28 +1458,36 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
} }
// === Prepare banner content === // === Prepare banner content ===
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from); const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
const meshtastic_Channel channel =
channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex());
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr; const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes); const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
char banner[256]; char banner[256];
// Check for bell character in message to determine alert type
bool isAlert = false; bool isAlert = false;
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
if (msgRaw[i] == '\x07') {
isAlert = true;
break;
}
}
if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_bell_vibra ||
moduleConfig.external_notification.alert_bell_buzzer)
// Check for bell character to determine if this message is an alert
for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
if (msgRaw[i] == ASCII_BELL) {
isAlert = true;
break;
}
}
// Unlike generic messages, alerts (when enabled via the ext notif module) ignore any
// 'mute' preferences set to any specific node or channel.
if (isAlert) { if (isAlert) {
if (longName && longName[0]) { if (longName && longName[0]) {
snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName); snprintf(banner, sizeof(banner), "Alert Received from\n%s", longName);
} else { } else {
strcpy(banner, "Alert Received"); strcpy(banner, "Alert Received");
} }
} else { screen->showSimpleBanner(banner, 3000);
} else if (!channel.settings.mute) {
if (longName && longName[0]) { if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L) #if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message"); strcpy(banner, "New Message");
@ -1490,20 +1498,21 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
} else { } else {
strcpy(banner, "New Message"); strcpy(banner, "New Message");
} }
}
#if defined(M5STACK_UNITC6L) #if defined(M5STACK_UNITC6L)
screen->setOn(true); screen->setOn(true);
screen->showSimpleBanner(banner, 1500); screen->showSimpleBanner(banner, 1500);
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(isAlert && moduleConfig.external_notification.alert_bell_buzzer) || (!isBroadcast(packet->to) && isToUs(p))) { (isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
// Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either (!isBroadcast(packet->to) && isToUs(p))) {
// - packet contains an alert and alert bell buzzer is enabled // Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
// - packet is a non-broadcast that is addressed to this node // - packet contains an alert and alert bell buzzer is enabled
playLongBeep(); // - packet is a non-broadcast that is addressed to this node
} playLongBeep();
}
#else #else
screen->showSimpleBanner(banner, 3000); screen->showSimpleBanner(banner, 3000);
#endif #endif
}
} }
} }

View File

@ -279,7 +279,7 @@ int32_t ButtonThread::runOnce()
if (!userButton.isIdle() || waitingForLongPress) { if (!userButton.isIdle() || waitingForLongPress) {
return 50; return 50;
} }
return INT32_MAX; return 100; // FIXME: Why can't we rely on interrupts and use INT32_MAX here?
} }
/* /*

View File

@ -3,16 +3,66 @@
InputBroker *inputBroker = nullptr; InputBroker *inputBroker = nullptr;
InputBroker::InputBroker(){}; InputBroker::InputBroker()
{
#ifdef HAS_FREE_RTOS
inputEventQueue = xQueueCreate(5, sizeof(InputEvent));
pollSoonQueue = xQueueCreate(5, sizeof(InputPollable *));
xTaskCreate(pollSoonWorker, "input-pollSoon", 2 * 1024, this, 10, &pollSoonTask);
#endif
}
void InputBroker::registerSource(Observable<const InputEvent *> *source) void InputBroker::registerSource(Observable<const InputEvent *> *source)
{ {
this->inputEventObserver.observe(source); this->inputEventObserver.observe(source);
} }
#ifdef HAS_FREE_RTOS
void InputBroker::requestPollSoon(InputPollable *pollable)
{
if (xPortInIsrContext() == pdTRUE) {
xQueueSendFromISR(pollSoonQueue, &pollable, NULL);
} else {
xQueueSend(pollSoonQueue, &pollable, 0);
}
}
void InputBroker::queueInputEvent(const InputEvent *event)
{
if (xPortInIsrContext() == pdTRUE) {
xQueueSendFromISR(inputEventQueue, event, NULL);
} else {
xQueueSend(inputEventQueue, event, portMAX_DELAY);
}
}
void InputBroker::processInputEventQueue()
{
InputEvent event;
while (xQueueReceive(inputEventQueue, &event, 0)) {
handleInputEvent(&event);
}
}
#endif
int InputBroker::handleInputEvent(const InputEvent *event) int InputBroker::handleInputEvent(const InputEvent *event)
{ {
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
this->notifyObservers(event); this->notifyObservers(event);
return 0; return 0;
} }
#ifdef HAS_FREE_RTOS
void InputBroker::pollSoonWorker(void *p)
{
InputBroker *instance = (InputBroker *)p;
while (true) {
InputPollable *pollable = NULL;
xQueueReceive(instance->pollSoonQueue, &pollable, portMAX_DELAY);
if (pollable) {
pollable->pollOnce();
}
}
vTaskDelete(NULL);
}
#endif

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "Observer.h" #include "Observer.h"
#include "freertosinc.h"
enum input_broker_event { enum input_broker_event {
INPUT_BROKER_NONE = 0, INPUT_BROKER_NONE = 0,
@ -41,6 +43,13 @@ typedef struct _InputEvent {
uint16_t touchX; uint16_t touchX;
uint16_t touchY; uint16_t touchY;
} InputEvent; } InputEvent;
class InputPollable
{
public:
virtual void pollOnce() = 0;
};
class InputBroker : public Observable<const InputEvent *> class InputBroker : public Observable<const InputEvent *>
{ {
CallbackObserver<InputBroker, const InputEvent *> inputEventObserver = CallbackObserver<InputBroker, const InputEvent *> inputEventObserver =
@ -50,9 +59,22 @@ class InputBroker : public Observable<const InputEvent *>
InputBroker(); InputBroker();
void registerSource(Observable<const InputEvent *> *source); void registerSource(Observable<const InputEvent *> *source);
void injectInputEvent(const InputEvent *event) { handleInputEvent(event); } void injectInputEvent(const InputEvent *event) { handleInputEvent(event); }
#ifdef HAS_FREE_RTOS
void requestPollSoon(InputPollable *pollable);
void queueInputEvent(const InputEvent *event);
void processInputEventQueue();
#endif
protected: protected:
int handleInputEvent(const InputEvent *event); int handleInputEvent(const InputEvent *event);
private:
#ifdef HAS_FREE_RTOS
QueueHandle_t inputEventQueue;
QueueHandle_t pollSoonQueue;
TaskHandle_t pollSoonTask;
static void pollSoonWorker(void *p);
#endif
}; };
extern InputBroker *inputBroker; extern InputBroker *inputBroker;

View File

@ -8,7 +8,7 @@
RotaryEncoderImpl *rotaryEncoderImpl; RotaryEncoderImpl *rotaryEncoderImpl;
RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME) RotaryEncoderImpl::RotaryEncoderImpl()
{ {
rotary = nullptr; rotary = nullptr;
} }
@ -18,7 +18,6 @@ bool RotaryEncoderImpl::init()
if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 || if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 ||
moduleConfig.canned_message.inputbroker_pin_b == 0) { moduleConfig.canned_message.inputbroker_pin_b == 0) {
// Input device is disabled. // Input device is disabled.
disable();
return false; return false;
} }
@ -30,7 +29,11 @@ bool RotaryEncoderImpl::init()
moduleConfig.canned_message.inputbroker_pin_press); moduleConfig.canned_message.inputbroker_pin_press);
rotary->resetButton(); rotary->resetButton();
inputBroker->registerSource(this); interruptInstance = this;
auto interruptHandler = []() { inputBroker->requestPollSoon(interruptInstance); };
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_a, interruptHandler, CHANGE);
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_b, interruptHandler, CHANGE);
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_press, interruptHandler, CHANGE);
LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a, LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw, moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw,
@ -38,36 +41,36 @@ bool RotaryEncoderImpl::init()
return true; return true;
} }
int32_t RotaryEncoderImpl::runOnce() void RotaryEncoderImpl::pollOnce()
{ {
InputEvent e{originName, INPUT_BROKER_NONE, 0, 0, 0}; InputEvent e{ORIGIN_NAME, INPUT_BROKER_NONE, 0, 0, 0};
static uint32_t lastPressed = millis(); static uint32_t lastPressed = millis();
if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) { if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) {
if (lastPressed + 200 < millis()) { if (lastPressed + 200 < millis()) {
LOG_DEBUG("Rotary event Press"); LOG_DEBUG("Rotary event Press");
lastPressed = millis(); lastPressed = millis();
e.inputEvent = this->eventPressed; e.inputEvent = this->eventPressed;
} inputBroker->queueInputEvent(&e);
} else {
switch (rotary->process()) {
case RotaryEncoder::DIRECTION_CW:
LOG_DEBUG("Rotary event CW");
e.inputEvent = this->eventCw;
break;
case RotaryEncoder::DIRECTION_CCW:
LOG_DEBUG("Rotary event CCW");
e.inputEvent = this->eventCcw;
break;
default:
break;
} }
} }
if (e.inputEvent != INPUT_BROKER_NONE) { switch (rotary->process()) {
this->notifyObservers(&e); case RotaryEncoder::DIRECTION_CW:
LOG_DEBUG("Rotary event CW");
e.inputEvent = this->eventCw;
inputBroker->queueInputEvent(&e);
break;
case RotaryEncoder::DIRECTION_CCW:
LOG_DEBUG("Rotary event CCW");
e.inputEvent = this->eventCcw;
inputBroker->queueInputEvent(&e);
break;
default:
break;
} }
return 10;
} }
RotaryEncoderImpl *RotaryEncoderImpl::interruptInstance;
#endif #endif

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
// This is a non-interrupt version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library) // This is a version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
#include "InputBroker.h" #include "InputBroker.h"
#include "concurrency/OSThread.h" #include "concurrency/OSThread.h"
@ -8,21 +8,21 @@
class RotaryEncoder; class RotaryEncoder;
class RotaryEncoderImpl : public Observable<const InputEvent *>, public concurrency::OSThread class RotaryEncoderImpl : public InputPollable
{ {
public: public:
RotaryEncoderImpl(); RotaryEncoderImpl();
bool init(void); bool init(void);
virtual void pollOnce() override;
protected: protected:
virtual int32_t runOnce() override; static RotaryEncoderImpl *interruptInstance;
input_broker_event eventCw = INPUT_BROKER_NONE; input_broker_event eventCw = INPUT_BROKER_NONE;
input_broker_event eventCcw = INPUT_BROKER_NONE; input_broker_event eventCcw = INPUT_BROKER_NONE;
input_broker_event eventPressed = INPUT_BROKER_NONE; input_broker_event eventPressed = INPUT_BROKER_NONE;
RotaryEncoder *rotary; RotaryEncoder *rotary;
const char *originName;
}; };
extern RotaryEncoderImpl *rotaryEncoderImpl; extern RotaryEncoderImpl *rotaryEncoderImpl;

View File

@ -297,6 +297,12 @@ void printInfo()
#ifndef PIO_UNIT_TESTING #ifndef PIO_UNIT_TESTING
void setup() void setup()
{ {
#if defined(R1_NEO)
pinMode(DCDC_EN_HOLD, OUTPUT);
digitalWrite(DCDC_EN_HOLD, HIGH);
pinMode(NRF_ON, OUTPUT);
digitalWrite(NRF_ON, HIGH);
#endif
#if defined(PIN_POWER_EN) #if defined(PIN_POWER_EN)
pinMode(PIN_POWER_EN, OUTPUT); pinMode(PIN_POWER_EN, OUTPUT);
@ -375,7 +381,7 @@ void setup()
io.pinMode(EXPANDS_DRV_EN, OUTPUT); io.pinMode(EXPANDS_DRV_EN, OUTPUT);
io.digitalWrite(EXPANDS_DRV_EN, HIGH); io.digitalWrite(EXPANDS_DRV_EN, HIGH);
io.pinMode(EXPANDS_AMP_EN, OUTPUT); io.pinMode(EXPANDS_AMP_EN, OUTPUT);
io.digitalWrite(EXPANDS_AMP_EN, HIGH); io.digitalWrite(EXPANDS_AMP_EN, LOW);
io.pinMode(EXPANDS_LORA_EN, OUTPUT); io.pinMode(EXPANDS_LORA_EN, OUTPUT);
io.digitalWrite(EXPANDS_LORA_EN, HIGH); io.digitalWrite(EXPANDS_LORA_EN, HIGH);
io.pinMode(EXPANDS_GPS_EN, OUTPUT); io.pinMode(EXPANDS_GPS_EN, OUTPUT);
@ -793,14 +799,7 @@ void setup()
} }
#endif #endif
// If we're taking on the repeater role, use NextHopRouter and turn off 3V3_S rail because peripherals are not needed router = new ReliableRouter();
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
router = new NextHopRouter();
#ifdef PIN_3V3_EN
digitalWrite(PIN_3V3_EN, LOW);
#endif
} else
router = new ReliableRouter();
// only play start melody when role is not tracker or sensor // only play start melody when role is not tracker or sensor
if (config.power.is_power_saving == true && if (config.power.is_power_saving == true &&
@ -926,8 +925,7 @@ void setup()
if (sensor_detected == false) { if (sensor_detected == false) {
#endif #endif
if (HAS_GPS) { if (HAS_GPS) {
if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER && if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
gps = GPS::createGps(); gps = GPS::createGps();
if (gps) { if (gps) {
gpsStatus->observe(&gps->newStatus); gpsStatus->observe(&gps->newStatus);
@ -1602,6 +1600,9 @@ void loop()
#endif #endif
service->loop(); service->loop();
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS)
inputBroker->processInputEventQueue();
#endif
#if defined(LGFX_SDL) #if defined(LGFX_SDL)
if (screen) { if (screen) {
auto dispdev = screen->getDisplayDevice(); auto dispdev = screen->getDisplayDevice();

View File

@ -85,9 +85,8 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p) bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
{ {
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) { config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
// ROUTER, REPEATER, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast), // ROUTER, ROUTER_LATE should never cancel relaying a packet (i.e. we should always rebroadcast),
// even if we've heard another station rebroadcast it already. // even if we've heard another station rebroadcast it already.
return false; return false;
} }
@ -106,7 +105,7 @@ bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p)
void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p) void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
{ {
if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA && roleAllowsCancelingDupe(p)) { if (p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_LORA && roleAllowsCancelingDupe(p)) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater! // cancel rebroadcast of this message *if* there was already one, unless we're a router!
// But only LoRa packets should be able to trigger this. // But only LoRa packets should be able to trigger this.
if (Router::cancelSending(p->from, p->id)) if (Router::cancelSending(p->from, p->id))
txRelayCanceled++; txRelayCanceled++;

View File

@ -59,7 +59,7 @@ class FloodingRouter : public Router
*/ */
virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override; virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override;
// Return false for roles like ROUTER or REPEATER which should always rebroadcast even when we've heard another rebroadcast of // Return false for roles like ROUTER which should always rebroadcast even when we've heard another rebroadcast of
// the same packet // the same packet
bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p); bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p);

View File

@ -85,12 +85,11 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping powerFSM.trigger(EVENT_PACKET_FOR_PHONE); // Possibly keep the node from sleeping
nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
bool isPreferredRebroadcaster = bool isPreferredRebroadcaster = config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER;
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_REPEATER);
if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) { mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo"); // because this potentially a Repeater which will LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo");
// ignore our request for its NodeInfo // ignore our request for its NodeInfo
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user && } else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
nodeInfoModule && !isPreferredRebroadcaster && !nodeDB->isFull()) { nodeInfoModule && !isPreferredRebroadcaster && !nodeDB->isFull()) {
if (airTime->isTxAllowedChannelUtil(true)) { if (airTime->isTxAllowedChannelUtil(true)) {

View File

@ -174,7 +174,6 @@ bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p)
*/ */
uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node) uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
{ {
// When we're a repeater router->sniffReceived will call NextHopRouter directly without checking for broadcast
if (isBroadcast(to)) if (isBroadcast(to))
return NO_NEXT_HOP_PREFERENCE; return NO_NEXT_HOP_PREFERENCE;
@ -212,7 +211,7 @@ bool NextHopRouter::roleAllowsCancelingFromTxQueue(const meshtastic_MeshPacket *
{ {
// Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once) // Return true if we're allowed to cancel a packet in the txQueue (so we may never transmit it even once)
// Return false for roles like ROUTER, REPEATER, ROUTER_LATE which should always transmit the packet at least once. // Return false for roles like ROUTER, ROUTER_LATE which should always transmit the packet at least once.
return roleAllowsCancelingDupe(p); // same logic as FloodingRouter::roleAllowsCancelingDupe return roleAllowsCancelingDupe(p); // same logic as FloodingRouter::roleAllowsCancelingDupe
} }
@ -225,7 +224,7 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
/* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue /* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
to avoid canceling a transmission if it was ACKed super fast via MQTT */ to avoid canceling a transmission if it was ACKed super fast via MQTT */
if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) { if (old->numRetransmissions < NUM_RELIABLE_RETX - 1) {
// We only cancel it if we are the original sender or if we're not a router(_late)/repeater // We only cancel it if we are the original sender or if we're not a router(_late)
if (isFromUs(p) || roleAllowsCancelingFromTxQueue(p)) { if (isFromUs(p) || roleAllowsCancelingFromTxQueue(p)) {
// remove the 'original' (identified by originator and packet->id) from the txqueue and free it // remove the 'original' (identified by originator and packet->id) from the txqueue and free it
cancelSending(getFrom(p), p->id); cancelSending(getFrom(p), p->id);

View File

@ -554,10 +554,9 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
#endif #endif
#ifdef USERPREFS_CONFIG_DEVICE_ROLE #ifdef USERPREFS_CONFIG_DEVICE_ROLE
// Restrict ROUTER*, LOST AND FOUND, and REPEATER roles for security reasons // Restrict ROUTER*, LOST AND FOUND roles for security reasons
if (IS_ONE_OF(USERPREFS_CONFIG_DEVICE_ROLE, meshtastic_Config_DeviceConfig_Role_ROUTER, if (IS_ONE_OF(USERPREFS_CONFIG_DEVICE_ROLE, meshtastic_Config_DeviceConfig_Role_ROUTER,
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_REPEATER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND)) {
meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND)) {
LOG_WARN("ROUTER roles are restricted, falling back to CLIENT role"); LOG_WARN("ROUTER roles are restricted, falling back to CLIENT role");
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT; config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
} else { } else {
@ -906,11 +905,6 @@ void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
moduleConfig.telemetry.device_update_interval = ONE_DAY; moduleConfig.telemetry.device_update_interval = ONE_DAY;
owner.has_is_unmessagable = true; owner.has_is_unmessagable = true;
owner.is_unmessagable = true; owner.is_unmessagable = true;
} else if (role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
owner.has_is_unmessagable = true;
owner.is_unmessagable = true;
config.display.screen_on_secs = 1;
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY;
} else if (role == meshtastic_Config_DeviceConfig_Role_SENSOR) { } else if (role == meshtastic_Config_DeviceConfig_Role_SENSOR) {
owner.has_is_unmessagable = true; owner.has_is_unmessagable = true;
owner.is_unmessagable = true; owner.is_unmessagable = true;
@ -1603,9 +1597,18 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
void NodeDB::addFromContact(meshtastic_SharedContact contact) void NodeDB::addFromContact(meshtastic_SharedContact contact)
{ {
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num); meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num);
if (!info) { if (!info || !contact.has_user) {
return; return;
} }
// If the local node has this node marked as manually verified
// and the client does not, do not allow the client to update the
// saved public key.
if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) {
if (contact.user.public_key.size != info->user.public_key.size ||
memcmp(contact.user.public_key.bytes, info->user.public_key.bytes, info->user.public_key.size) != 0) {
return;
}
}
info->num = contact.node_num; info->num = contact.node_num;
info->has_user = true; info->has_user = true;
info->user = TypeConversions::ConvertToUserLite(contact.user); info->user = TypeConversions::ConvertToUserLite(contact.user);
@ -1620,10 +1623,12 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
} else { } else {
info->last_heard = getValidTime(RTCQualityNTP); info->last_heard = getValidTime(RTCQualityNTP);
info->is_favorite = true; info->is_favorite = true;
info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK; // As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified
if (contact.manually_verified) {
info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
}
// Mark the node's key as manually verified to indicate trustworthiness. // Mark the node's key as manually verified to indicate trustworthiness.
updateGUIforNode = info; updateGUIforNode = info;
// powerFSM.trigger(EVENT_NODEDB_UPDATED); This event has been retired
sortMeshDB(); sortMeshDB();
notifyObservers(true); // Force an update whether or not our node counts have changed notifyObservers(true); // Force an update whether or not our node counts have changed
} }

View File

@ -710,6 +710,13 @@ bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
// sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Text messages can only be sent once every 2 seconds"); // sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Text messages can only be sent once every 2 seconds");
return false; return false;
} }
// Upgrade traceroute requests from phone to use reliable delivery, matching TraceRouteModule
if (p.decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP && !isBroadcast(p.to)) {
// Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo)
p.want_ack = true;
}
lastPortNumToRadio[p.decoded.portnum] = millis(); lastPortNumToRadio[p.decoded.portnum] = millis();
service->handleToRadio(p); service->handleToRadio(p);
return true; return true;

View File

@ -317,9 +317,8 @@ uint32_t RadioInterface::getTxDelayMsecWeightedWorst(float snr)
/** Returns true if we should rebroadcast early like a ROUTER */ /** Returns true if we should rebroadcast early like a ROUTER */
bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p) bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
{ {
// If we are a ROUTER or REPEATER, we always rebroadcast early // If we are a ROUTER, we always rebroadcast early
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
return true; return true;
} }

View File

@ -103,10 +103,20 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
/* A response may be set to want_ack for retransmissions, but we don't need to ACK a response if it received /* A response may be set to want_ack for retransmissions, but we don't need to ACK a response if it received
an implicit ACK already. If we received it directly or via NextHopRouter, only ACK with a hop limit of 0 to an implicit ACK already. If we received it directly or via NextHopRouter, only ACK with a hop limit of 0 to
make sure the other side stops retransmitting. */ make sure the other side stops retransmitting. */
if (!p->decoded.request_id && !p->decoded.reply_id) {
if (shouldSuccessAckWithWantAck(p)) {
// If this packet should always be ACKed reliably with want_ack back to the original sender, make sure we
// do that unconditionally.
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit), true);
} else if (!p->decoded.request_id && !p->decoded.reply_id) {
// If it's not an ACK or a reply, send an ACK.
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit)); routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
} else if ((p->hop_start > 0 && p->hop_start == p->hop_limit) || p->next_hop != NO_NEXT_HOP_PREFERENCE) { } else if ((p->hop_start > 0 && p->hop_start == p->hop_limit) || p->next_hop != NO_NEXT_HOP_PREFERENCE) {
// If we received the packet directly from the original sender, send a 0-hop ACK since the original sender
// won't overhear any implicit ACKs. If we received the packet via NextHopRouter, also send a 0-hop ACK to
// stop the immediate relayer's retransmissions.
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0); sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
} }
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 && } else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
@ -152,4 +162,36 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
// handle the packet as normal // handle the packet as normal
isBroadcast(p->to) ? FloodingRouter::sniffReceived(p, c) : NextHopRouter::sniffReceived(p, c); isBroadcast(p->to) ? FloodingRouter::sniffReceived(p, c) : NextHopRouter::sniffReceived(p, c);
}
/**
* If we ACK this packet, should we set want_ack=true on the ACK for reliable delivery of the ACK packet?
*/
bool ReliableRouter::shouldSuccessAckWithWantAck(const meshtastic_MeshPacket *p)
{
// Don't ACK-with-want-ACK outgoing packets
if (isFromUs(p))
return false;
// Only ACK-with-want-ACK if the original packet asked for want_ack
if (!p->want_ack)
return false;
// Only ACK-with-want-ACK packets to us (not broadcast)
if (!isToUs(p))
return false;
// Special case for text message DMs:
bool isTextMessage =
(p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) &&
IS_ONE_OF(p->decoded.portnum, meshtastic_PortNum_TEXT_MESSAGE_APP, meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP);
if (isTextMessage) {
// If it's a non-broadcast text message, and the original asked for want_ack,
// let's send an ACK that is itself want_ack to improve reliability of confirming delivery back to the sender.
// This should include all DMs regardless of whether or not reply_id is set.
return true;
}
return false;
} }

View File

@ -31,4 +31,10 @@ class ReliableRouter : public NextHopRouter
* We hook this method so we can see packets before FloodingRouter says they should be discarded * We hook this method so we can see packets before FloodingRouter says they should be discarded
*/ */
virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override; virtual bool shouldFilterReceived(const meshtastic_MeshPacket *p) override;
private:
/**
* Should this packet be ACKed with a want_ack for reliable delivery?
*/
bool shouldSuccessAckWithWantAck(const meshtastic_MeshPacket *p);
}; };

View File

@ -198,9 +198,10 @@ meshtastic_MeshPacket *Router::allocForSending()
/** /**
* Send an ack or a nak packet back towards whoever sent idFrom * Send an ack or a nak packet back towards whoever sent idFrom
*/ */
void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit) void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit,
bool ackWantsAck)
{ {
routingModule->sendAckNak(err, to, idFrom, chIndex, hopLimit); routingModule->sendAckNak(err, to, idFrom, chIndex, hopLimit, ackWantsAck);
} }
void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket *p) void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket *p)
@ -399,10 +400,6 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
{ {
concurrency::LockGuard g(cryptLock); concurrency::LockGuard g(cryptLock);
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_ALL_SKIP_DECODING)
return DecodeState::DECODE_FAILURE;
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY && if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY &&
(nodeDB->getMeshNode(p->from) == NULL || !nodeDB->getMeshNode(p->from)->has_user)) { (nodeDB->getMeshNode(p->from) == NULL || !nodeDB->getMeshNode(p->from)->has_user)) {
LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet", p->from); LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet", p->from);

View File

@ -125,7 +125,8 @@ class Router : protected concurrency::OSThread, protected PacketHistory
/** /**
* Send an ack or a nak packet back towards whoever sent idFrom * Send an ack or a nak packet back towards whoever sent idFrom
*/ */
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0); void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0,
bool ackWantsAck = false);
private: private:
/** /**

View File

@ -132,6 +132,8 @@ typedef struct _meshtastic_SharedContact {
meshtastic_User user; meshtastic_User user;
/* Add this contact to the blocked / ignored list */ /* Add this contact to the blocked / ignored list */
bool should_ignore; bool should_ignore;
/* Set the IS_KEY_MANUALLY_VERIFIED bit */
bool manually_verified;
} meshtastic_SharedContact; } meshtastic_SharedContact;
/* This message is used by a client to initiate or complete a key verification */ /* This message is used by a client to initiate or complete a key verification */
@ -319,13 +321,13 @@ extern "C" {
#define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0} #define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
#define meshtastic_HamParameters_init_default {"", 0, 0, ""} #define meshtastic_HamParameters_init_default {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}} #define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0} #define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0}
#define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} #define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
#define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}} #define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0} #define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
#define meshtastic_HamParameters_init_zero {"", 0, 0, ""} #define meshtastic_HamParameters_init_zero {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}} #define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0} #define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0}
#define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0} #define meshtastic_KeyVerificationAdmin_init_zero {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
/* Field tags (for use in manual encoding/decoding) */ /* Field tags (for use in manual encoding/decoding) */
@ -341,6 +343,7 @@ extern "C" {
#define meshtastic_SharedContact_node_num_tag 1 #define meshtastic_SharedContact_node_num_tag 1
#define meshtastic_SharedContact_user_tag 2 #define meshtastic_SharedContact_user_tag 2
#define meshtastic_SharedContact_should_ignore_tag 3 #define meshtastic_SharedContact_should_ignore_tag 3
#define meshtastic_SharedContact_manually_verified_tag 4
#define meshtastic_KeyVerificationAdmin_message_type_tag 1 #define meshtastic_KeyVerificationAdmin_message_type_tag 1
#define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2 #define meshtastic_KeyVerificationAdmin_remote_nodenum_tag 2
#define meshtastic_KeyVerificationAdmin_nonce_tag 3 #define meshtastic_KeyVerificationAdmin_nonce_tag 3
@ -504,7 +507,8 @@ X(a, STATIC, REPEATED, MESSAGE, node_remote_hardware_pins, 1)
#define meshtastic_SharedContact_FIELDLIST(X, a) \ #define meshtastic_SharedContact_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, node_num, 1) \ X(a, STATIC, SINGULAR, UINT32, node_num, 1) \
X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \
X(a, STATIC, SINGULAR, BOOL, should_ignore, 3) X(a, STATIC, SINGULAR, BOOL, should_ignore, 3) \
X(a, STATIC, SINGULAR, BOOL, manually_verified, 4)
#define meshtastic_SharedContact_CALLBACK NULL #define meshtastic_SharedContact_CALLBACK NULL
#define meshtastic_SharedContact_DEFAULT NULL #define meshtastic_SharedContact_DEFAULT NULL
#define meshtastic_SharedContact_user_MSGTYPE meshtastic_User #define meshtastic_SharedContact_user_MSGTYPE meshtastic_User
@ -539,7 +543,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
#define meshtastic_HamParameters_size 31 #define meshtastic_HamParameters_size 31
#define meshtastic_KeyVerificationAdmin_size 25 #define meshtastic_KeyVerificationAdmin_size 25
#define meshtastic_NodeRemoteHardwarePinsResponse_size 496 #define meshtastic_NodeRemoteHardwarePinsResponse_size 496
#define meshtastic_SharedContact_size 125 #define meshtastic_SharedContact_size 127
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */

View File

@ -611,10 +611,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
} }
config.device = c.payload_variant.device; config.device = c.payload_variant.device;
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_NONE && if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_NONE &&
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER, config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
meshtastic_Config_DeviceConfig_Role_REPEATER)) {
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL; config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL;
const char *warning = "Rebroadcast mode can't be set to NONE for a router or repeater"; const char *warning = "Rebroadcast mode can't be set to NONE for a router";
LOG_WARN(warning); LOG_WARN(warning);
sendWarning(warning); sendWarning(warning);
} }
@ -627,8 +626,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d", min_node_info_broadcast_secs); LOG_DEBUG("Tried to set node_info_broadcast_secs too low, setting to %d", min_node_info_broadcast_secs);
config.device.node_info_broadcast_secs = min_node_info_broadcast_secs; config.device.node_info_broadcast_secs = min_node_info_broadcast_secs;
} }
// Router Client is deprecated; Set it to client // Router Client and Repeater deprecated; Set it to client
if (c.payload_variant.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT) { if (IS_ONE_OF(c.payload_variant.device.role, meshtastic_Config_DeviceConfig_Role_ROUTER_CLIENT,
meshtastic_Config_DeviceConfig_Role_REPEATER)) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT; config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
if (moduleConfig.store_forward.enabled && !moduleConfig.store_forward.is_server) { if (moduleConfig.store_forward.enabled && !moduleConfig.store_forward.is_server) {
moduleConfig.store_forward.is_server = true; moduleConfig.store_forward.is_server = true;
@ -637,10 +637,9 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
} }
} }
#if USERPREFS_EVENT_MODE #if USERPREFS_EVENT_MODE
// If we're in event mode, nobody is a Router or Repeater // If we're in event mode, nobody is a Router or Router Late
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE || config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT; config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
} }
#endif #endif
@ -707,20 +706,40 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
#endif #endif
config.display = c.payload_variant.display; config.display = c.payload_variant.display;
break; break;
case meshtastic_Config_lora_tag:
case meshtastic_Config_lora_tag: {
// Wrap the entire case in a block to scope variables and avoid crossing initialization
auto oldLoraConfig = config.lora;
auto validatedLora = c.payload_variant.lora;
LOG_INFO("Set config: LoRa"); LOG_INFO("Set config: LoRa");
config.has_lora = true; config.has_lora = true;
if (validatedLora.coding_rate < 4 || validatedLora.coding_rate > 8) {
LOG_WARN("Invalid coding_rate %d, setting to 5", validatedLora.coding_rate);
validatedLora.coding_rate = 5;
}
if (validatedLora.spread_factor < 7 || validatedLora.spread_factor > 12) {
LOG_WARN("Invalid spread_factor %d, setting to 11", validatedLora.spread_factor);
validatedLora.spread_factor = 11;
}
if (validatedLora.bandwidth == 0) {
int originalBandwidth = validatedLora.bandwidth;
validatedLora.bandwidth = myRegion->wideLora ? 800 : 250;
LOG_WARN("Invalid bandwidth %d, setting to default", originalBandwidth);
}
// If no lora radio parameters change, don't need to reboot // If no lora radio parameters change, don't need to reboot
if (config.lora.use_preset == c.payload_variant.lora.use_preset && config.lora.region == c.payload_variant.lora.region && if (oldLoraConfig.use_preset == validatedLora.use_preset && oldLoraConfig.region == validatedLora.region &&
config.lora.modem_preset == c.payload_variant.lora.modem_preset && oldLoraConfig.modem_preset == validatedLora.modem_preset && oldLoraConfig.bandwidth == validatedLora.bandwidth &&
config.lora.bandwidth == c.payload_variant.lora.bandwidth && oldLoraConfig.spread_factor == validatedLora.spread_factor &&
config.lora.spread_factor == c.payload_variant.lora.spread_factor && oldLoraConfig.coding_rate == validatedLora.coding_rate && oldLoraConfig.tx_power == validatedLora.tx_power &&
config.lora.coding_rate == c.payload_variant.lora.coding_rate && oldLoraConfig.frequency_offset == validatedLora.frequency_offset &&
config.lora.tx_power == c.payload_variant.lora.tx_power && oldLoraConfig.override_frequency == validatedLora.override_frequency &&
config.lora.frequency_offset == c.payload_variant.lora.frequency_offset && oldLoraConfig.channel_num == validatedLora.channel_num &&
config.lora.override_frequency == c.payload_variant.lora.override_frequency && oldLoraConfig.sx126x_rx_boosted_gain == validatedLora.sx126x_rx_boosted_gain) {
config.lora.channel_num == c.payload_variant.lora.channel_num &&
config.lora.sx126x_rx_boosted_gain == c.payload_variant.lora.sx126x_rx_boosted_gain) {
requiresReboot = false; requiresReboot = false;
} }
@ -739,7 +758,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
digitalWrite(RF95_FAN_EN, HIGH ^ 0); digitalWrite(RF95_FAN_EN, HIGH ^ 0);
} }
#endif #endif
config.lora = c.payload_variant.lora; config.lora = validatedLora;
// If we're setting region for the first time, init the region and regenerate the keys // If we're setting region for the first time, init the region and regenerate the keys
if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) { if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
if (!owner.is_licensed) { if (!owner.is_licensed) {
@ -784,6 +803,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG; changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
} }
break; break;
}
case meshtastic_Config_bluetooth_tag: case meshtastic_Config_bluetooth_tag:
LOG_INFO("Set config: Bluetooth"); LOG_INFO("Set config: Bluetooth");
config.has_bluetooth = true; config.has_bluetooth = true;

View File

@ -442,7 +442,7 @@ ExternalNotificationModule::ExternalNotificationModule()
ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp) ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshPacket &mp)
{ {
if (moduleConfig.external_notification.enabled && !isMuted) { if (moduleConfig.external_notification.enabled && !isSilenced) {
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
drv.setWaveform(0, 75); drv.setWaveform(0, 75);
drv.setWaveform(1, 56); drv.setWaveform(1, 56);
@ -453,12 +453,13 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
// Check if the message contains a bell character. Don't do this loop for every pin, just once. // Check if the message contains a bell character. Don't do this loop for every pin, just once.
auto &p = mp.decoded; auto &p = mp.decoded;
bool containsBell = false; bool containsBell = false;
for (int i = 0; i < p.payload.size; i++) { for (size_t i = 0; i < p.payload.size; i++) {
if (p.payload.bytes[i] == ASCII_BELL) { if (p.payload.bytes[i] == ASCII_BELL) {
containsBell = true; containsBell = true;
} }
} }
meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex());
if (moduleConfig.external_notification.alert_bell) { if (moduleConfig.external_notification.alert_bell) {
if (containsBell) { if (containsBell) {
LOG_INFO("externalNotificationModule - Notification Bell"); LOG_INFO("externalNotificationModule - Notification Bell");
@ -509,7 +510,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
} }
} }
if (moduleConfig.external_notification.alert_message) { if (moduleConfig.external_notification.alert_message && !ch.settings.mute) {
LOG_INFO("externalNotificationModule - Notification Module"); LOG_INFO("externalNotificationModule - Notification Module");
isNagging = true; isNagging = true;
setExternalState(0, true); setExternalState(0, true);
@ -520,7 +521,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
} }
} }
if (moduleConfig.external_notification.alert_message_vibra) { if (moduleConfig.external_notification.alert_message_vibra && !ch.settings.mute) {
LOG_INFO("externalNotificationModule - Notification Module (Vibra)"); LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
isNagging = true; isNagging = true;
setExternalState(1, true); setExternalState(1, true);
@ -531,7 +532,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
} }
} }
if (moduleConfig.external_notification.alert_message_buzzer) { if (moduleConfig.external_notification.alert_message_buzzer && !ch.settings.mute) {
LOG_INFO("externalNotificationModule - Notification Module (Buzzer)"); LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY || if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(!isBroadcast(mp.to) && isToUs(&mp))) { (!isBroadcast(mp.to) && isToUs(&mp))) {

View File

@ -43,8 +43,8 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
void setExternalState(uint8_t index = 0, bool on = false); void setExternalState(uint8_t index = 0, bool on = false);
bool getExternal(uint8_t index = 0); bool getExternal(uint8_t index = 0);
void setMute(bool mute) { isMuted = mute; } void setMute(bool mute) { isSilenced = mute; }
bool getMute() { return isMuted; } bool getMute() { return isSilenced; }
bool canBuzz(); bool canBuzz();
bool nagging(); bool nagging();
@ -67,7 +67,7 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
bool isNagging = false; bool isNagging = false;
bool isMuted = false; bool isSilenced = false;
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp, virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request, meshtastic_AdminMessage *request,

View File

@ -112,204 +112,191 @@
*/ */
void setupModules() void setupModules()
{ {
if (config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) {
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER #if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
inputBroker = new InputBroker(); inputBroker = new InputBroker();
systemCommandsModule = new SystemCommandsModule(); systemCommandsModule = new SystemCommandsModule();
buzzerFeedbackThread = new BuzzerFeedbackThread(); buzzerFeedbackThread = new BuzzerFeedbackThread();
} }
#endif #endif
#if !MESHTASTIC_EXCLUDE_ADMIN #if !MESHTASTIC_EXCLUDE_ADMIN
adminModule = new AdminModule(); adminModule = new AdminModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_NODEINFO #if !MESHTASTIC_EXCLUDE_NODEINFO
nodeInfoModule = new NodeInfoModule(); nodeInfoModule = new NodeInfoModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
positionModule = new PositionModule(); positionModule = new PositionModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_WAYPOINT #if !MESHTASTIC_EXCLUDE_WAYPOINT
waypointModule = new WaypointModule(); waypointModule = new WaypointModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_TEXTMESSAGE #if !MESHTASTIC_EXCLUDE_TEXTMESSAGE
textMessageModule = new TextMessageModule(); textMessageModule = new TextMessageModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_TRACEROUTE #if !MESHTASTIC_EXCLUDE_TRACEROUTE
traceRouteModule = new TraceRouteModule(); traceRouteModule = new TraceRouteModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_NEIGHBORINFO #if !MESHTASTIC_EXCLUDE_NEIGHBORINFO
if (moduleConfig.has_neighbor_info && moduleConfig.neighbor_info.enabled) { if (moduleConfig.has_neighbor_info && moduleConfig.neighbor_info.enabled) {
neighborInfoModule = new NeighborInfoModule(); neighborInfoModule = new NeighborInfoModule();
} }
#endif #endif
#if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR #if !MESHTASTIC_EXCLUDE_DETECTIONSENSOR
if (moduleConfig.has_detection_sensor && moduleConfig.detection_sensor.enabled) { if (moduleConfig.has_detection_sensor && moduleConfig.detection_sensor.enabled) {
detectionSensorModule = new DetectionSensorModule(); detectionSensorModule = new DetectionSensorModule();
} }
#endif #endif
#if !MESHTASTIC_EXCLUDE_ATAK #if !MESHTASTIC_EXCLUDE_ATAK
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK, if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK, meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) {
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) { atakPluginModule = new AtakPluginModule();
atakPluginModule = new AtakPluginModule(); }
}
#endif #endif
#if !MESHTASTIC_EXCLUDE_PKI #if !MESHTASTIC_EXCLUDE_PKI
keyVerificationModule = new KeyVerificationModule(); keyVerificationModule = new KeyVerificationModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_DROPZONE #if !MESHTASTIC_EXCLUDE_DROPZONE
dropzoneModule = new DropzoneModule(); dropzoneModule = new DropzoneModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE #if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE
new GenericThreadModule(); new GenericThreadModule();
#endif #endif
// Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance // Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance
// to a global variable. // to a global variable.
#if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE #if !MESHTASTIC_EXCLUDE_REMOTEHARDWARE
new RemoteHardwareModule(); new RemoteHardwareModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_POWERSTRESS #if !MESHTASTIC_EXCLUDE_POWERSTRESS
new PowerStressModule(); new PowerStressModule();
#endif #endif
// Example: Put your module here // Example: Put your module here
// new ReplyModule(); // new ReplyModule();
#if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER #if (HAS_BUTTON || ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_INPUTBROKER
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1(); rotaryEncoderInterruptImpl1 = new RotaryEncoderInterruptImpl1();
if (!rotaryEncoderInterruptImpl1->init()) { if (!rotaryEncoderInterruptImpl1->init()) {
delete rotaryEncoderInterruptImpl1; delete rotaryEncoderInterruptImpl1;
rotaryEncoderInterruptImpl1 = nullptr; rotaryEncoderInterruptImpl1 = nullptr;
} }
#ifdef T_LORA_PAGER #ifdef T_LORA_PAGER
// use a special FSM based rotary encoder version for T-LoRa Pager // use a special FSM based rotary encoder version for T-LoRa Pager
rotaryEncoderImpl = new RotaryEncoderImpl(); rotaryEncoderImpl = new RotaryEncoderImpl();
if (!rotaryEncoderImpl->init()) { if (!rotaryEncoderImpl->init()) {
delete rotaryEncoderImpl; delete rotaryEncoderImpl;
rotaryEncoderImpl = nullptr; rotaryEncoderImpl = nullptr;
} }
#else #else
upDownInterruptImpl1 = new UpDownInterruptImpl1(); upDownInterruptImpl1 = new UpDownInterruptImpl1();
if (!upDownInterruptImpl1->init()) { if (!upDownInterruptImpl1->init()) {
delete upDownInterruptImpl1; delete upDownInterruptImpl1;
upDownInterruptImpl1 = nullptr; upDownInterruptImpl1 = nullptr;
} }
#endif #endif
cardKbI2cImpl = new CardKbI2cImpl(); cardKbI2cImpl = new CardKbI2cImpl();
cardKbI2cImpl->init(); cardKbI2cImpl->init();
#if defined(M5STACK_UNITC6L) #if defined(M5STACK_UNITC6L)
i2cButton = new i2cButtonThread("i2cButtonThread"); i2cButton = new i2cButtonThread("i2cButtonThread");
#endif #endif
#ifdef INPUTBROKER_MATRIX_TYPE #ifdef INPUTBROKER_MATRIX_TYPE
kbMatrixImpl = new KbMatrixImpl(); kbMatrixImpl = new KbMatrixImpl();
kbMatrixImpl->init(); kbMatrixImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE #endif // INPUTBROKER_MATRIX_TYPE
#ifdef INPUTBROKER_SERIAL_TYPE #ifdef INPUTBROKER_SERIAL_TYPE
aSerialKeyboardImpl = new SerialKeyboardImpl(); aSerialKeyboardImpl = new SerialKeyboardImpl();
aSerialKeyboardImpl->init(); aSerialKeyboardImpl->init();
#endif // INPUTBROKER_MATRIX_TYPE #endif // INPUTBROKER_MATRIX_TYPE
} }
#endif // HAS_BUTTON #endif // HAS_BUTTON
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
seesawRotary = new SeesawRotary("SeesawRotary"); seesawRotary = new SeesawRotary("SeesawRotary");
if (!seesawRotary->init()) { if (!seesawRotary->init()) {
delete seesawRotary; delete seesawRotary;
seesawRotary = nullptr; seesawRotary = nullptr;
}
aLinuxInputImpl = new LinuxInputImpl();
aLinuxInputImpl->init();
} }
aLinuxInputImpl = new LinuxInputImpl();
aLinuxInputImpl->init();
}
#endif #endif
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL #if !MESHTASTIC_EXCLUDE_INPUTBROKER && HAS_TRACKBALL
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
trackballInterruptImpl1 = new TrackballInterruptImpl1(); trackballInterruptImpl1 = new TrackballInterruptImpl1();
trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS); trackballInterruptImpl1->init(TB_DOWN, TB_UP, TB_LEFT, TB_RIGHT, TB_PRESS);
} }
#endif #endif
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE #ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
expressLRSFiveWayInput = new ExpressLRSFiveWay(); expressLRSFiveWayInput = new ExpressLRSFiveWay();
#endif #endif
#if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES #if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
cannedMessageModule = new CannedMessageModule(); cannedMessageModule = new CannedMessageModule();
} }
#endif #endif
#if ARCH_PORTDUINO #if ARCH_PORTDUINO
new HostMetricsModule(); new HostMetricsModule();
#endif #endif
#if HAS_TELEMETRY #if HAS_TELEMETRY
new DeviceTelemetryModule(); new DeviceTelemetryModule();
#endif #endif
#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (moduleConfig.has_telemetry && if (moduleConfig.has_telemetry &&
(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) { (moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
new EnvironmentTelemetryModule(); new EnvironmentTelemetryModule();
} }
#if __has_include("Adafruit_PM25AQI.h") #if __has_include("Adafruit_PM25AQI.h")
if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled && if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled &&
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) { nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
new AirQualityTelemetryModule(); new AirQualityTelemetryModule();
} }
#endif #endif
#if !MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY #if !MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX30102].first > 0 || if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX30102].first > 0 ||
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MLX90614].first > 0) { nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MLX90614].first > 0) {
new HealthTelemetryModule(); new HealthTelemetryModule();
} }
#endif #endif
#endif #endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (moduleConfig.has_telemetry && if (moduleConfig.has_telemetry &&
(moduleConfig.telemetry.power_measurement_enabled || moduleConfig.telemetry.power_screen_enabled)) { (moduleConfig.telemetry.power_measurement_enabled || moduleConfig.telemetry.power_screen_enabled)) {
new PowerTelemetryModule(); new PowerTelemetryModule();
} }
#endif #endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \ #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \
!defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#if !MESHTASTIC_EXCLUDE_SERIAL #if !MESHTASTIC_EXCLUDE_SERIAL
if (moduleConfig.has_serial && moduleConfig.serial.enabled && if (moduleConfig.has_serial && moduleConfig.serial.enabled &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
new SerialModule(); new SerialModule();
} }
#endif #endif
#endif #endif
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
// Only run on an esp32 based device. // Only run on an esp32 based device.
#if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO #if defined(USE_SX1280) && !MESHTASTIC_EXCLUDE_AUDIO
audioModule = new AudioModule(); audioModule = new AudioModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER #if !MESHTASTIC_EXCLUDE_PAXCOUNTER
if (moduleConfig.has_paxcounter && moduleConfig.paxcounter.enabled) { if (moduleConfig.has_paxcounter && moduleConfig.paxcounter.enabled) {
paxcounterModule = new PaxcounterModule(); paxcounterModule = new PaxcounterModule();
} }
#endif #endif
#endif #endif
#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) #if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_STOREFORWARD #if !MESHTASTIC_EXCLUDE_STOREFORWARD
if (moduleConfig.has_store_forward && moduleConfig.store_forward.enabled) { if (moduleConfig.has_store_forward && moduleConfig.store_forward.enabled) {
storeForwardModule = new StoreForwardModule(); storeForwardModule = new StoreForwardModule();
} }
#endif #endif
#endif #endif
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION #if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
externalNotificationModule = new ExternalNotificationModule(); externalNotificationModule = new ExternalNotificationModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
if (moduleConfig.has_range_test && moduleConfig.range_test.enabled) if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)
new RangeTestModule(); new RangeTestModule();
#endif #endif
} else {
#if !MESHTASTIC_EXCLUDE_ADMIN
adminModule = new AdminModule();
#endif
#if HAS_TELEMETRY
new DeviceTelemetryModule();
#endif
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
traceRouteModule = new TraceRouteModule();
#endif
}
// NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra // NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra
// acks // acks
routingModule = new RoutingModule(); routingModule = new RoutingModule();

View File

@ -94,11 +94,6 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
u.public_key.bytes[0] = 0; u.public_key.bytes[0] = 0;
u.public_key.size = 0; u.public_key.size = 0;
} }
// Coerce unmessagable for Repeater role
if (u.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
u.has_is_unmessagable = true;
u.is_unmessagable = true;
}
LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name); LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name);
lastSentToMesh = millis(); lastSentToMesh = millis();

View File

@ -42,17 +42,19 @@ bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mesh
meshtastic_MeshPacket *RoutingModule::allocReply() meshtastic_MeshPacket *RoutingModule::allocReply()
{ {
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER)
return NULL;
assert(currentRequest); assert(currentRequest);
return NULL; return NULL;
} }
void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit) void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit,
bool ackWantsAck)
{ {
auto p = allocAckNak(err, to, idFrom, chIndex, hopLimit); auto p = allocAckNak(err, to, idFrom, chIndex, hopLimit);
// Allow the caller to set want_ack on this ACK packet if it's important that the ACK be delivered reliably
p->want_ack = ackWantsAck;
router->sendLocal(p); // we sometimes send directly to the local node router->sendLocal(p); // we sometimes send directly to the local node
} }

View File

@ -13,8 +13,8 @@ class RoutingModule : public ProtobufModule<meshtastic_Routing>
*/ */
RoutingModule(); RoutingModule();
virtual void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, virtual void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0,
uint8_t hopLimit = 0); bool ackWantsAck = false);
// Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response // Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response
uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit); uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit);

View File

@ -26,7 +26,6 @@ int32_t DeviceTelemetryModule::runOnce()
Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval, Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) && default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
airTime->isTxAllowedChannelUtil(!isImpoliteRole) && airTime->isTxAllowedAirUtil() && airTime->isTxAllowedChannelUtil(!isImpoliteRole) && airTime->isTxAllowedAirUtil() &&
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) { config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN) {
sendTelemetry(); sendTelemetry();
lastSentToMesh = uptimeLastMs; lastSentToMesh = uptimeLastMs;
@ -44,10 +43,6 @@ int32_t DeviceTelemetryModule::runOnce()
bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
{ {
// Don't worry about storing telemetry in NodeDB if we're a repeater
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER)
return false;
if (t->which_variant == meshtastic_Telemetry_device_metrics_tag) { if (t->which_variant == meshtastic_Telemetry_device_metrics_tag) {
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) #if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
const char *sender = getSenderShortName(mp); const char *sender = getSenderShortName(mp);

View File

@ -22,10 +22,6 @@ int32_t HostMetricsModule::runOnce()
bool HostMetricsModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) bool HostMetricsModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
{ {
// Don't worry about storing telemetry in NodeDB if we're a repeater
if (config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER)
return false;
if (t->which_variant == meshtastic_Telemetry_host_metrics_tag) { if (t->which_variant == meshtastic_Telemetry_host_metrics_tag) {
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) #if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
const char *sender = getSenderShortName(mp); const char *sender = getSenderShortName(mp);

View File

@ -419,6 +419,9 @@ bool TraceRouteModule::startTraceRoute(NodeNum node)
p->decoded.portnum = meshtastic_PortNum_TRACEROUTE_APP; p->decoded.portnum = meshtastic_PortNum_TRACEROUTE_APP;
p->decoded.want_response = true; p->decoded.want_response = true;
// Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo)
p->want_ack = true;
// Manually encode the RouteDiscovery payload // Manually encode the RouteDiscovery payload
p->decoded.payload.size = p->decoded.payload.size =
pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, &req); pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, &req);
@ -532,6 +535,9 @@ void TraceRouteModule::launch(NodeNum node)
p->decoded.portnum = meshtastic_PortNum_TRACEROUTE_APP; p->decoded.portnum = meshtastic_PortNum_TRACEROUTE_APP;
p->decoded.want_response = true; p->decoded.want_response = true;
// Use reliable delivery for traceroute requests (which will be copied to traceroute responses by setReplyTo)
p->want_ack = true;
p->decoded.payload.size = p->decoded.payload.size =
pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, &req); pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, &req);

View File

@ -335,7 +335,8 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke
meshtastic::BluetoothStatus newStatus(textkey); meshtastic::BluetoothStatus newStatus(textkey);
bluetoothStatus->updateStatus(&newStatus); bluetoothStatus->updateStatus(&newStatus);
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus #if HAS_SCREEN && \
!defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
if (screen) { if (screen) {
screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { screen->startAlert([](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
char btPIN[16] = "888888"; char btPIN[16] = "888888";

View File

@ -98,6 +98,8 @@
#define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK
#elif defined(SEEED_WIO_TRACKER_L1) #elif defined(SEEED_WIO_TRACKER_L1)
#define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1
#elif defined(R1_NEO)
#define HW_VENDOR meshtastic_HardwareModel_MUZI_R1_NEO
#elif defined(HELTEC_MESH_SOLAR) #elif defined(HELTEC_MESH_SOLAR)
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR
#else #else
@ -149,3 +151,6 @@
// No serial ports on this board - ONLY use segger in memory console // No serial ports on this board - ONLY use segger in memory console
#define USE_SEGGER #define USE_SEGGER
#endif #endif
// Detect if running in ISR context (ARM Cortex-M4)
#define xPortInIsrContext() ((SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) == 0 ? pdFALSE : pdTRUE)

View File

@ -35,4 +35,7 @@
#define HW_VENDOR meshtastic_HardwareModel_RP2040_FEATHER_RFM95 #define HW_VENDOR meshtastic_HardwareModel_RP2040_FEATHER_RFM95
#elif defined(PRIVATE_HW) #elif defined(PRIVATE_HW)
#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW #define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW
#endif #endif
// Detect if running in ISR context (ARM Cortex-M33 / RISC-V)
#define xPortInIsrContext() (__get_current_exception() == 0 ? pdFALSE : pdTRUE)

View File

@ -34,6 +34,8 @@
#define OCV_ARRAY 4200, 3876, 3826, 3763, 3713, 3660, 3573, 3485, 3422, 3359, 3300 #define OCV_ARRAY 4200, 3876, 3826, 3763, 3713, 3660, 3573, 3485, 3422, 3359, 3300
#elif defined(SEEED_SOLAR_NODE) #elif defined(SEEED_SOLAR_NODE)
#define OCV_ARRAY 4200, 3986, 3922, 3812, 3734, 3645, 3527, 3420, 3281, 3087, 2786 #define OCV_ARRAY 4200, 3986, 3922, 3812, 3734, 3645, 3527, 3420, 3281, 3087, 2786
#elif defined(R1_NEO)
#define OCV_ARRAY 4330, 4292, 4254, 4216, 4178, 4140, 4102, 4064, 4026, 3988, 3950
#else // LiIon #else // LiIon
#define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100 #define OCV_ARRAY 4190, 4050, 3990, 3890, 3800, 3720, 3630, 3530, 3420, 3300, 3100
#endif #endif

View File

@ -532,8 +532,7 @@ void enableModemSleep()
bool shouldLoraWake(uint32_t msecToWake) bool shouldLoraWake(uint32_t msecToWake)
{ {
return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || return msecToWake < portMAX_DELAY && (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER);
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER);
} }
void enableLoraInterrupt() void enableLoraInterrupt()

View File

@ -83,8 +83,8 @@ class MockNodeDB : public NodeDB
class MockRoutingModule : public RoutingModule class MockRoutingModule : public RoutingModule
{ {
public: public:
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0,
uint8_t hopLimit = 0) override bool ackWantsAck = false) override
{ {
ackNacks_.emplace_back(err, to, idFrom, chIndex, hopLimit); ackNacks_.emplace_back(err, to, idFrom, chIndex, hopLimit);
} }

View File

@ -21,7 +21,7 @@
// "USERPREFS_CONFIG_LORA_REGION": "meshtastic_Config_LoRaConfig_RegionCode_US", // "USERPREFS_CONFIG_LORA_REGION": "meshtastic_Config_LoRaConfig_RegionCode_US",
// "USERPREFS_CONFIG_OWNER_LONG_NAME": "My Long Name", // "USERPREFS_CONFIG_OWNER_LONG_NAME": "My Long Name",
// "USERPREFS_CONFIG_OWNER_SHORT_NAME": "MLN", // "USERPREFS_CONFIG_OWNER_SHORT_NAME": "MLN",
// "USERPREFS_CONFIG_DEVICE_ROLE": "meshtastic_Config_DeviceConfig_Role_CLIENT", // Defaults to CLIENT. ROUTER*, LOST AND FOUND, and REPEATER roles are restricted. // "USERPREFS_CONFIG_DEVICE_ROLE": "meshtastic_Config_DeviceConfig_Role_CLIENT", // Defaults to CLIENT. ROUTER*, and LOST AND FOUND roles are restricted.
// "USERPREFS_EVENT_MODE": "1", // "USERPREFS_EVENT_MODE": "1",
// "USERPREFS_FIRMWARE_EDITION": "meshtastic_FirmwareEdition_BURNING_MAN", // "USERPREFS_FIRMWARE_EDITION": "meshtastic_FirmwareEdition_BURNING_MAN",
// "USERPREFS_FIXED_BLUETOOTH": "121212", // "USERPREFS_FIXED_BLUETOOTH": "121212",

View File

@ -15,7 +15,7 @@ build_flags = ${esp32s3_base.build_flags}
-D SDCARD_USE_SPI1 -D SDCARD_USE_SPI1
-D ENABLE_ROTARY_PULLUP -D ENABLE_ROTARY_PULLUP
-D ENABLE_BUTTON_PULLUP -D ENABLE_BUTTON_PULLUP
-D HALF_STEP -D ROTARY_BUXTRONICS
lib_deps = ${esp32s3_base.lib_deps} lib_deps = ${esp32s3_base.lib_deps}
lovyan03/LovyanGFX@1.2.7 lovyan03/LovyanGFX@1.2.7
@ -26,7 +26,7 @@ lib_deps = ${esp32s3_base.lib_deps}
lewisxhe/SensorLib@0.3.1 lewisxhe/SensorLib@0.3.1
https://github.com/pschatzmann/arduino-audio-driver/archive/refs/tags/v0.1.3.zip https://github.com/pschatzmann/arduino-audio-driver/archive/refs/tags/v0.1.3.zip
https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip
https://github.com/mverch67/RotaryEncoder/archive/25a59d5745a6645536f921427d80b08e78f886d4.zip https://github.com/mverch67/RotaryEncoder/archive/da958a21389cbcd485989705df602a33e092dd88.zip
[env:tlora-pager-tft] [env:tlora-pager-tft]
board_level = extra board_level = extra

View File

@ -0,0 +1,19 @@
; The R1 Neo board
[env:r1-neo]
extends = nrf52840_base
board = r1-neo
board_check = true
build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/r1-neo
-D R1_NEO
-DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-DRADIOLIB_EXCLUDE_SX128X=1
-DRADIOLIB_EXCLUDE_SX127X=1
-DRADIOLIB_EXCLUDE_LR11X0=1
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/r1-neo> +<mesh/api/> +<mqtt/>
lib_deps =
${nrf52840_base.lib_deps}
${networking_base.lib_deps}
https://github.com/RAKWireless/RAK13800-W5100S/archive/1.0.2.zip
rakwireless/RAKwireless NCP5623 RGB LED library@^1.0.2
artronshop/ArtronShop_RX8130CE@1.0.0

View File

@ -0,0 +1,45 @@
/*
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
Copyright (c) 2016 Sandeep Mistry All right reserved.
Copyright (c) 2018, Adafruit Industries (adafruit.com)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "variant.h"
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
const uint32_t g_ADigitalPinMap[] = {
// P0
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
// P1
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
void initVariant()
{
// LED1 & LED2
pinMode(PIN_LED1, OUTPUT);
ledOff(PIN_LED1);
pinMode(PIN_LED2, OUTPUT);
ledOff(PIN_LED2);
// 3V3 Power Rail
// pinMode(PIN_3V3_EN, OUTPUT);
// digitalWrite(PIN_3V3_EN, HIGH);
}

View File

@ -0,0 +1,149 @@
/*
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
Copyright (c) 2016 Sandeep Mistry All right reserved.
Copyright (c) 2018, Adafruit Industries (adafruit.com)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _VARIANT_R1NEO_
#define _VARIANT_R1NEO_
#define RAK4630
/** Master clock frequency */
#define VARIANT_MCK (64000000ul)
#define USE_LFXO // Board uses 32khz crystal for LF
// define USE_LFRC // Board uses RC for LF
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "WVariant.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Number of pins defined in PinDescription array
#define PINS_COUNT (48)
#define NUM_DIGITAL_PINS (48)
#define NUM_ANALOG_INPUTS (1)
#define NUM_ANALOG_OUTPUTS (0)
// LEDs
#define PIN_LED1 (32 + 4) // P1.04 Controls Green LED
#define PIN_LED2 (28) // P0.28 Controls Blue LED
#define LED_BUILTIN PIN_LED1
#define LED_CONN PIN_LED2
#define LED_GREEN PIN_LED1
#define LED_BLUE PIN_LED2
#define LED_STATE_ON 1 // State when LED is litted
// Button
#define PIN_BUTTON1 (26)
#define BUTTON_ACTIVE_LOW 0
#define BUTTON_ACTIVE_PULLUP 0
#define BUTTON_SENSE_TYPE INPUT_SENSE_HIGH
#define ADC_RESOLUTION 14
// Serial for GPS
#define PIN_SERIAL1_RX (25)
#define PIN_SERIAL1_TX (24)
// Connected to Jlink CDC
#define PIN_SERIAL2_RX (8)
#define PIN_SERIAL2_TX (6)
/*
* SPI Interfaces
*/
#define SPI_INTERFACES_COUNT 1
#define PIN_SPI_MISO (45)
#define PIN_SPI_MOSI (44)
#define PIN_SPI_SCK (43)
static const uint8_t SS = 42;
static const uint8_t MOSI = PIN_SPI_MOSI;
static const uint8_t MISO = PIN_SPI_MISO;
static const uint8_t SCK = PIN_SPI_SCK;
// R1 Neo Extras
#define DCDC_EN_HOLD (13) // P0.13 Keeps DCDC alive after user button is pressed
#define NRF_ON (29) // P0.29 Tells IO controller device is on
// RAKRGB
#define HAS_NCP5623
#define HAS_SCREEN 0
/*
* Wire Interfaces
*/
#define WIRE_INTERFACES_COUNT 1
#define PIN_WIRE_SDA (19) // P0.19 RTC_SDA
#define PIN_WIRE_SCL (20) // P0.20 RTC_SCL
#define PIN_BUZZER (0 + 3) // P0.03
#define USE_SX1262
#define SX126X_CS (42)
#define SX126X_DIO1 (47)
#define SX126X_BUSY (46)
#define SX126X_RESET (38)
#define SX126X_POWER_EN (37)
// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// Testing USB detection
#define NRF_APM
#define PIN_GPS_EN (32 + 1) // P1.01
#define PIN_GPS_PPS (2) // P0.02 Pulse per second input from the GPS
#define GPS_RX_PIN PIN_SERIAL1_RX
#define GPS_TX_PIN PIN_SERIAL1_TX
// Battery
#define BATTERY_PIN (0 + 31) // P0.31 ADC_VBAT
// and has 12 bit resolution
#define BATTERY_SENSE_RESOLUTION_BITS 12
#define BATTERY_SENSE_RESOLUTION 4096.0
#undef AREF_VOLTAGE
#define AREF_VOLTAGE 3.0
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define ADC_MULTIPLIER 1.73
#define HAS_RTC 1
#define RX8130CE_RTC 0x32
#ifdef __cplusplus
}
#endif
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif