Develop -> Master (#8209)

* Create channel-mute toggle function

* Added mute state to channel settings

* Create node-mute toggle functions

* Added mute state to nodedb entries

* Rebase protos

* Decouple protobuf changes

* Disable bell-invoked ext notifs for muted nodes

* Clearly dilineate module mute from sender or channel mute

* Disable bell-invoked ext notifs for muted channels

* Trunk fmt

* Disable message-invoked ext notifs for muted channels and nodes

* Disable on-screen 'new message' popup for muted nodes and channels

* Don't mute alerts

* Make use of pre-existing channel_settings.module_settings.is_client_muted setting

* Revert previous commit - this needs it's own proto

* Regen protos

* T-Lora Pager: Interrupt based rotary encoder

* T-Lora Pager: Fix amplifier fuzzing/popping

* Fix build for other variants

* Fix - reference actual channel when changing settings

* Update protos

* Refactor ref syntax

* Fix defines

* Update comments and remove unused function

* Regen protos

* Regen protobuffs again

* Fix build failure in ci, add missing argument

* Format

* InputPollable: System for polling after interrupts

* T-Lora Pager: Use InputPollable for RotaryEncoderImpl

* Rename RotaryEncoderImpl to TLoraPagerRotaryEncoder

* Revert "Rename RotaryEncoderImpl to TLoraPagerRotaryEncoder"

This reverts commit a76cc88dc2.

* Revert unnecessary ifdefs

* Regen protos

* Use latest protos

* Regen protos for latest changes

* Decouple node-mute from channel-mute

* Regen protos

* Fix desktop build

* More flexible InputPollable paradigm

* Custom xPortInIsrContext() for nRF52/RP2xx0

* Use channel as specified in the received packet

* Use channel as specified in the received packet for OLED screen notifications

* add heltec tracker v2 board.

* Use common power amp definition for Heltec v4 and Heltec Tracker v2

* Set appropriate mqtt root upon lora region change

* Use user preferences root topic if present

* delete SX126X_MAX_POWER=11

* Assume previous root on topic change

* update mqtt root when region is changed via OLED menu handler

* Regen protos

* Removed magic numbers

* Add DIRECT_MSG_ONLY buzzer mode (#8158)

* Handle existing special case for M5STACK_UNITC6L for DIRECT_MSG_ONLY buzz mode

There already exists a special case for M5STACK_UNITC6L.
Modified it to adhere to new DIRECT_MSG_ONLY buzzer mode

* Add new buzzer mode DIRECT_MSG_ONLY to BuzzerModeMenu

* Disable notifications when buzzer mode is DIRECT_MSG_ONLY

* Change alert_message_buzzer in notification module in DIRECT_MSG_ONLY buzz mode

Better comments

* Fixed spelling in debug log

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: nexpspace <4kosjdicx@mozmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>

* run trunk fmt

* Update variants/esp32s3/heltec_wireless_tracker_v2/variant.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Regen protos

* Pull latest protobufs

* Don't use IS_ONE_OF when loading Modules

* GAT562: Use PRIVATE_HW (fix build) (#8198)

* ESP32s2 doesn't implement HWCDC (#8199)

* Fix build script failure under certain conditions for devices that use UF2 binaries  (#8150)

* Validate CR and SF lora config (#8146)

* Validate CR and SF lora config

* No zero-bw

* Update src/modules/AdminModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix braces

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Quote firmware paths given to uf2conv

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Calculate airtime of transmitted and received packets separately (#8205)

* Correcting GPS PINs (#8087)

https://github.com/meshtastic/firmware/issues/8084

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>

* Clear out user.id except for sending to phone (#8202)

* Null out user.id except for sending to phone

* Fix

* Update src/modules/NodeInfoModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Copilot garbage

* This is unnecessary, because we don't stored user.id on userlite

* Don't need this

* Fix warning

* Just alter the protobuf

* Alter protobuf doesn't do anything with the altered data, so let's re-encode it

* Check inputbroker before access

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add dropped packet count to LocalStats (#8207)

* Add dropped packet count to LocalStats
In case the transmit queue was full

* Trunked

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>

---------

Co-authored-by: ford-jones <fordnicholasjones@gmail.com>
Co-authored-by: WillyJL <me@willyjl.dev>
Co-authored-by: Ford Jones <107664313+ford-jones@users.noreply.github.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Quency-D <hj_zzns@163.com>
Co-authored-by: Quency-D <55523105+Quency-D@users.noreply.github.com>
Co-authored-by: nexpspace <380097+nexpspace@users.noreply.github.com>
Co-authored-by: nexpspace <4kosjdicx@mozmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Austin Lane <vidplace7@gmail.com>
Co-authored-by: Ken Piper <kealper@ivystone.net>
Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
Co-authored-by: Szetya <szetya@gmail.com>
This commit is contained in:
Ben Meadors
2025-10-04 08:14:41 -05:00
committed by GitHub
co-authored by GitHub ford-jones WillyJL Ford Jones Tom Fifield Quency-D Quency-D nexpspace nexpspace Copilot Austin Lane Ken Piper GUVWAF Szetya
46 changed files with 617 additions and 162 deletions
+1 -1
View File
@@ -86,7 +86,7 @@ if platform.name == "espressif32":
if platform.name == "nordicnrf52": if platform.name == "nordicnrf52":
env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex", env.AddPostAction("$BUILD_DIR/${PROGNAME}.hex",
env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py $BUILD_DIR/firmware.hex -c -f 0xADA52840 -o $BUILD_DIR/firmware.uf2", env.VerboseAction(f"\"{sys.executable}\" ./bin/uf2conv.py \"$BUILD_DIR/firmware.hex\" -c -f 0xADA52840 -o \"$BUILD_DIR/firmware.uf2\"",
"Generating UF2 file")) "Generating UF2 file"))
Import("projenv") Import("projenv")
+37
View File
@@ -0,0 +1,37 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "heltec_wireless_tracker_v2"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "Heltec Wireless Tracker V2",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://heltec.org",
"vendor": "Heltec"
}
+17
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:
+1 -1
View File
@@ -86,7 +86,7 @@ int32_t SerialConsole::runOnce()
#endif #endif
int32_t delay = runOncePart(); int32_t delay = runOncePart();
#if defined(SERIAL_HAS_ON_RECEIVE) #if defined(SERIAL_HAS_ON_RECEIVE) || defined(CONFIG_IDF_TARGET_ESP32S2)
return Port.available() ? delay : INT32_MAX; return Port.available() ? delay : INT32_MAX;
#elif defined(IS_USB_SERIAL) #elif defined(IS_USB_SERIAL)
return HWCDC::isPlugged() ? delay : (1000 * 20); return HWCDC::isPlugged() ? delay : (1000 * 20);
+2 -1
View File
@@ -15,7 +15,8 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
{ {
// Only provide feedback if buzzer is enabled for notifications // Only provide feedback if buzzer is enabled for notifications
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED || if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED ||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY) { config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_NOTIFICATIONS_ONLY ||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY) {
return 0; // Let other handlers process the event return 0; // Let other handlers process the event
} }
+1 -1
View File
@@ -117,7 +117,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define SX126X_MAX_POWER 22 #define SX126X_MAX_POWER 22
#endif #endif
#ifdef HELTEC_V4 #ifdef USE_GC1109_PA
// Power Amps are often non-linear, so we can use an array of values for the power curve // Power Amps are often non-linear, so we can use an array of values for the power curve
#define NUM_PA_POINTS 22 #define NUM_PA_POINTS 22
#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7 #define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
+30 -15
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;
@@ -453,7 +453,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#endif #endif
dispdev->displayOn(); dispdev->displayOn();
#ifdef HELTEC_TRACKER_V1_X #if defined(HELTEC_TRACKER_V1_X) || defined(HELTEC_WIRELESS_TRACKER_V2)
ui->init(); ui->init();
#endif #endif
#ifdef USE_ST7789 #ifdef USE_ST7789
@@ -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,14 +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);
playLongBeep(); if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(isAlert && moduleConfig.external_notification.alert_bell_buzzer) ||
(!isBroadcast(packet->to) && isToUs(p))) {
// Beep if not in DIRECT_MSG_ONLY mode or if in DIRECT_MSG_ONLY mode and either
// - packet contains an alert and alert bell buzzer is enabled
// - packet is a non-broadcast that is addressed to this node
playLongBeep();
}
#else #else
screen->showSimpleBanner(banner, 3000); screen->showSimpleBanner(banner, 3000);
#endif #endif
}
} }
} }
+13 -3
View File
@@ -116,6 +116,8 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) { if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) {
config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected); config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected);
auto changes = SEGMENT_CONFIG;
// This is needed as we wait til picking the LoRa region to generate keys for the first time. // This is needed as we wait til picking the LoRa region to generate keys for the first time.
if (!owner.is_licensed) { if (!owner.is_licensed) {
bool keygenSuccess = false; bool keygenSuccess = false;
@@ -124,6 +126,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) { if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
keygenSuccess = true; keygenSuccess = true;
} }
} else { } else {
LOG_INFO("Generate new PKI keys"); LOG_INFO("Generate new PKI keys");
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes); crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
@@ -141,7 +144,14 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
if (myRegion->dutyCycle < 100) { if (myRegion->dutyCycle < 100) {
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
} }
service->reloadConfig(SEGMENT_CONFIG);
if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
// Default broker is in use, so subscribe to the appropriate MQTT root topic for this region
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
changes |= SEGMENT_MODULECONFIG;
}
service->reloadConfig(changes);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
} }
}; };
@@ -911,11 +921,11 @@ void menuHandler::BluetoothToggleMenu()
void menuHandler::BuzzerModeMenu() void menuHandler::BuzzerModeMenu()
{ {
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only"}; static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only", "DMs Only"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Buzzer Mode"; bannerOptions.message = "Buzzer Mode";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4; bannerOptions.optionsCount = 5;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
config.device.buzzer_mode = (meshtastic_Config_DeviceConfig_BuzzerMode)selected; config.device.buzzer_mode = (meshtastic_Config_DeviceConfig_BuzzerMode)selected;
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
+51 -1
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
+22
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;
+25 -22
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
+4 -4
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;
+5 -1
View File
@@ -381,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);
@@ -1600,6 +1600,10 @@ void loop()
#endif #endif
service->loop(); service->loop();
#if !MESHTASTIC_EXCLUDE_INPUTBROKER && defined(HAS_FREE_RTOS)
if (inputBroker)
inputBroker->processInputEventQueue();
#endif
#if defined(LGFX_SDL) #if defined(LGFX_SDL)
if (screen) { if (screen) {
auto dispdev = screen->getDisplayDevice(); auto dispdev = screen->getDisplayDevice();
+2
View File
@@ -65,5 +65,7 @@ template <class T> class LR11x0Interface : public RadioLibInterface
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override; virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
}; };
#endif #endif
+8 -1
View File
@@ -65,7 +65,7 @@ void fixPriority(meshtastic_MeshPacket *p)
} }
/** enqueue a packet, return false if full */ /** enqueue a packet, return false if full */
bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p) bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p, bool *dropped)
{ {
// no space - try to replace a lower priority packet in the queue // no space - try to replace a lower priority packet in the queue
if (queue.size() >= maxLen) { if (queue.size() >= maxLen) {
@@ -73,9 +73,16 @@ bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p)
if (!replaced) { if (!replaced) {
LOG_WARN("TX queue is full, and there is no lower-priority packet available to evict in favour of 0x%08x", p->id); LOG_WARN("TX queue is full, and there is no lower-priority packet available to evict in favour of 0x%08x", p->id);
} }
if (dropped) {
*dropped = true;
}
return replaced; return replaced;
} }
if (dropped) {
*dropped = false;
}
// Find the correct position using upper_bound to maintain a stable order // Find the correct position using upper_bound to maintain a stable order
auto it = std::upper_bound(queue.begin(), queue.end(), p, CompareMeshPacketFunc); auto it = std::upper_bound(queue.begin(), queue.end(), p, CompareMeshPacketFunc);
queue.insert(it, p); // Insert packet at the found position queue.insert(it, p); // Insert packet at the found position
+4 -2
View File
@@ -19,8 +19,10 @@ class MeshPacketQueue
public: public:
explicit MeshPacketQueue(size_t _maxLen); explicit MeshPacketQueue(size_t _maxLen);
/** enqueue a packet, return false if full */ /** enqueue a packet, return false if full
bool enqueue(meshtastic_MeshPacket *p); * @param dropped Optional pointer to a bool that will be set to true if a packet was dropped
*/
bool enqueue(meshtastic_MeshPacket *p, bool *dropped = nullptr);
/** return true if the queue is empty */ /** return true if the queue is empty */
bool empty(); bool empty();
+5 -4
View File
@@ -56,10 +56,6 @@
#include <WiFiOTA.h> #include <WiFiOTA.h>
#endif #endif
// stringify
#define xstr(s) str(s)
#define str(s) #s
NodeDB *nodeDB = nullptr; NodeDB *nodeDB = nullptr;
// we have plenty of ram so statically alloc this tempbuf (for now) // we have plenty of ram so statically alloc this tempbuf (for now)
@@ -260,6 +256,8 @@ NodeDB::NodeDB()
owner.role = config.device.role; owner.role = config.device.role;
// Ensure macaddr is set to our macaddr as it will be copied in our info below // Ensure macaddr is set to our macaddr as it will be copied in our info below
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
// Ensure owner.id is always derived from the node number
snprintf(owner.id, sizeof(owner.id), "!%08x", getNodeNum());
if (!config.has_security) { if (!config.has_security) {
config.has_security = true; config.has_security = true;
@@ -1695,6 +1693,9 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
} }
#endif #endif
// Always ensure user.id is derived from nodeId, regardless of what was received
snprintf(p.id, sizeof(p.id), "!%08x", nodeId);
// Both of info->user and p start as filled with zero so I think this is okay // Both of info->user and p start as filled with zero so I think this is okay
auto lite = TypeConversions::ConvertToUserLite(p); auto lite = TypeConversions::ConvertToUserLite(p);
bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex); bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex);
+2
View File
@@ -65,6 +65,8 @@ class RF95Interface : public RadioLibInterface
*/ */
virtual void configHardwareForSend() override; virtual void configHardwareForSend() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(*lora, pl, received); }
private: private:
/** Some boards require GPIO control of tx vs rx paths */ /** Some boards require GPIO control of tx vs rx paths */
void setTransmitEnable(bool txon); void setTransmitEnable(bool txon);
+4 -31
View File
@@ -230,33 +230,7 @@ The band is from 902 to 928 MHz. It mentions channel number and its respective c
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency. separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
*/ */
/** uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool received)
* Calculate airtime per
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
* section 4
*
* @return num msecs for the packet
*/
uint32_t RadioInterface::getPacketTime(uint32_t pl)
{
float bandwidthHz = bw * 1000.0f;
bool headDisable = false; // we currently always use the header
float tSym = (1 << sf) / bandwidthHz;
bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms
float tPreamble = (preambleLength + 4.25f) * tSym;
float numPayloadSym =
8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f);
float tPayload = numPayloadSym * tSym;
float tPacket = tPreamble + tPayload;
uint32_t msecs = tPacket * 1000;
return msecs;
}
uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p)
{ {
uint32_t pl = 0; uint32_t pl = 0;
if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) { if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag) {
@@ -265,7 +239,7 @@ uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p)
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded); size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
pl = numbytes + sizeof(PacketHeader); pl = numbytes + sizeof(PacketHeader);
} }
return getPacketTime(pl); return getPacketTime(pl, received);
} }
/** The delay to use for retransmitting dropped packets */ /** The delay to use for retransmitting dropped packets */
@@ -624,8 +598,7 @@ void RadioInterface::applyModemConfig()
saveFreq(freq + loraConfig.frequency_offset); saveFreq(freq + loraConfig.frequency_offset);
slotTimeMsec = computeSlotTimeMsec(); slotTimeMsec = computeSlotTimeMsec();
preambleTimeMsec = getPacketTime((uint32_t)0); preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw);
maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset); LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset);
LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", myRegion->name, channelName, loraConfig.modem_preset, LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", myRegion->name, channelName, loraConfig.modem_preset,
@@ -635,7 +608,7 @@ void RadioInterface::applyModemConfig()
LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw); LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw);
LOG_INFO("channel_num: %d", channel_num + 1); LOG_INFO("channel_num: %d", channel_num + 1);
LOG_INFO("frequency: %f", getFreq()); LOG_INFO("frequency: %f", getFreq());
LOG_INFO("Slot time: %u msec", slotTimeMsec); LOG_INFO("Slot time: %u msec, preamble time: %u msec", slotTimeMsec, preambleTimeMsec);
} }
/** Slottime is the time to detect a transmission has started, consisting of: /** Slottime is the time to detect a transmission has started, consisting of:
+4 -5
View File
@@ -87,9 +87,8 @@ class RadioInterface
const uint8_t NUM_SYM_CAD = 2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48 const uint8_t NUM_SYM_CAD = 2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48
const uint8_t NUM_SYM_CAD_24GHZ = 4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280 const uint8_t NUM_SYM_CAD_24GHZ = 4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280
uint32_t slotTimeMsec = computeSlotTimeMsec(); uint32_t slotTimeMsec = computeSlotTimeMsec();
uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving
uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast
uint32_t maxPacketTimeMsec = 3246; // calculated on startup, this is the default for LongFast
const uint32_t PROCESSING_TIME_MSEC = const uint32_t PROCESSING_TIME_MSEC =
4500; // time to construct, process and construct a packet again (empirically determined) 4500; // time to construct, process and construct a packet again (empirically determined)
const uint8_t CWmin = 3; // minimum CWsize const uint8_t CWmin = 3; // minimum CWsize
@@ -202,8 +201,8 @@ class RadioInterface
* *
* @return num msecs for the packet * @return num msecs for the packet
*/ */
uint32_t getPacketTime(const meshtastic_MeshPacket *p); uint32_t getPacketTime(const meshtastic_MeshPacket *p, bool received = false);
uint32_t getPacketTime(uint32_t totalPacketLen); virtual uint32_t getPacketTime(uint32_t totalPacketLen, bool received = false) = 0;
/** /**
* Get the channel we saved. * Get the channel we saved.
+31 -19
View File
@@ -116,16 +116,21 @@ bool RadioLibInterface::receiveDetected(uint16_t irq, ulong syncWordHeaderValidF
if (detected) { if (detected) {
if (!activeReceiveStart) { if (!activeReceiveStart) {
activeReceiveStart = millis(); activeReceiveStart = millis();
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && !(irq & syncWordHeaderValidFlag)) { } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec)) {
// The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag if (!(irq & syncWordHeaderValidFlag)) {
activeReceiveStart = 0; // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag
LOG_DEBUG("Ignore false preamble detection"); activeReceiveStart = 0;
return false; LOG_DEBUG("Ignore false preamble detection");
} else if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) { return false;
// We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag } else {
activeReceiveStart = 0; uint32_t maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
LOG_DEBUG("Ignore false header detection"); if (!Throttle::isWithinTimespanMs(activeReceiveStart, maxPacketTimeMsec)) {
return false; // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag
activeReceiveStart = 0;
LOG_DEBUG("Ignore false header detection");
return false;
}
}
} }
} }
return detected; return detected;
@@ -172,7 +177,12 @@ ErrorCode RadioLibInterface::send(meshtastic_MeshPacket *p)
printPacket("enqueue for send", p); printPacket("enqueue for send", p);
LOG_DEBUG("txGood=%d,txRelay=%d,rxGood=%d,rxBad=%d", txGood, txRelay, rxGood, rxBad); LOG_DEBUG("txGood=%d,txRelay=%d,rxGood=%d,rxBad=%d", txGood, txRelay, rxGood, rxBad);
ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN; bool dropped = false;
ErrorCode res = txQueue.enqueue(p, &dropped) ? ERRNO_OK : ERRNO_UNKNOWN;
if (dropped) {
txDrop++;
}
if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks
packetPool.release(p); packetPool.release(p);
@@ -354,11 +364,15 @@ void RadioLibInterface::clampToLateRebroadcastWindow(NodeNum from, PacketId id)
meshtastic_MeshPacket *p = txQueue.remove(from, id, true, false); meshtastic_MeshPacket *p = txQueue.remove(from, id, true, false);
if (p) { if (p) {
p->tx_after = millis() + getTxDelayMsecWeightedWorst(p->rx_snr); p->tx_after = millis() + getTxDelayMsecWeightedWorst(p->rx_snr);
if (txQueue.enqueue(p)) { bool dropped = false;
if (txQueue.enqueue(p, &dropped)) {
LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis()); LOG_DEBUG("Move existing queued packet to the late rebroadcast window %dms from now", p->tx_after - millis());
} else { } else {
packetPool.release(p); packetPool.release(p);
} }
if (dropped) {
txDrop++;
}
} }
} }
@@ -411,8 +425,6 @@ void RadioLibInterface::completeSending()
void RadioLibInterface::handleReceiveInterrupt() void RadioLibInterface::handleReceiveInterrupt()
{ {
uint32_t xmitMsec;
// when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race // when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race
// Condition? // Condition?
if (!isReceiving) { if (!isReceiving) {
@@ -425,12 +437,12 @@ void RadioLibInterface::handleReceiveInterrupt()
// read the number of actually received bytes // read the number of actually received bytes
size_t length = iface->getPacketLength(); size_t length = iface->getPacketLength();
xmitMsec = getPacketTime(length); uint32_t rxMsec = getPacketTime(length, true);
#ifndef DISABLE_WELCOME_UNSET #ifndef DISABLE_WELCOME_UNSET
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) { if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
LOG_WARN("lora rx disabled: Region unset"); LOG_WARN("lora rx disabled: Region unset");
airTime->logAirtime(RX_ALL_LOG, xmitMsec); airTime->logAirtime(RX_ALL_LOG, rxMsec);
return; return;
} }
#endif #endif
@@ -446,7 +458,7 @@ void RadioLibInterface::handleReceiveInterrupt()
radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags); radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags);
rxBad++; rxBad++;
airTime->logAirtime(RX_ALL_LOG, xmitMsec); airTime->logAirtime(RX_ALL_LOG, rxMsec);
} else { } else {
// Skip the 4 headers that are at the beginning of the rxBuf // Skip the 4 headers that are at the beginning of the rxBuf
@@ -456,7 +468,7 @@ void RadioLibInterface::handleReceiveInterrupt()
if (payloadLen < 0) { if (payloadLen < 0) {
LOG_WARN("Ignore received packet too short"); LOG_WARN("Ignore received packet too short");
rxBad++; rxBad++;
airTime->logAirtime(RX_ALL_LOG, xmitMsec); airTime->logAirtime(RX_ALL_LOG, rxMsec);
} else { } else {
rxGood++; rxGood++;
// altered packet with "from == 0" can do Remote Node Administration without permission // altered packet with "from == 0" can do Remote Node Administration without permission
@@ -494,7 +506,7 @@ void RadioLibInterface::handleReceiveInterrupt()
printPacket("Lora RX", mp); printPacket("Lora RX", mp);
airTime->logAirtime(RX_LOG, xmitMsec); airTime->logAirtime(RX_LOG, rxMsec);
deliverToReceiver(mp); deliverToReceiver(mp);
} }
+42
View File
@@ -61,6 +61,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE); MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE);
protected: protected:
ModemType_t modemType = RADIOLIB_MODEM_LORA;
DataRate_t getDataRate() const { return {.lora = {.spreadingFactor = sf, .bandwidth = bw, .codingRate = cr}}; }
PacketConfig_t getPacketConfig() const
{
return {.lora = {.preambleLength = preambleLength,
.implicitHeader = false,
.crcEnabled = true,
// We use auto LDRO, meaning it is enabled if the symbol time is >= 16msec
.ldrOptimize = (1 << sf) / bw >= 16}};
}
/** /**
* We use a meshtastic sync word, but hashed with the Channel name. For releases before 1.2 we used 0x12 (or for very old * We use a meshtastic sync word, but hashed with the Channel name. For releases before 1.2 we used 0x12 (or for very old
* loads 0x14) Note: do not use 0x34 - that is reserved for lorawan * loads 0x14) Note: do not use 0x34 - that is reserved for lorawan
@@ -105,6 +116,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
* Debugging counts * Debugging counts
*/ */
uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0; uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0;
uint16_t txDrop = 0;
public: public:
RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
@@ -209,6 +221,36 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
*/ */
virtual void setStandby(); virtual void setStandby();
/**
* Derive packet time either for a received (using header info) or a transmitted packet
*/
template <typename T> uint32_t computePacketTime(T &lora, uint32_t pl, bool received)
{
if (received) {
// First get the actual coding rate and CRC status from the received packet
uint8_t rxCR;
bool hasCRC;
lora.getLoRaRxHeaderInfo(&rxCR, &hasCRC);
// Go from raw header value to denominator
if (rxCR < 5) {
rxCR += 4;
} else if (rxCR == 7) {
rxCR = 8;
}
// Received packet configuration must be the same as configured, except for coding rate and CRC
DataRate_t dr = getDataRate();
dr.lora.codingRate = rxCR;
PacketConfig_t pc = getPacketConfig();
pc.lora.crcEnabled = hasCRC;
return lora.calculateTimeOnAir(modemType, dr, pc, pl) / 1000;
}
return lora.getTimeOnAir(pl) / 1000;
}
const char *radioLibErr = "RadioLib err="; const char *radioLibErr = "RadioLib err=";
/** /**
+1 -1
View File
@@ -76,7 +76,7 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
If we don't add this, we will likely retransmit too early. If we don't add this, we will likely retransmit too early.
*/ */
for (auto i = pending.begin(); i != pending.end(); i++) { for (auto i = pending.begin(); i != pending.end(); i++) {
i->second.nextTxMsec += iface->getPacketTime(p); i->second.nextTxMsec += iface->getPacketTime(p, true);
} }
return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p); return isBroadcast(p->to) ? FloodingRouter::shouldFilterReceived(p) : NextHopRouter::shouldFilterReceived(p);
+3 -3
View File
@@ -52,7 +52,7 @@ template <typename T> bool SX126xInterface<T>::init()
pinMode(SX126X_POWER_EN, OUTPUT); pinMode(SX126X_POWER_EN, OUTPUT);
#endif #endif
#ifdef HELTEC_V4 #if defined(USE_GC1109_PA)
pinMode(LORA_PA_POWER, OUTPUT); pinMode(LORA_PA_POWER, OUTPUT);
digitalWrite(LORA_PA_POWER, HIGH); digitalWrite(LORA_PA_POWER, HIGH);
@@ -352,7 +352,7 @@ template <typename T> bool SX126xInterface<T>::sleep()
digitalWrite(SX126X_POWER_EN, LOW); digitalWrite(SX126X_POWER_EN, LOW);
#endif #endif
#ifdef HELTEC_V4 #if defined(USE_GC1109_PA)
/* /*
* Do not switch the power on and off frequently. * Do not switch the power on and off frequently.
* After turning off LORA_PA_EN, the power consumption has dropped to the uA level. * After turning off LORA_PA_EN, the power consumption has dropped to the uA level.
@@ -367,7 +367,7 @@ template <typename T> bool SX126xInterface<T>::sleep()
/** Some boards require GPIO control of tx vs rx paths */ /** Some boards require GPIO control of tx vs rx paths */
template <typename T> void SX126xInterface<T>::setTransmitEnable(bool txon) template <typename T> void SX126xInterface<T>::setTransmitEnable(bool txon)
{ {
#ifdef HELTEC_V4 #if defined(USE_GC1109_PA)
digitalWrite(LORA_PA_POWER, HIGH); digitalWrite(LORA_PA_POWER, HIGH);
digitalWrite(LORA_PA_EN, HIGH); digitalWrite(LORA_PA_EN, HIGH);
digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0); digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0);
+2
View File
@@ -72,6 +72,8 @@ template <class T> class SX126xInterface : public RadioLibInterface
virtual void setStandby() override; virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
private: private:
/** Some boards require GPIO control of tx vs rx paths */ /** Some boards require GPIO control of tx vs rx paths */
void setTransmitEnable(bool txon); void setTransmitEnable(bool txon);
+2
View File
@@ -67,4 +67,6 @@ template <class T> class SX128xInterface : public RadioLibInterface
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override; virtual void setStandby() override;
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
}; };
+15 -4
View File
@@ -554,10 +554,8 @@ void AdminModule::handleSetOwner(const meshtastic_User &o)
changed |= strcmp(owner.short_name, o.short_name); changed |= strcmp(owner.short_name, o.short_name);
strncpy(owner.short_name, o.short_name, sizeof(owner.short_name)); strncpy(owner.short_name, o.short_name, sizeof(owner.short_name));
} }
if (*o.id) { snprintf(owner.id, sizeof(owner.id), "!%08x", nodeDB->getNodeNum());
changed |= strcmp(owner.id, o.id);
strncpy(owner.id, o.id, sizeof(owner.id));
}
if (owner.is_licensed != o.is_licensed) { if (owner.is_licensed != o.is_licensed) {
changed = 1; changed = 1;
owner.is_licensed = o.is_licensed; owner.is_licensed = o.is_licensed;
@@ -784,11 +782,24 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
if (myRegion->dutyCycle < 100) { if (myRegion->dutyCycle < 100) {
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
} }
// Compare the entire string, we are sure of the length as a topic has never been set
if (strcmp(moduleConfig.mqtt.root, default_mqtt_root) == 0) { if (strcmp(moduleConfig.mqtt.root, default_mqtt_root) == 0) {
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name); sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG; changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
} }
} }
if (config.lora.region != myRegion->code) {
// Region has changed so check whether there is a regulatory one we should be using instead.
// Additionally as a side-effect, assume a new value under myRegion
initRegion();
if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
// Default root is in use, so subscribe to the appropriate MQTT topic for this region
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
}
changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
}
break; break;
} }
case meshtastic_Config_bluetooth_tag: case meshtastic_Config_bluetooth_tag:
+26 -18
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,25 +532,32 @@ 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)");
isNagging = true; if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) { (!isBroadcast(mp.to) && isToUs(&mp))) {
setExternalState(2, true); // Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us
} else { isNagging = true;
if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
setExternalState(2, true);
} else {
#ifdef HAS_I2S #ifdef HAS_I2S
if (moduleConfig.external_notification.use_i2s_as_buzzer) { if (moduleConfig.external_notification.use_i2s_as_buzzer) {
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
} else } else
#endif #endif
if (moduleConfig.external_notification.use_pwm) { if (moduleConfig.external_notification.use_pwm) {
rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone);
}
}
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms;
} }
}
if (moduleConfig.external_notification.nag_timeout) {
nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
} else { } else {
nagCycleCutoff = millis() + moduleConfig.external_notification.output_ms; // Don't beep if buzzer mode is "direct messages only" and it is no direct message
LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY");
} }
} }
setIntervalFromNow(0); // run once so we know if we should do something setIntervalFromNow(0); // run once so we know if we should do something
+3 -3
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,
+2 -1
View File
@@ -148,7 +148,8 @@ void setupModules()
} }
#endif #endif
#if !MESHTASTIC_EXCLUDE_ATAK #if !MESHTASTIC_EXCLUDE_ATAK
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK, meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) { if (config.device.role == meshtastic_Config_DeviceConfig_Role_TAK ||
config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) {
atakPluginModule = new AtakPluginModule(); atakPluginModule = new AtakPluginModule();
} }
#endif #endif
+22 -2
View File
@@ -30,14 +30,32 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
bool wasBroadcast = isBroadcast(mp.to); bool wasBroadcast = isBroadcast(mp.to);
// LOG_DEBUG("did encode");
// if user has changed while packet was not for us, inform phone // if user has changed while packet was not for us, inform phone
if (hasChanged && !wasBroadcast && !isToUs(&mp)) if (hasChanged && !wasBroadcast && !isToUs(&mp)) {
service->sendToPhone(packetPool.allocCopy(mp)); auto packetCopy = packetPool.allocCopy(mp); // Keep a copy of the packet for later analysis
// Re-encode the user protobuf, as we have stripped out the user.id
packetCopy->decoded.payload.size = pb_encode_to_bytes(
packetCopy->decoded.payload.bytes, sizeof(packetCopy->decoded.payload.bytes), &meshtastic_User_msg, &p);
service->sendToPhone(packetCopy);
}
// LOG_DEBUG("did handleReceived"); // LOG_DEBUG("did handleReceived");
return false; // Let others look at this message also if they want return false; // Let others look at this message also if they want
} }
void NodeInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_User *p)
{
// Coerce user.id to be derived from the node number
snprintf(p->id, sizeof(p->id), "!%08x", getFrom(&mp));
// Re-encode the altered protobuf back into the packet
mp.decoded.payload.size =
pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), &meshtastic_User_msg, p);
}
void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t channel, bool _shorterTimeout) void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t channel, bool _shorterTimeout)
{ {
// cancel any not yet sent (now stale) position packets // cancel any not yet sent (now stale) position packets
@@ -95,6 +113,8 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
u.public_key.size = 0; u.public_key.size = 0;
} }
// Clear the user.id field since it should be derived from node number on the receiving end
u.id[0] = '\0';
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();
return allocDataProtobuf(u); return allocDataProtobuf(u);
+3
View File
@@ -30,6 +30,9 @@ class NodeInfoModule : public ProtobufModule<meshtastic_User>, private concurren
*/ */
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *p) override; virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *p) override;
/** Called to alter received User protobuf */
virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_User *p) override;
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked /** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. */ * so that subclasses can (optionally) send a response back to the original sender. */
virtual meshtastic_MeshPacket *allocReply() override; virtual meshtastic_MeshPacket *allocReply() override;
@@ -121,6 +121,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry()
telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad; telemetry.variant.local_stats.num_packets_rx = RadioLibInterface::instance->rxGood + RadioLibInterface::instance->rxBad;
telemetry.variant.local_stats.num_packets_rx_bad = RadioLibInterface::instance->rxBad; telemetry.variant.local_stats.num_packets_rx_bad = RadioLibInterface::instance->rxBad;
telemetry.variant.local_stats.num_tx_relay = RadioLibInterface::instance->txRelay; telemetry.variant.local_stats.num_tx_relay = RadioLibInterface::instance->txRelay;
telemetry.variant.local_stats.num_tx_dropped = RadioLibInterface::instance->txDrop;
} }
#ifdef ARCH_PORTDUINO #ifdef ARCH_PORTDUINO
if (SimRadio::instance) { if (SimRadio::instance) {
@@ -128,6 +129,7 @@ meshtastic_Telemetry DeviceTelemetryModule::getLocalStatsTelemetry()
telemetry.variant.local_stats.num_packets_rx = SimRadio::instance->rxGood + SimRadio::instance->rxBad; telemetry.variant.local_stats.num_packets_rx = SimRadio::instance->rxGood + SimRadio::instance->rxBad;
telemetry.variant.local_stats.num_packets_rx_bad = SimRadio::instance->rxBad; telemetry.variant.local_stats.num_packets_rx_bad = SimRadio::instance->rxBad;
telemetry.variant.local_stats.num_tx_relay = SimRadio::instance->txRelay; telemetry.variant.local_stats.num_tx_relay = SimRadio::instance->txRelay;
telemetry.variant.local_stats.num_tx_dropped = SimRadio::instance->txDrop;
} }
#else #else
telemetry.variant.local_stats.heap_total_bytes = memGet.getHeapSize(); telemetry.variant.local_stats.heap_total_bytes = memGet.getHeapSize();
+2
View File
@@ -201,6 +201,8 @@
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4 #define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4
#elif defined(M5STACK_UNITC6L) #elif defined(M5STACK_UNITC6L)
#define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L #define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L
#elif defined(HELTEC_WIRELESS_TRACKER_V2)
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2
#endif #endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
+3
View File
@@ -151,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)
+35 -5
View File
@@ -13,7 +13,12 @@ ErrorCode SimRadio::send(meshtastic_MeshPacket *p)
{ {
printPacket("enqueuing for send", p); printPacket("enqueuing for send", p);
ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN; bool dropped = false;
ErrorCode res = txQueue.enqueue(p, &dropped) ? ERRNO_OK : ERRNO_UNKNOWN;
if (dropped) {
txDrop++;
}
if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks
packetPool.release(p); packetPool.release(p);
@@ -182,7 +187,7 @@ void SimRadio::onNotify(uint32_t notification)
assert(txp); assert(txp);
startSend(txp); startSend(txp);
// Packet has been sent, count it toward our TX airtime utilization. // Packet has been sent, count it toward our TX airtime utilization.
uint32_t xmitMsec = getPacketTime(txp); uint32_t xmitMsec = RadioInterface::getPacketTime(txp);
airTime->logAirtime(TX_LOG, xmitMsec); airTime->logAirtime(TX_LOG, xmitMsec);
notifyLater(xmitMsec, ISR_TX, false); // Model the time it is busy sending notifyLater(xmitMsec, ISR_TX, false); // Model the time it is busy sending
@@ -252,7 +257,7 @@ void SimRadio::startReceive(meshtastic_MeshPacket *p)
if (isActivelyReceiving()) { if (isActivelyReceiving()) {
LOG_WARN("Collision detected, dropping current and previous packet!"); LOG_WARN("Collision detected, dropping current and previous packet!");
rxBad++; rxBad++;
airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket)); airTime->logAirtime(RX_ALL_LOG, getPacketTime(receivingPacket, true));
packetPool.release(receivingPacket); packetPool.release(receivingPacket);
receivingPacket = nullptr; receivingPacket = nullptr;
return; return;
@@ -270,7 +275,7 @@ void SimRadio::startReceive(meshtastic_MeshPacket *p)
} }
isReceiving = true; isReceiving = true;
receivingPacket = packetPool.allocCopy(*p); receivingPacket = packetPool.allocCopy(*p);
uint32_t airtimeMsec = getPacketTime(p); uint32_t airtimeMsec = getPacketTime(p, true);
notifyLater(airtimeMsec, ISR_RX, false); // Model the time it is busy receiving notifyLater(airtimeMsec, ISR_RX, false); // Model the time it is busy receiving
#else #else
isReceiving = true; isReceiving = true;
@@ -311,7 +316,7 @@ void SimRadio::handleReceiveInterrupt()
printPacket("Lora RX", mp); printPacket("Lora RX", mp);
airTime->logAirtime(RX_LOG, getPacketTime(mp)); airTime->logAirtime(RX_LOG, RadioInterface::getPacketTime(mp, true));
deliverToReceiver(mp); deliverToReceiver(mp);
} }
@@ -333,3 +338,28 @@ int16_t SimRadio::readData(uint8_t *data, size_t len)
return state; return state;
} }
/**
* Calculate airtime per
* https://www.rs-online.com/designspark/rel-assets/ds-assets/uploads/knowledge-items/application-notes-for-the-internet-of-things/LoRa%20Design%20Guide.pdf
* section 4
*
* @return num msecs for the packet
*/
uint32_t SimRadio::getPacketTime(uint32_t pl, bool received)
{
float bandwidthHz = bw * 1000.0f;
bool headDisable = false; // we currently always use the header
float tSym = (1 << sf) / bandwidthHz;
bool lowDataOptEn = tSym > 16e-3 ? true : false; // Needed if symbol time is >16ms
float tPreamble = (preambleLength + 4.25f) * tSym;
float numPayloadSym =
8 + max(ceilf(((8.0f * pl - 4 * sf + 28 + 16 - 20 * headDisable) / (4 * (sf - 2 * lowDataOptEn))) * cr), 0.0f);
float tPayload = numPayloadSym * tSym;
float tPacket = tPreamble + tPayload;
uint32_t msecs = tPacket * 1000;
return msecs;
}
+3
View File
@@ -52,6 +52,7 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
* Debugging counts * Debugging counts
*/ */
uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0; uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0;
uint16_t txDrop = 0;
protected: protected:
/// are _trying_ to receive a packet currently (note - we might just be waiting for one) /// are _trying_ to receive a packet currently (note - we might just be waiting for one)
@@ -88,6 +89,8 @@ class SimRadio : public RadioInterface, protected concurrency::NotifiedWorkerThr
/** /**
* If a send was in progress finish it and return the buffer to the pool */ * If a send was in progress finish it and return the buffer to the pool */
void completeSending(); void completeSending();
virtual uint32_t getPacketTime(uint32_t pl, bool received = false) override;
}; };
extern SimRadio *simRadio; extern SimRadio *simRadio;
+3
View File
@@ -36,3 +36,6 @@
#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)
+1 -1
View File
@@ -553,7 +553,7 @@ void enableLoraInterrupt()
gpio_pullup_en((gpio_num_t)LORA_CS); gpio_pullup_en((gpio_num_t)LORA_CS);
#endif #endif
#ifdef HELTEC_V4 #if defined(USE_GC1109_PA)
gpio_pullup_en((gpio_num_t)LORA_PA_POWER); gpio_pullup_en((gpio_num_t)LORA_PA_POWER);
gpio_pullup_en((gpio_num_t)LORA_PA_EN); gpio_pullup_en((gpio_num_t)LORA_PA_EN);
gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN); gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN);
+1
View File
@@ -37,6 +37,7 @@
#define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8 #define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define USE_GC1109_PA // We have a GC1109 power amplifier+attenuator
#define LORA_PA_POWER 7 // power en #define LORA_PA_POWER 7 // power en
#define LORA_PA_EN 2 #define LORA_PA_EN 2
#define LORA_PA_TX_EN 46 // enable tx #define LORA_PA_TX_EN 46 // enable tx
@@ -0,0 +1,71 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include "soc/soc_caps.h"
#include <stdint.h>
#define DISPLAY_HEIGHT 80
#define DISPLAY_WIDTH 160
#define USB_VID 0x303a
#define USB_PID 0x1001
static const uint8_t LED_BUILTIN = 18;
#define BUILTIN_LED LED_BUILTIN // backward compatibility
#define LED_BUILTIN LED_BUILTIN
static const uint8_t TX = 43;
static const uint8_t RX = 44;
static const uint8_t SDA = 5;
static const uint8_t SCL = 6;
static const uint8_t SS = 8;
static const uint8_t MOSI = 10;
static const uint8_t MISO = 11;
static const uint8_t SCK = 9;
static const uint8_t A0 = 1;
static const uint8_t A1 = 2;
static const uint8_t A2 = 3;
static const uint8_t A3 = 4;
static const uint8_t A4 = 5;
static const uint8_t A5 = 6;
static const uint8_t A6 = 7;
static const uint8_t A7 = 8;
static const uint8_t A8 = 9;
static const uint8_t A9 = 10;
static const uint8_t A10 = 11;
static const uint8_t A11 = 12;
static const uint8_t A12 = 13;
static const uint8_t A13 = 14;
static const uint8_t A14 = 15;
static const uint8_t A15 = 16;
static const uint8_t A16 = 17;
static const uint8_t A17 = 18;
static const uint8_t A18 = 19;
static const uint8_t A19 = 20;
static const uint8_t T1 = 1;
static const uint8_t T2 = 2;
static const uint8_t T3 = 3;
static const uint8_t T4 = 4;
static const uint8_t T5 = 5;
static const uint8_t T6 = 6;
static const uint8_t T7 = 7;
static const uint8_t T8 = 8;
static const uint8_t T9 = 9;
static const uint8_t T10 = 10;
static const uint8_t T11 = 11;
static const uint8_t T12 = 12;
static const uint8_t T13 = 13;
static const uint8_t T14 = 14;
static const uint8_t Vext = 3;
static const uint8_t LED = 18;
static const uint8_t RST_LoRa = 12;
static const uint8_t BUSY_LoRa = 13;
static const uint8_t DIO0 = 14;
#endif /* Pins_Arduino_h */
@@ -0,0 +1,14 @@
[env:heltec-wireless-tracker-v2]
extends = esp32s3_base
board = heltec_wireless_tracker_v2
board_build.partitions = default_8MB.csv
upload_protocol = esptool
build_flags =
${esp32s3_base.build_flags}
-I variants/esp32s3/heltec_wireless_tracker_v2
-D HELTEC_WIRELESS_TRACKER_V2
-D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
lib_deps =
${esp32s3_base.lib_deps}
lovyan03/LovyanGFX@^1.2.0
@@ -0,0 +1,78 @@
#define LED_PIN 18
#define _VARIANT_HELTEC_WIRELESS_TRACKER
// I2C
#define I2C_SDA SDA
#define I2C_SCL SCL
// ST7735S TFT LCD
#define ST7735S 1 // there are different (sub-)versions of ST7735
#define ST7735_CS 38
#define ST7735_RS 40 // DC
#define ST7735_SDA 42 // MOSI
#define ST7735_SCK 41
#define ST7735_RESET 39
#define ST7735_MISO -1
#define ST7735_BUSY -1
#define TFT_BL 21
#define ST7735_SPI_HOST SPI3_HOST
#define SPI_FREQUENCY 40000000
#define SPI_READ_FREQUENCY 16000000
#define SCREEN_ROTATE
#define TFT_HEIGHT DISPLAY_WIDTH
#define TFT_WIDTH DISPLAY_HEIGHT
#define TFT_OFFSET_X 24
#define TFT_OFFSET_Y 0
#define TFT_INVERT false
#define SCREEN_TRANSITION_FRAMERATE 3 // fps
#define DISPLAY_FORCE_SMALL_FONTS
#define VEXT_ENABLE 3 // active HIGH - powers the GPS, GPS LNA and OLED
#define VEXT_ON_VALUE HIGH
#define BUTTON_PIN 0
#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
#define ADC_CHANNEL ADC1_GPIO1_CHANNEL
#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider
#define ADC_MULTIPLIER 4.9 * 1.045
#define ADC_CTRL 2 // active HIGH, powers the voltage divider.
#define ADC_USE_PULLUP // Use internal pullup/pulldown instead of actively driving the output
#undef GPS_RX_PIN
#undef GPS_TX_PIN
#define GPS_RX_PIN 33
#define GPS_TX_PIN 34
#define PIN_GPS_RESET 35
#define PIN_GPS_PPS 36
// #define PIN_GPS_EN 3 // Uncomment to power off the GPS with triple-click on Tracker v2, though we'll also lose the
// display.
#define GPS_RESET_MODE LOW
#define GPS_UC6580
#define GPS_BAUDRATE 115200
#define USE_SX1262
#define LORA_DIO0 -1 // a No connect on the SX1262 module
#define LORA_RESET 12
#define LORA_DIO1 14 // SX1262 IRQ
#define LORA_DIO2 13 // SX1262 BUSY
#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TCXO is enabled
#define LORA_SCK 9
#define LORA_MISO 11
#define LORA_MOSI 10
#define LORA_CS 8
#define SX126X_CS LORA_CS
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY LORA_DIO2
#define SX126X_RESET LORA_RESET
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define USE_GC1109_PA // We have a GC1109 power amplifier+attenuator
#define LORA_PA_POWER 7 // power en
#define LORA_PA_EN 4
#define LORA_PA_TX_EN 46 // enable tx
+2 -2
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
@@ -6,7 +6,8 @@ board = gat562_mesh_trial_tracker
board_check = true board_check = true
build_flags = ${nrf52840_base.build_flags} build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/gat562_mesh_trial_tracker -I variants/nrf52840/gat562_mesh_trial_tracker
-D GAT562_MESH_TRIAL_TRACKER ;-D GAT562_MESH_TRIAL_TRACKER
-D PRIVATE_HW
-DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -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_SX128X=1
-DRADIOLIB_EXCLUDE_SX127X=1 -DRADIOLIB_EXCLUDE_SX127X=1
+6 -5
View File
@@ -140,11 +140,12 @@ static const uint8_t A0 = PIN_A0;
#define HAS_GPS 1 #define HAS_GPS 1
// #define PIN_GPS_REINIT (32 + 5) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K // #define PIN_GPS_REINIT (32 + 5) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K
#define PIN_GPS_STANDBY (32 + 10) // An output to wake GPS, low means allow sleep, high means force wake #define PIN_GPS_EN (32 + 11) // GPS power
// Seems to be missing on this new board #define GPS_EN_ACTIVE 1
#define PIN_GPS_PPS (0 + 29) // Pulse per second input from the GPS #define PIN_GPS_STANDBY (32 + 13) // wakeup pin
#define GPS_TX_PIN (32 + 15) // This is for bits going TOWARDS the CPU #define PIN_GPS_PPS (32 + 15)
#define GPS_RX_PIN (32 + 13) // This is for bits going TOWARDS the GPS #define GPS_TX_PIN (32 + 10) // L76K module RX PIN
#define GPS_RX_PIN (0 + 29) // L76K module TX PIN
#define GPS_THREAD_INTERVAL 50 #define GPS_THREAD_INTERVAL 50