Compare commits

..

2 Commits

Author SHA1 Message Date
Jonathan Bennett
66cb549817 Merge branch 'develop' into merge-master-fix 2026-01-01 16:12:28 -06:00
Jonathan Bennett
c351c49a72 Merge remote-tracking branch 'origin/master' into develop 2026-01-01 15:36:26 -06:00
44 changed files with 405 additions and 1162 deletions

View File

@@ -22,7 +22,7 @@ jobs:
### @{fc-author}, Welcome to Meshtastic! :wave:
Thanks for opening your first issue. If it's helpful, an easy way
to get logs is the "Open Serial Monitor" button on the [Web Flasher](https://flasher.meshtastic.org).
to get logs is the "Open Serial Monitor" button on the (Web Flasher](https://flasher.meshtastic.org).
If you have ideas for features, note that we often debate big ideas
in the [discussions tab](https://github.com/meshtastic/firmware/discussions/categories/ideas)

View File

@@ -8,9 +8,7 @@ on:
branches:
- master
- develop
- pioarduino # Remove when merged // use `feature/` in the future.
- event/*
- feature/*
paths-ignore:
- "**.md"
- version.properties
@@ -20,9 +18,7 @@ on:
branches:
- master
- develop
- pioarduino # Remove when merged // use `feature/` in the future.
- event/*
- feature/*
paths-ignore:
- "**.md"
#- "**.yml"

View File

@@ -143,7 +143,7 @@ jobs:
merge-multiple: true
- name: Test Report
uses: dorny/test-reporter@v2.4.0
uses: dorny/test-reporter@v2.3.0
with:
name: PlatformIO Tests
path: testreport.xml

View File

@@ -1,43 +0,0 @@
# Minimal container to run PlatformIO native/portduino tests
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
python3 \
python3-pip \
git \
build-essential \
cmake \
pkg-config \
libssl-dev \
libncurses5 \
libsdl2-dev \
libx11-dev \
libxext-dev \
libusb-1.0-0-dev \
libyaml-cpp-dev \
libuv1-dev \
libgpiod-dev \
libbluetooth-dev \
libulfius-dev \
liborcania-dev \
libmicrohttpd-dev \
libjansson-dev \
libgnutls28-dev \
libcurl4-gnutls-dev \
libi2c-dev \
openssl \
lsb-release \
cppcheck \
uuid-dev \
zlib1g-dev \
libbsd-dev \
gdb \
&& pip3 install --no-cache-dir platformio \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
CMD ["bash"]

View File

@@ -4,8 +4,8 @@
| Firmware Version | Supported |
| ---------------- | ------------------ |
| 2.7.x | :white_check_mark: |
| <= 2.6.x | :x: |
| 2.6.x | :white_check_mark: |
| <= 2.5.x | :x: |
## Reporting a Vulnerability

View File

@@ -87,9 +87,6 @@
</screenshots>
<releases>
<release version="2.7.18" date="2026-01-02">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url>
</release>
<release version="2.7.17" date="2025-11-28">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.17</url>
</release>

View File

@@ -1,50 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_1W",
"-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",
"psram_type": "opi",
"hwids": [
[
"0x303A",
"0x1001"
]
],
"mcu": "esp32s3",
"variant": "t-beam-1w"
},
"connectivity": [
"wifi",
"bluetooth",
"lora"
],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": [
"arduino"
],
"name": "LilyGo TBeam-1W",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"require_upload_port": true,
"speed": 921600
},
"url": "http://www.lilygo.cn/",
"vendor": "LilyGo"
}

6
debian/changelog vendored
View File

@@ -1,9 +1,3 @@
meshtasticd (2.7.18.0) unstable; urgency=medium
* Version 2.7.18
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Fri, 02 Jan 2026 12:45:36 +0000
meshtasticd (2.7.17.0) unstable; urgency=medium
* Version 2.7.17

View File

@@ -119,7 +119,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/a8e2f947f7abaf0c5ac8e6dd189a22156335beaa.zip
https://github.com/meshtastic/device-ui/archive/940ba8570f59c59c3508643f4d72840de716ce20.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]

View File

@@ -24,9 +24,6 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
switch (event->inputEvent) {
case INPUT_BROKER_USER_PRESS:
case INPUT_BROKER_ALT_PRESS:
playClick(); // Low delay feedback
break;
case INPUT_BROKER_SELECT:
case INPUT_BROKER_SELECT_LONG:
playBeep(); // Confirmation feedback
@@ -61,4 +58,4 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
}
return 0; // Allow other handlers to process the event
}
}

View File

@@ -113,14 +113,7 @@ void playShutdownMelody()
void playChirp()
{
// A short, friendly "chirp" sound for key presses
ToneDuration melody[] = {{NOTE_AS3, 20}}; // Short AS3 note
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
void playClick()
{
// A very short "click" sound with minimum delay; ideal for rotary encoder events
ToneDuration melody[] = {{NOTE_AS3, 1}}; // Very Short AS3
ToneDuration melody[] = {{NOTE_AS3, 20}}; // Very short AS3 note
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}

View File

@@ -9,7 +9,6 @@ void playGPSDisableBeep();
void playComboTune();
void playBoop();
void playChirp();
void playClick();
void playLongPressLeadUp();
bool playNextLeadUpNote(); // Play the next note in the lead-up sequence
void resetLeadUpSequence(); // Reset the lead-up sequence to start from beginning

View File

@@ -444,18 +444,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
#endif
// BME680 BSEC2 support detection
#if !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
#if defined(RAK_4631) || defined(TBEAM_V10)
#define MESHTASTIC_BME680_BSEC2_SUPPORTED 1
#define MESHTASTIC_BME680_HEADER <bsec2.h>
#else
#define MESHTASTIC_BME680_BSEC2_SUPPORTED 0
#define MESHTASTIC_BME680_HEADER <Adafruit_BME680.h>
#endif // defined(RAK_4631)
#endif // !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
// -----------------------------------------------------------------------------
// Global switches to turn off features for a minimized build
// -----------------------------------------------------------------------------

View File

@@ -107,60 +107,50 @@ void menuHandler::OnboardMessage()
void menuHandler::LoraRegionPicker(uint32_t duration)
{
static const LoraRegionOption regionOptions[] = {
{"Back", OptionsAction::Back},
{"US", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_US},
{"EU_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_433},
{"EU_868", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_868},
{"CN", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_CN},
{"JP", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_JP},
{"ANZ", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ANZ},
{"KR", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_KR},
{"TW", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_TW},
{"RU", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_RU},
{"IN", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_IN},
{"NZ_865", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_NZ_865},
{"TH", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_TH},
{"LORA_24", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_LORA_24},
{"UA_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_UA_433},
{"UA_868", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_UA_868},
{"MY_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_MY_433},
{"MY_919", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_MY_919},
{"SG_923", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_SG_923},
{"PH_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_PH_433},
{"PH_868", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_PH_868},
{"PH_915", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_PH_915},
{"ANZ_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ANZ_433},
{"KZ_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_KZ_433},
{"KZ_863", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_KZ_863},
{"NP_865", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_NP_865},
{"BR_902", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_BR_902},
};
constexpr size_t regionCount = sizeof(regionOptions) / sizeof(regionOptions[0]);
static std::array<const char *, regionCount> regionLabels{};
const char *bannerMessage = "Set the LoRa region";
static const char *optionsArray[] = {"Back",
"US",
"EU_433",
"EU_868",
"CN",
"JP",
"ANZ",
"KR",
"TW",
"RU",
"IN",
"NZ_865",
"TH",
"LORA_24",
"UA_433",
"UA_868",
"MY_433",
"MY_"
"919",
"SG_"
"923",
"PH_433",
"PH_868",
"PH_915",
"ANZ_433",
"KZ_433",
"KZ_863",
"NP_865",
"BR_902"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Set the LoRa region";
if (currentResolution == ScreenResolution::UltraLow) {
bannerMessage = "LoRa Region";
bannerOptions.message = "LoRa Region";
}
auto bannerOptions =
createStaticBannerOptions(bannerMessage, regionOptions, regionLabels, [](const LoraRegionOption &option, int) -> void {
if (!option.hasValue) {
return;
}
auto selectedRegion = option.value;
if (config.lora.region == selectedRegion) {
return;
}
config.lora.region = selectedRegion;
bannerOptions.durationMs = duration;
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 27;
bannerOptions.InitialSelected = 0;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected != 0 && config.lora.region != _meshtastic_Config_LoRaConfig_RegionCode(selected)) {
config.lora.region = _meshtastic_Config_LoRaConfig_RegionCode(selected);
auto changes = SEGMENT_CONFIG;
// FIXME: This should be a method consolidated with the same logic in the admin message as well
// 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 !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
if (!owner.is_licensed) {
bool keygenSuccess = false;
@@ -197,19 +187,8 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
service->reloadConfig(changes);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
});
bannerOptions.durationMs = duration;
int initialSelection = 0;
for (size_t i = 0; i < regionCount; ++i) {
if (regionOptions[i].hasValue && regionOptions[i].value == config.lora.region) {
initialSelection = static_cast<int>(i);
break;
}
}
bannerOptions.InitialSelected = initialSelection;
};
screen->showOverlayBanner(bannerOptions);
}
@@ -324,100 +303,102 @@ void menuHandler::showConfirmationBanner(const char *message, std::function<void
void menuHandler::ClockFacePicker()
{
static const ClockFaceOption clockFaceOptions[] = {
{"Back", OptionsAction::Back},
{"Digital", OptionsAction::Select, false},
{"Analog", OptionsAction::Select, true},
static const char *optionsArray[] = {"Back", "Digital", "Analog"};
enum optionsNumbers { Back = 0, Digital = 1, Analog = 2 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Which Face?";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
menuHandler::menuQueue = menuHandler::clock_menu;
screen->runNow();
} else if (selected == Digital) {
uiconfig.is_clockface_analog = false;
saveUIConfig();
screen->setFrames(Screen::FOCUS_CLOCK);
} else {
uiconfig.is_clockface_analog = true;
saveUIConfig();
screen->setFrames(Screen::FOCUS_CLOCK);
}
};
constexpr size_t clockFaceCount = sizeof(clockFaceOptions) / sizeof(clockFaceOptions[0]);
static std::array<const char *, clockFaceCount> clockFaceLabels{};
auto bannerOptions = createStaticBannerOptions("Which Face?", clockFaceOptions, clockFaceLabels,
[](const ClockFaceOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuHandler::menuQueue = menuHandler::clock_menu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
if (uiconfig.is_clockface_analog == option.value) {
return;
}
uiconfig.is_clockface_analog = option.value;
saveUIConfig();
screen->setFrames(Screen::FOCUS_CLOCK);
});
bannerOptions.InitialSelected = uiconfig.is_clockface_analog ? 2 : 1;
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::TZPicker()
{
static const TimezoneOption timezoneOptions[] = {
{"Back", OptionsAction::Back},
{"US/Hawaii", OptionsAction::Select, "HST10"},
{"US/Alaska", OptionsAction::Select, "AKST9AKDT,M3.2.0,M11.1.0"},
{"US/Pacific", OptionsAction::Select, "PST8PDT,M3.2.0,M11.1.0"},
{"US/Arizona", OptionsAction::Select, "MST7"},
{"US/Mountain", OptionsAction::Select, "MST7MDT,M3.2.0,M11.1.0"},
{"US/Central", OptionsAction::Select, "CST6CDT,M3.2.0,M11.1.0"},
{"US/Eastern", OptionsAction::Select, "EST5EDT,M3.2.0,M11.1.0"},
{"BR/Brasilia", OptionsAction::Select, "BRT3"},
{"UTC", OptionsAction::Select, "UTC0"},
{"EU/Western", OptionsAction::Select, "GMT0BST,M3.5.0/1,M10.5.0"},
{"EU/Central", OptionsAction::Select, "CET-1CEST,M3.5.0,M10.5.0/3"},
{"EU/Eastern", OptionsAction::Select, "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Asia/Kolkata", OptionsAction::Select, "IST-5:30"},
{"Asia/Hong_Kong", OptionsAction::Select, "HKT-8"},
{"AU/AWST", OptionsAction::Select, "AWST-8"},
{"AU/ACST", OptionsAction::Select, "ACST-9:30ACDT,M10.1.0,M4.1.0/3"},
{"AU/AEST", OptionsAction::Select, "AEST-10AEDT,M10.1.0,M4.1.0/3"},
{"Pacific/NZ", OptionsAction::Select, "NZST-12NZDT,M9.5.0,M4.1.0/3"},
};
constexpr size_t timezoneCount = sizeof(timezoneOptions) / sizeof(timezoneOptions[0]);
static std::array<const char *, timezoneCount> timezoneLabels{};
auto bannerOptions = createStaticBannerOptions(
"Pick Timezone", timezoneOptions, timezoneLabels, [](const TimezoneOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuHandler::menuQueue = menuHandler::clock_menu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
if (strncmp(config.device.tzdef, option.value, sizeof(config.device.tzdef)) == 0) {
return;
}
strncpy(config.device.tzdef, option.value, sizeof(config.device.tzdef));
config.device.tzdef[sizeof(config.device.tzdef) - 1] = '\0';
static const char *optionsArray[] = {"Back",
"US/Hawaii",
"US/Alaska",
"US/Pacific",
"US/Arizona",
"US/Mountain",
"US/Central",
"US/Eastern",
"BR/Brasilia",
"UTC",
"EU/Western",
"EU/"
"Central",
"EU/Eastern",
"Asia/Kolkata",
"Asia/Hong_Kong",
"AU/AWST",
"AU/ACST",
"AU/AEST",
"Pacific/NZ"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Pick Timezone";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 19;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) {
menuHandler::menuQueue = menuHandler::clock_menu;
screen->runNow();
} else if (selected == 1) { // Hawaii
strncpy(config.device.tzdef, "HST10", sizeof(config.device.tzdef));
} else if (selected == 2) { // Alaska
strncpy(config.device.tzdef, "AKST9AKDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef));
} else if (selected == 3) { // Pacific
strncpy(config.device.tzdef, "PST8PDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef));
} else if (selected == 4) { // Arizona
strncpy(config.device.tzdef, "MST7", sizeof(config.device.tzdef));
} else if (selected == 5) { // Mountain
strncpy(config.device.tzdef, "MST7MDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef));
} else if (selected == 6) { // Central
strncpy(config.device.tzdef, "CST6CDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef));
} else if (selected == 7) { // Eastern
strncpy(config.device.tzdef, "EST5EDT,M3.2.0,M11.1.0", sizeof(config.device.tzdef));
} else if (selected == 8) { // Brazil
strncpy(config.device.tzdef, "BRT3", sizeof(config.device.tzdef));
} else if (selected == 9) { // UTC
strncpy(config.device.tzdef, "UTC0", sizeof(config.device.tzdef));
} else if (selected == 10) { // EU/Western
strncpy(config.device.tzdef, "GMT0BST,M3.5.0/1,M10.5.0", sizeof(config.device.tzdef));
} else if (selected == 11) { // EU/Central
strncpy(config.device.tzdef, "CET-1CEST,M3.5.0,M10.5.0/3", sizeof(config.device.tzdef));
} else if (selected == 12) { // EU/Eastern
strncpy(config.device.tzdef, "EET-2EEST,M3.5.0/3,M10.5.0/4", sizeof(config.device.tzdef));
} else if (selected == 13) { // Asia/Kolkata
strncpy(config.device.tzdef, "IST-5:30", sizeof(config.device.tzdef));
} else if (selected == 14) { // China
strncpy(config.device.tzdef, "HKT-8", sizeof(config.device.tzdef));
} else if (selected == 15) { // AU/AWST
strncpy(config.device.tzdef, "AWST-8", sizeof(config.device.tzdef));
} else if (selected == 16) { // AU/ACST
strncpy(config.device.tzdef, "ACST-9:30ACDT,M10.1.0,M4.1.0/3", sizeof(config.device.tzdef));
} else if (selected == 17) { // AU/AEST
strncpy(config.device.tzdef, "AEST-10AEDT,M10.1.0,M4.1.0/3", sizeof(config.device.tzdef));
} else if (selected == 18) { // NZ
strncpy(config.device.tzdef, "NZST-12NZDT,M9.5.0,M4.1.0/3", sizeof(config.device.tzdef));
}
if (selected != 0) {
setenv("TZ", config.device.tzdef, 1);
service->reloadConfig(SEGMENT_CONFIG);
});
int initialSelection = 0;
for (size_t i = 0; i < timezoneCount; ++i) {
if (timezoneOptions[i].hasValue &&
strncmp(config.device.tzdef, timezoneOptions[i].value, sizeof(config.device.tzdef)) == 0) {
initialSelection = static_cast<int>(i);
break;
}
}
bannerOptions.InitialSelected = initialSelection;
};
screen->showOverlayBanner(bannerOptions);
}
@@ -477,9 +458,10 @@ void menuHandler::messageResponseMenu()
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Action";
if (currentResolution == ScreenResolution::UltraLow) {
bannerOptions.message = "Message";
} else {
bannerOptions.message = "Message Action";
}
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -928,12 +910,8 @@ void menuHandler::homeBaseMenu()
} else if (selected == Sleep) {
screen->setOn(false);
} else if (selected == Position) {
service->refreshLocalMeshNode();
if (service->trySendPosition(NODENUM_BROADCAST, true)) {
IF_SCREEN(screen->showSimpleBanner("Position\nSent", 3000));
} else {
IF_SCREEN(screen->showSimpleBanner("Node Info\nSent", 3000));
}
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SEND_PING, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
} else if (selected == Preset) {
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
} else if (selected == Freetext) {
@@ -1130,92 +1108,57 @@ void menuHandler::favoriteBaseMenu()
void menuHandler::positionBaseMenu()
{
enum class PositionAction {
GpsToggle,
GpsFormat,
enum optionsNumbers {
Back,
GPSToggle,
GPSFormat,
CompassMenu,
CompassCalibrate,
GPSSmartPosition,
GPSUpdateInterval,
GPSPositionBroadcast
GPSPositionBroadcast,
enumEnd
};
static const PositionMenuOption baseOptions[] = {
{"Back", OptionsAction::Back},
{"On/Off Toggle", OptionsAction::Select, static_cast<int>(PositionAction::GpsToggle)},
{"Format", OptionsAction::Select, static_cast<int>(PositionAction::GpsFormat)},
{"Smart Position", OptionsAction::Select, static_cast<int>(PositionAction::GPSSmartPosition)},
{"Update Interval", OptionsAction::Select, static_cast<int>(PositionAction::GPSUpdateInterval)},
{"Broadcast Interval", OptionsAction::Select, static_cast<int>(PositionAction::GPSPositionBroadcast)},
{"Compass", OptionsAction::Select, static_cast<int>(PositionAction::CompassMenu)},
};
static const char *optionsArray[enumEnd] = {
"Back", "On/Off Toggle", "Format", "Smart Position", "Update Interval", "Broadcast Interval", "Compass"};
static int optionsEnumArray[enumEnd] = {
Back, GPSToggle, GPSFormat, GPSSmartPosition, GPSUpdateInterval, GPSPositionBroadcast, CompassMenu};
int options = 7;
static const PositionMenuOption calibrateOptions[] = {
{"Back", OptionsAction::Back},
{"On/Off Toggle", OptionsAction::Select, static_cast<int>(PositionAction::GpsToggle)},
{"Format", OptionsAction::Select, static_cast<int>(PositionAction::GpsFormat)},
{"Smart Position", OptionsAction::Select, static_cast<int>(PositionAction::GPSSmartPosition)},
{"Update Interval", OptionsAction::Select, static_cast<int>(PositionAction::GPSUpdateInterval)},
{"Broadcast Interval", OptionsAction::Select, static_cast<int>(PositionAction::GPSPositionBroadcast)},
{"Compass", OptionsAction::Select, static_cast<int>(PositionAction::CompassMenu)},
{"Compass Calibrate", OptionsAction::Select, static_cast<int>(PositionAction::CompassCalibrate)},
};
constexpr size_t baseCount = sizeof(baseOptions) / sizeof(baseOptions[0]);
constexpr size_t calibrateCount = sizeof(calibrateOptions) / sizeof(calibrateOptions[0]);
static std::array<const char *, baseCount> baseLabels{};
static std::array<const char *, calibrateCount> calibrateLabels{};
auto onSelection = [](const PositionMenuOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
return;
}
if (!option.hasValue) {
return;
}
auto action = static_cast<PositionAction>(option.value);
switch (action) {
case PositionAction::GpsToggle:
menuQueue = gps_toggle_menu;
screen->runNow();
break;
case PositionAction::GpsFormat:
menuQueue = gps_format_menu;
screen->runNow();
break;
case PositionAction::CompassMenu:
menuQueue = compass_point_north_menu;
screen->runNow();
break;
case PositionAction::CompassCalibrate:
if (accelerometerThread) {
accelerometerThread->calibrate(30);
}
break;
case PositionAction::GPSSmartPosition:
menuQueue = gps_smart_position_menu;
screen->runNow();
break;
case PositionAction::GPSUpdateInterval:
menuQueue = gps_update_interval_menu;
screen->runNow();
break;
case PositionAction::GPSPositionBroadcast:
menuQueue = gps_position_broadcast_menu;
screen->runNow();
break;
}
};
BannerOverlayOptions bannerOptions;
if (accelerometerThread) {
bannerOptions = createStaticBannerOptions("GPS Action", calibrateOptions, calibrateLabels, onSelection);
} else {
bannerOptions = createStaticBannerOptions("GPS Action", baseOptions, baseLabels, onSelection);
optionsArray[options] = "Compass Calibrate";
optionsEnumArray[options++] = CompassCalibrate;
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "GPS Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == GPSToggle) {
menuQueue = gps_toggle_menu;
screen->runNow();
} else if (selected == GPSFormat) {
menuQueue = gps_format_menu;
screen->runNow();
} else if (selected == CompassMenu) {
menuQueue = compass_point_north_menu;
screen->runNow();
} else if (selected == CompassCalibrate) {
accelerometerThread->calibrate(30);
} else if (selected == GPSSmartPosition) {
menuQueue = gps_smart_position_menu;
screen->runNow();
} else if (selected == GPSUpdateInterval) {
menuQueue = gps_update_interval_menu;
screen->runNow();
} else if (selected == GPSPositionBroadcast) {
menuQueue = gps_position_broadcast_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
@@ -1271,38 +1214,27 @@ void menuHandler::nodeListMenu()
void menuHandler::nodeNameLengthMenu()
{
static const NodeNameOption nodeNameOptions[] = {
{"Back", OptionsAction::Back},
{"Long", OptionsAction::Select, true},
{"Short", OptionsAction::Select, false},
enum OptionsNumbers { Back, Long, Short };
static const char *optionsArray[] = {"Back", "Long", "Short"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Name Length";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Long) {
// Set names to long
LOG_INFO("Setting names to long");
config.display.use_long_node_name = true;
} else if (selected == Short) {
// Set names to short
LOG_INFO("Setting names to short");
config.display.use_long_node_name = false;
} else if (selected == Back) {
menuQueue = node_base_menu;
screen->runNow();
}
};
constexpr size_t nodeNameCount = sizeof(nodeNameOptions) / sizeof(nodeNameOptions[0]);
static std::array<const char *, nodeNameCount> nodeNameLabels{};
auto bannerOptions = createStaticBannerOptions("Node Name Length", nodeNameOptions, nodeNameLabels,
[](const NodeNameOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuQueue = node_base_menu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
if (config.display.use_long_node_name == option.value) {
return;
}
config.display.use_long_node_name = option.value;
LOG_INFO("Setting names to %s", option.value ? "long" : "short");
});
int initialSelection = config.display.use_long_node_name ? 1 : 2;
bannerOptions.InitialSelected = initialSelection;
bannerOptions.InitialSelected = config.display.use_long_node_name == true ? 1 : 2;
screen->showOverlayBanner(bannerOptions);
}
@@ -1336,169 +1268,119 @@ void menuHandler::resetNodeDBMenu()
void menuHandler::compassNorthMenu()
{
static const CompassOption compassOptions[] = {
{"Back", OptionsAction::Back},
{"Dynamic", OptionsAction::Select, meshtastic_CompassMode_DYNAMIC},
{"Fixed Ring", OptionsAction::Select, meshtastic_CompassMode_FIXED_RING},
{"Freeze Heading", OptionsAction::Select, meshtastic_CompassMode_FREEZE_HEADING},
};
constexpr size_t compassCount = sizeof(compassOptions) / sizeof(compassOptions[0]);
static std::array<const char *, compassCount> compassLabels{};
auto bannerOptions = createStaticBannerOptions("North Directions?", compassOptions, compassLabels,
[](const CompassOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuQueue = position_base_menu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
if (uiconfig.compass_mode == option.value) {
return;
}
uiconfig.compass_mode = option.value;
saveUIConfig();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
});
int initialSelection = 0;
for (size_t i = 0; i < compassCount; ++i) {
if (compassOptions[i].hasValue && uiconfig.compass_mode == compassOptions[i].value) {
initialSelection = static_cast<int>(i);
break;
enum optionsNumbers { Back, Dynamic, Fixed, Freeze };
static const char *optionsArray[] = {"Back", "Dynamic", "Fixed Ring", "Freeze Heading"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "North Directions?";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4;
bannerOptions.InitialSelected = uiconfig.compass_mode + 1;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Dynamic) {
if (uiconfig.compass_mode != meshtastic_CompassMode_DYNAMIC) {
uiconfig.compass_mode = meshtastic_CompassMode_DYNAMIC;
saveUIConfig();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == Fixed) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FIXED_RING) {
uiconfig.compass_mode = meshtastic_CompassMode_FIXED_RING;
saveUIConfig();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == Freeze) {
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
uiconfig.compass_mode = meshtastic_CompassMode_FREEZE_HEADING;
saveUIConfig();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
} else if (selected == Back) {
menuQueue = position_base_menu;
screen->runNow();
}
}
bannerOptions.InitialSelected = initialSelection;
};
screen->showOverlayBanner(bannerOptions);
}
#if !MESHTASTIC_EXCLUDE_GPS
void menuHandler::GPSToggleMenu()
{
static const GPSToggleOption gpsToggleOptions[] = {
{"Back", OptionsAction::Back},
{"Enabled", OptionsAction::Select, meshtastic_Config_PositionConfig_GpsMode_ENABLED},
{"Disabled", OptionsAction::Select, meshtastic_Config_PositionConfig_GpsMode_DISABLED},
};
constexpr size_t toggleCount = sizeof(gpsToggleOptions) / sizeof(gpsToggleOptions[0]);
static std::array<const char *, toggleCount> toggleLabels{};
auto bannerOptions =
createStaticBannerOptions("Toggle GPS", gpsToggleOptions, toggleLabels, [](const GPSToggleOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuQueue = position_base_menu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
if (config.position.gps_mode == option.value) {
return;
}
config.position.gps_mode = option.value;
if (option.value == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
playGPSEnableBeep();
gps->enable();
} else {
playGPSDisableBeep();
gps->disable();
}
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle GPS";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
playGPSEnableBeep();
gps->enable();
service->reloadConfig(SEGMENT_CONFIG);
});
int initialSelection = 0;
for (size_t i = 0; i < toggleCount; ++i) {
if (gpsToggleOptions[i].hasValue && config.position.gps_mode == gpsToggleOptions[i].value) {
initialSelection = static_cast<int>(i);
break;
} else if (selected == 2) {
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED;
playGPSDisableBeep();
gps->disable();
service->reloadConfig(SEGMENT_CONFIG);
} else {
menuQueue = position_base_menu;
screen->runNow();
}
}
bannerOptions.InitialSelected = initialSelection;
};
bannerOptions.InitialSelected = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED ? 1 : 2;
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::GPSFormatMenu()
{
static const GPSFormatOption formatOptionsHigh[] = {
{"Back", OptionsAction::Back},
{"Decimal Degrees", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC},
{"Degrees Minutes Seconds", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS},
{"Universal Transverse Mercator", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM},
{"Military Grid Reference System", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS},
{"Open Location Code", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC},
{"Ordnance Survey Grid Ref", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR},
{"Maidenhead Locator", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS},
};
static const GPSFormatOption formatOptionsLow[] = {
{"Back", OptionsAction::Back},
{"DEC", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC},
{"DMS", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS},
{"UTM", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM},
{"MGRS", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS},
{"OLC", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC},
{"OSGR", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR},
{"MLS", OptionsAction::Select, meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS},
};
constexpr size_t formatCount = sizeof(formatOptionsHigh) / sizeof(formatOptionsHigh[0]);
static std::array<const char *, formatCount> formatLabelsHigh{};
static std::array<const char *, formatCount> formatLabelsLow{};
auto onSelection = [](const GPSFormatOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
static const char *optionsArray[] = {"Back",
(currentResolution == ScreenResolution::High) ? "Decimal Degrees" : "DEC",
(currentResolution == ScreenResolution::High) ? "Degrees Minutes Seconds" : "DMS",
(currentResolution == ScreenResolution::High) ? "Universal Transverse Mercator" : "UTM",
(currentResolution == ScreenResolution::High) ? "Military Grid Reference System"
: "MGRS",
(currentResolution == ScreenResolution::High) ? "Open Location Code" : "OLC",
(currentResolution == ScreenResolution::High) ? "Ordnance Survey Grid Ref" : "OSGR",
(currentResolution == ScreenResolution::High) ? "Maidenhead Locator" : "MLS"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "GPS Format";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 8;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DEC;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 2) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_DMS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 3) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_UTM;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 4) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MGRS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 5) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 6) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_OSGR;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else if (selected == 7) {
uiconfig.gps_format = meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
} else {
menuQueue = position_base_menu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
if (uiconfig.gps_format == option.value) {
return;
}
uiconfig.gps_format = option.value;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
};
BannerOverlayOptions bannerOptions;
int initialSelection = 0;
if (currentResolution == ScreenResolution::High) {
bannerOptions = createStaticBannerOptions("GPS Format", formatOptionsHigh, formatLabelsHigh, onSelection);
for (size_t i = 0; i < formatCount; ++i) {
if (formatOptionsHigh[i].hasValue && uiconfig.gps_format == formatOptionsHigh[i].value) {
initialSelection = static_cast<int>(i);
break;
}
}
} else {
bannerOptions = createStaticBannerOptions("GPS Format", formatOptionsLow, formatLabelsLow, onSelection);
for (size_t i = 0; i < formatCount; ++i) {
if (formatOptionsLow[i].hasValue && uiconfig.gps_format == formatOptionsLow[i].value) {
initialSelection = static_cast<int>(i);
break;
}
}
}
bannerOptions.InitialSelected = initialSelection;
bannerOptions.InitialSelected = uiconfig.gps_format + 1;
screen->showOverlayBanner(bannerOptions);
}
@@ -1819,63 +1701,100 @@ void menuHandler::switchToMUIMenu()
void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
{
static const ScreenColorOption colorOptions[] = {
{"Back", OptionsAction::Back},
{"Default", OptionsAction::Select, ScreenColor(0, 0, 0, true)},
{"Meshtastic Green", OptionsAction::Select, ScreenColor(103, 234, 148)},
{"Yellow", OptionsAction::Select, ScreenColor(255, 255, 128)},
{"Red", OptionsAction::Select, ScreenColor(255, 64, 64)},
{"Orange", OptionsAction::Select, ScreenColor(255, 160, 20)},
{"Purple", OptionsAction::Select, ScreenColor(204, 153, 255)},
{"Blue", OptionsAction::Select, ScreenColor(0, 0, 255)},
{"Teal", OptionsAction::Select, ScreenColor(16, 102, 102)},
{"Cyan", OptionsAction::Select, ScreenColor(0, 255, 255)},
{"Ice", OptionsAction::Select, ScreenColor(173, 216, 230)},
{"Pink", OptionsAction::Select, ScreenColor(255, 105, 180)},
{"White", OptionsAction::Select, ScreenColor(255, 255, 255)},
{"Gray", OptionsAction::Select, ScreenColor(128, 128, 128)},
};
constexpr size_t colorCount = sizeof(colorOptions) / sizeof(colorOptions[0]);
static std::array<const char *, colorCount> colorLabels{};
auto bannerOptions = createStaticBannerOptions(
"Select Screen Color", colorOptions, colorLabels, [display](const ScreenColorOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuQueue = system_base_menu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
static const char *optionsArray[] = {
"Back", "Default", "Meshtastic Green", "Yellow", "Red", "Orange", "Purple", "Blue", "Teal", "Cyan", "Ice", "Pink",
"White", "Gray"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Select Screen Color";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 14;
bannerOptions.bannerCallback = [display](int selected) -> void {
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || \
HAS_TFT || defined(HACKADAY_COMMUNICATOR)
const ScreenColor &color = option.value;
if (color.useVariant) {
LOG_INFO("Setting color to system default or defined variant");
} else {
LOG_INFO("Setting color to %s", option.label);
}
uint8_t r = color.r;
uint8_t g = color.g;
uint8_t b = color.b;
uint8_t TFT_MESH_r = 0;
uint8_t TFT_MESH_g = 0;
uint8_t TFT_MESH_b = 0;
if (selected == 1) {
LOG_INFO("Setting color to system default or defined variant");
// Given just before we set all these to zero, we will allow this to go through
} else if (selected == 2) {
LOG_INFO("Setting color to Meshtastic Green");
TFT_MESH_r = 103;
TFT_MESH_g = 234;
TFT_MESH_b = 148;
} else if (selected == 3) {
LOG_INFO("Setting color to Yellow");
TFT_MESH_r = 255;
TFT_MESH_g = 255;
TFT_MESH_b = 128;
} else if (selected == 4) {
LOG_INFO("Setting color to Red");
TFT_MESH_r = 255;
TFT_MESH_g = 64;
TFT_MESH_b = 64;
} else if (selected == 5) {
LOG_INFO("Setting color to Orange");
TFT_MESH_r = 255;
TFT_MESH_g = 160;
TFT_MESH_b = 20;
} else if (selected == 6) {
LOG_INFO("Setting color to Purple");
TFT_MESH_r = 204;
TFT_MESH_g = 153;
TFT_MESH_b = 255;
} else if (selected == 7) {
LOG_INFO("Setting color to Blue");
TFT_MESH_r = 0;
TFT_MESH_g = 0;
TFT_MESH_b = 255;
} else if (selected == 8) {
LOG_INFO("Setting color to Teal");
TFT_MESH_r = 16;
TFT_MESH_g = 102;
TFT_MESH_b = 102;
} else if (selected == 9) {
LOG_INFO("Setting color to Cyan");
TFT_MESH_r = 0;
TFT_MESH_g = 255;
TFT_MESH_b = 255;
} else if (selected == 10) {
LOG_INFO("Setting color to Ice");
TFT_MESH_r = 173;
TFT_MESH_g = 216;
TFT_MESH_b = 230;
} else if (selected == 11) {
LOG_INFO("Setting color to Pink");
TFT_MESH_r = 255;
TFT_MESH_g = 105;
TFT_MESH_b = 180;
} else if (selected == 12) {
LOG_INFO("Setting color to White");
TFT_MESH_r = 255;
TFT_MESH_g = 255;
TFT_MESH_b = 255;
} else if (selected == 13) {
LOG_INFO("Setting color to Gray");
TFT_MESH_r = 128;
TFT_MESH_g = 128;
TFT_MESH_b = 128;
} else {
menuQueue = system_base_menu;
screen->runNow();
}
if (selected != 0) {
display->setColor(BLACK);
display->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
display->setColor(WHITE);
if (color.useVariant || (r == 0 && g == 0 && b == 0)) {
if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) {
#ifdef TFT_MESH_OVERRIDE
TFT_MESH = TFT_MESH_OVERRIDE;
#else
TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
#endif
} else {
TFT_MESH = COLOR565(r, g, b);
TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
}
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
@@ -1883,40 +1802,16 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
#endif
screen->setFrames(graphics::Screen::FOCUS_SYSTEM);
if (color.useVariant || (r == 0 && g == 0 && b == 0)) {
if (TFT_MESH_r == 0 && TFT_MESH_g == 0 && TFT_MESH_b == 0) {
uiconfig.screen_rgb_color = 0;
} else {
uiconfig.screen_rgb_color =
(static_cast<uint32_t>(r) << 16) | (static_cast<uint32_t>(g) << 8) | static_cast<uint32_t>(b);
uiconfig.screen_rgb_color = (TFT_MESH_r << 16) | (TFT_MESH_g << 8) | TFT_MESH_b;
}
LOG_INFO("Storing Value of %d to uiconfig.screen_rgb_color", uiconfig.screen_rgb_color);
saveUIConfig();
#endif
});
int initialSelection = 0;
if (uiconfig.screen_rgb_color == 0) {
initialSelection = 1;
} else {
uint32_t currentColor = uiconfig.screen_rgb_color;
for (size_t i = 0; i < colorCount; ++i) {
if (!colorOptions[i].hasValue) {
continue;
}
const ScreenColor &color = colorOptions[i].value;
if (color.useVariant) {
continue;
}
uint32_t encoded =
(static_cast<uint32_t>(color.r) << 16) | (static_cast<uint32_t>(color.g) << 8) | static_cast<uint32_t>(color.b);
if (encoded == currentColor) {
initialSelection = static_cast<int>(i);
break;
}
}
}
bannerOptions.InitialSelected = initialSelection;
#endif
};
screen->showOverlayBanner(bannerOptions);
}

View File

@@ -128,28 +128,7 @@ template <typename T> struct MenuOption {
MenuOption(const char *labelIn, OptionsAction actionIn) : label(labelIn), action(actionIn), hasValue(false), value() {}
};
struct ScreenColor {
uint8_t r;
uint8_t g;
uint8_t b;
bool useVariant;
ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false)
: r(rIn), g(gIn), b(bIn), useVariant(variantIn)
{
}
};
using RadioPresetOption = MenuOption<meshtastic_Config_LoRaConfig_ModemPreset>;
using LoraRegionOption = MenuOption<meshtastic_Config_LoRaConfig_RegionCode>;
using TimezoneOption = MenuOption<const char *>;
using CompassOption = MenuOption<meshtastic_CompassMode>;
using ScreenColorOption = MenuOption<ScreenColor>;
using GPSToggleOption = MenuOption<meshtastic_Config_PositionConfig_GpsMode>;
using GPSFormatOption = MenuOption<meshtastic_DeviceUIConfig_GpsCoordinateFormat>;
using NodeNameOption = MenuOption<bool>;
using PositionMenuOption = MenuOption<int>;
using ClockFaceOption = MenuOption<bool>;
} // namespace graphics
#endif

View File

@@ -238,12 +238,6 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key)
// call to startRetransmission.
packetPool.release(p);
#ifdef USE_ADAPTIVE_CODING_RATE
if (iface) {
iface->clearAdaptiveCodingRateState(getFrom(p), p->id);
}
#endif
return true;
} else
return false;

View File

@@ -520,10 +520,6 @@ void RadioInterface::applyModemConfig()
sf = 12;
break;
}
if (loraConfig.coding_rate >= 5 && loraConfig.coding_rate <= 8 && loraConfig.coding_rate != cr) {
cr = loraConfig.coding_rate;
LOG_INFO("Using custom Coding Rate %u", cr);
}
} else {
sf = loraConfig.spread_factor;
cr = loraConfig.coding_rate;
@@ -618,13 +614,6 @@ void RadioInterface::applyModemConfig()
slotTimeMsec = computeSlotTimeMsec();
preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw);
#ifdef USE_ADAPTIVE_CODING_RATE
if (adaptiveCrOverride >= 5 && adaptiveCrOverride <= 8 && cr != adaptiveCrOverride) {
cr = adaptiveCrOverride;
LOG_DEBUG("Adaptive coding rate override set to %u", cr);
}
#endif
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,
channel_num, power);
@@ -737,70 +726,3 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
sendingPacket = p;
return p->encrypted.size + sizeof(PacketHeader);
}
#ifdef USE_ADAPTIVE_CODING_RATE
uint8_t RadioInterface::computeAdaptiveCodingRate(uint8_t attempt) const
{
if (attempt <= 1) {
return 5; // Attempt 1: 4/5
}
if (attempt == 2) {
return 7; // Attempt 2: 4/7
}
return 8; // Attempt 3+: 4/8
}
uint64_t RadioInterface::adaptiveKey(NodeNum from, PacketId id) const
{
return (static_cast<uint64_t>(from) << 32) | id;
}
void RadioInterface::pruneAdaptiveAttempts(uint32_t now)
{
const uint32_t expiryMsec = 5 * 60 * 1000UL; // drop state after 5 minutes
for (auto it = adaptiveAttempts.begin(); it != adaptiveAttempts.end();) {
if (now - it->second.lastUseMsec > expiryMsec) {
it = adaptiveAttempts.erase(it);
} else {
++it;
}
}
}
uint8_t RadioInterface::recordAdaptiveAttempt(const meshtastic_MeshPacket *p)
{
const uint32_t now = millis();
pruneAdaptiveAttempts(now);
const uint64_t key = adaptiveKey(getFrom(p), p->id);
auto &state = adaptiveAttempts[key];
if (state.attempts < UINT8_MAX) {
state.attempts++;
}
state.lastUseMsec = now;
return state.attempts;
}
bool RadioInterface::applyAdaptiveCodingRate(const meshtastic_MeshPacket *p)
{
const uint8_t attempt = recordAdaptiveAttempt(p);
const uint8_t desiredCr = computeAdaptiveCodingRate(attempt);
if (desiredCr < 5 || desiredCr > 8) {
return false;
}
adaptiveCrOverride = desiredCr;
if (cr != desiredCr) {
cr = desiredCr;
reconfigure();
return true;
}
return false;
}
void RadioInterface::clearAdaptiveCodingRateState(NodeNum from, PacketId id)
{
adaptiveAttempts.erase(adaptiveKey(from, id));
}
#endif

View File

@@ -6,9 +6,6 @@
#include "PointerQueue.h"
#include "airtime.h"
#include "error.h"
#ifdef USE_ADAPTIVE_CODING_RATE
#include <unordered_map>
#endif
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
@@ -223,11 +220,6 @@ class RadioInterface
// Whether we use the default frequency slot given our LoRa config (region and modem preset)
static bool uses_default_frequency_slot;
#ifdef USE_ADAPTIVE_CODING_RATE
/** Clear adaptive coding rate tracking for a completed packet id */
void clearAdaptiveCodingRateState(NodeNum from, PacketId id);
#endif
protected:
int8_t power = 17; // Set by applyModemConfig()
@@ -258,20 +250,6 @@ class RadioInterface
*/
virtual void saveChannelNum(uint32_t savedChannelNum);
#ifdef USE_ADAPTIVE_CODING_RATE
bool applyAdaptiveCodingRate(const meshtastic_MeshPacket *p);
struct AdaptiveAttemptState {
uint8_t attempts = 0;
uint32_t lastUseMsec = 0;
};
std::unordered_map<uint64_t, AdaptiveAttemptState> adaptiveAttempts;
uint8_t adaptiveCrOverride = 0;
uint8_t recordAdaptiveAttempt(const meshtastic_MeshPacket *p);
uint8_t computeAdaptiveCodingRate(uint8_t attempt) const;
void pruneAdaptiveAttempts(uint32_t now);
uint64_t adaptiveKey(NodeNum from, PacketId id) const;
#endif
private:
/**
* Convert our modemConfig enum into wf, sf, etc...

View File

@@ -540,9 +540,6 @@ bool RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
packetPool.release(txp);
return false;
} else {
#ifdef USE_ADAPTIVE_CODING_RATE
applyAdaptiveCodingRate(txp);
#endif
configHardwareForSend(); // must be after setStandby
size_t numbytes = beginSending(txp);

View File

@@ -113,7 +113,7 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
// Check 3: role check (moderate cost - multiple comparisons)
if (!IS_ONE_OF(node->user.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
continue;
}

View File

@@ -62,11 +62,6 @@ template <typename T> bool SX126xInterface<T>::init()
digitalWrite(LORA_PA_TX_EN, LOW);
#endif
#ifdef RF95_FAN_EN
digitalWrite(RF95_FAN_EN, HIGH);
pinMode(RF95_FAN_EN, OUTPUT);
#endif
#if ARCH_PORTDUINO
tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) {
@@ -90,13 +85,6 @@ template <typename T> bool SX126xInterface<T>::init()
power = -9;
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
#ifdef SX126X_PA_RAMP_US
// Set custom PA ramp time for boards requiring longer stabilization (e.g., T-Beam 1W needs >800us)
if (res == RADIOLIB_ERR_NONE) {
lora.setPaRampTime(SX126X_PA_RAMP_US);
}
#endif
// \todo Display actual typename of the adapter, not just `SX126x`
LOG_INFO("SX126x init result %d", res);
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)

View File

@@ -53,7 +53,7 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#include "Sensor/LTR390UVSensor.h"
#endif
#if __has_include(MESHTASTIC_BME680_HEADER)
#if __has_include(<bsec2.h>)
#include "Sensor/BME680Sensor.h"
#endif
@@ -214,7 +214,7 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include(<Adafruit_LTR390.h>)
addSensor<LTR390UVSensor>(i2cScanner, ScanI2C::DeviceType::LTR390UV);
#endif
#if __has_include(MESHTASTIC_BME680_HEADER)
#if __has_include(<bsec2.h>)
addSensor<BME680Sensor>(i2cScanner, ScanI2C::DeviceType::BME_680);
#endif
#if __has_include(<Adafruit_BMP280.h>)

View File

@@ -136,12 +136,12 @@ void HealthTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *
display->drawString(x, y += _fontHeight(FONT_SMALL), tempStr);
if (lastMeasurement.variant.health_metrics.has_heart_bpm) {
char heartStr[32];
snprintf(heartStr, sizeof(heartStr), "Heart Rate: %u bpm", lastMeasurement.variant.health_metrics.heart_bpm);
snprintf(heartStr, sizeof(heartStr), "Heart Rate: %.0f bpm", lastMeasurement.variant.health_metrics.heart_bpm);
display->drawString(x, y += _fontHeight(FONT_SMALL), heartStr);
}
if (lastMeasurement.variant.health_metrics.has_spO2) {
char spo2Str[32];
snprintf(spo2Str, sizeof(spo2Str), "spO2: %u %%", lastMeasurement.variant.health_metrics.spO2);
snprintf(spo2Str, sizeof(spo2Str), "spO2: %.0f %%", lastMeasurement.variant.health_metrics.spO2);
display->drawString(x, y += _fontHeight(FONT_SMALL), spo2Str);
}
}

View File

@@ -1,6 +1,6 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER)
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<bsec2.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "BME680Sensor.h"
@@ -10,7 +10,6 @@
BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {}
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
int32_t BME680Sensor::runOnce()
{
if (!bme680.run()) {
@@ -18,13 +17,10 @@ int32_t BME680Sensor::runOnce()
}
return 35;
}
#endif // defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
status = 0;
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
if (!bme680.begin(dev->address.address, *bus))
checkStatus("begin");
@@ -46,25 +42,12 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
if (status == 0)
LOG_DEBUG("BME680Sensor::runOnce: bme680.status %d", bme680.status);
#else
bme680 = makeBME680(bus);
if (!bme680->begin(dev->address.address)) {
LOG_ERROR("Init sensor: %s failed at begin()", sensorName);
return status;
}
status = 1;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
initI2CSensor();
return status;
}
bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0)
return false;
@@ -82,27 +65,9 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
// Check if we need to save state to filesystem (every STATE_SAVE_PERIOD ms)
measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal;
updateState();
#else
if (!bme680->performReading()) {
LOG_ERROR("BME680Sensor::getMetrics: performReading failed");
return false;
}
measurement->variant.environment_metrics.has_temperature = true;
measurement->variant.environment_metrics.has_relative_humidity = true;
measurement->variant.environment_metrics.has_barometric_pressure = true;
measurement->variant.environment_metrics.has_gas_resistance = true;
measurement->variant.environment_metrics.temperature = bme680->readTemperature();
measurement->variant.environment_metrics.relative_humidity = bme680->readHumidity();
measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F;
measurement->variant.environment_metrics.gas_resistance = bme680->readGas() / 1000.0;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
return true;
}
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
void BME680Sensor::loadState()
{
#ifdef FSCom
@@ -179,6 +144,5 @@ void BME680Sensor::checkStatus(const char *functionName)
else if (bme680.sensor.status > BME68X_OK)
LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status);
}
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif

View File

@@ -1,40 +1,23 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER)
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<bsec2.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#include <bme68xLibrary.h>
#include <bsec2.h>
#else
#include <Adafruit_BME680.h>
#include <memory>
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis()
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
const uint8_t bsec_config[] = {
#include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt"
};
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
class BME680Sensor : public TelemetrySensor
{
private:
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
Bsec2 bme680;
#else
using BME680Ptr = std::unique_ptr<Adafruit_BME680>;
static BME680Ptr makeBME680(TwoWire *bus) { return std::make_unique<Adafruit_BME680>(bus); }
BME680Ptr bme680;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
protected:
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
const char *bsecConfigFileName = "/prefs/bsec.dat";
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
uint8_t accuracy = 0;
@@ -51,13 +34,10 @@ class BME680Sensor : public TelemetrySensor
void loadState();
void updateState();
void checkStatus(const char *functionName);
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
public:
BME680Sensor();
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
virtual int32_t runOnce() override;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};

View File

@@ -1,8 +1,6 @@
#include "SerialConsole.h"
#include "concurrency/OSThread.h"
#include "gps/RTC.h"
#include "mesh/MeshRadio.h"
#include "mesh/NodeDB.h"
#include "TestUtil.h"
@@ -16,19 +14,5 @@ void initializeTestEnvironment()
tv.tv_usec = 0;
perhapsSetRTC(RTCQualityNTP, &tv);
#endif
concurrency::OSThread::setup();
}
void initializeTestEnvironmentMinimal()
{
// Only satisfy OSThread assertions; skip SerialConsole and platform-specific setup
concurrency::hasBeenSetup = true;
// Ensure region/config globals are sane before any RadioInterface instances compute slot timing
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
config.lora.use_preset = true;
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
initRegion();
concurrency::OSThread::setup();
}

View File

@@ -1,7 +1,4 @@
#pragma once
// Initialize testing environment.
void initializeTestEnvironment();
// Minimal init without creating SerialConsole or portduino peripherals (useful for lightweight logic tests)
void initializeTestEnvironmentMinimal();
void initializeTestEnvironment();

View File

@@ -1,164 +0,0 @@
#include <Arduino.h>
#include <unity.h>
#include "TestUtil.h"
// Ensure adaptive coding rate logic is available during tests
#ifndef USE_ADAPTIVE_CODING_RATE
#define USE_ADAPTIVE_CODING_RATE 1
#endif
#include "mesh/RadioInterface.h"
class TestRadio : public RadioInterface
{
public:
bool applyForTest(const meshtastic_MeshPacket *p) { return applyAdaptiveCodingRate(p); }
uint8_t getAttempts(NodeNum from, PacketId id)
{
#ifdef USE_ADAPTIVE_CODING_RATE
auto it = adaptiveAttempts.find(adaptiveKey(from, id));
return (it == adaptiveAttempts.end()) ? 0 : it->second.attempts;
#else
(void)from;
(void)id;
return 0;
#endif
}
#ifdef USE_ADAPTIVE_CODING_RATE
void setAdaptiveState(NodeNum from, PacketId id, uint8_t attempts, uint32_t lastUse)
{
adaptiveAttempts[adaptiveKey(from, id)] = {attempts, lastUse};
}
#endif
uint8_t currentCr() const { return cr; }
void setCrForTest(uint8_t value) { cr = value; }
ErrorCode send(meshtastic_MeshPacket *p) override
{
packetPool.release(p);
return ERRNO_OK;
}
uint32_t getPacketTime(uint32_t /*totalPacketLen*/, bool /*received*/ = false) override { return 0; }
bool reconfigure() override
{
reconfigureCount++;
lastCr = cr;
return true;
}
uint32_t reconfigureCount = 0;
uint8_t lastCr = 0;
};
void test_attempt_progression()
{
TestRadio radio;
meshtastic_MeshPacket packet = {};
packet.from = 0xABCDEF01;
packet.id = 0x1;
TEST_ASSERT_FALSE(radio.applyForTest(&packet));
TEST_ASSERT_EQUAL_UINT8(1, radio.getAttempts(packet.from, packet.id));
TEST_ASSERT_EQUAL_UINT8(5, radio.currentCr());
TEST_ASSERT_EQUAL_UINT32(0, radio.reconfigureCount);
TEST_ASSERT_TRUE(radio.applyForTest(&packet));
TEST_ASSERT_EQUAL_UINT8(2, radio.getAttempts(packet.from, packet.id));
TEST_ASSERT_EQUAL_UINT8(7, radio.currentCr());
TEST_ASSERT_EQUAL_UINT32(1, radio.reconfigureCount);
TEST_ASSERT_EQUAL_UINT8(7, radio.lastCr);
TEST_ASSERT_TRUE(radio.applyForTest(&packet));
TEST_ASSERT_EQUAL_UINT8(3, radio.getAttempts(packet.from, packet.id));
TEST_ASSERT_EQUAL_UINT8(8, radio.currentCr());
TEST_ASSERT_EQUAL_UINT32(2, radio.reconfigureCount);
TEST_ASSERT_EQUAL_UINT8(8, radio.lastCr);
}
void test_attempts_are_per_packet()
{
TestRadio radio;
meshtastic_MeshPacket first = {};
first.from = 0x1001;
first.id = 0xA;
meshtastic_MeshPacket second = {};
second.from = 0x1001;
second.id = 0xB;
radio.applyForTest(&first);
radio.applyForTest(&second);
radio.applyForTest(&first);
TEST_ASSERT_EQUAL_UINT8(2, radio.getAttempts(first.from, first.id));
TEST_ASSERT_EQUAL_UINT8(1, radio.getAttempts(second.from, second.id));
TEST_ASSERT_EQUAL_UINT8(7, radio.currentCr());
}
void test_clear_resets_attempts_and_rate()
{
TestRadio radio;
meshtastic_MeshPacket packet = {};
packet.from = 0xCAFE;
packet.id = 0x55;
radio.applyForTest(&packet);
radio.applyForTest(&packet);
radio.applyForTest(&packet);
radio.reconfigureCount = 0;
radio.setCrForTest(8);
radio.clearAdaptiveCodingRateState(packet.from, packet.id);
TEST_ASSERT_TRUE(radio.applyForTest(&packet));
TEST_ASSERT_EQUAL_UINT8(1, radio.getAttempts(packet.from, packet.id));
TEST_ASSERT_EQUAL_UINT8(5, radio.currentCr());
TEST_ASSERT_EQUAL_UINT32(1, radio.reconfigureCount);
}
void test_prunes_expired_state()
{
TestRadio radio;
meshtastic_MeshPacket packet = {};
packet.from = 0xBEEF;
packet.id = 0x99;
radio.applyForTest(&packet);
#ifdef USE_ADAPTIVE_CODING_RATE
const uint32_t now = millis();
radio.setAdaptiveState(packet.from, packet.id, 3, now - (5 * 60 * 1000UL + 50));
#endif
radio.reconfigureCount = 0;
radio.setCrForTest(5);
TEST_ASSERT_FALSE(radio.applyForTest(&packet));
TEST_ASSERT_EQUAL_UINT8(1, radio.getAttempts(packet.from, packet.id));
TEST_ASSERT_EQUAL_UINT32(0, radio.reconfigureCount);
}
void setup()
{
printf("AdaptiveCodingRate test setup start\n");
fflush(stdout);
// Use minimal init to avoid pulling in SerialConsole/portduino peripherals for these logic-only tests
initializeTestEnvironmentMinimal();
printf("AdaptiveCodingRate test init done\n");
fflush(stdout);
UNITY_BEGIN();
RUN_TEST(test_attempt_progression);
RUN_TEST(test_attempts_are_per_packet);
RUN_TEST(test_clear_resets_attempts_and_rate);
RUN_TEST(test_prunes_expired_state);
UNITY_END();
}
void loop()
{
delay(1000);
}

View File

@@ -38,6 +38,7 @@ build_flags =
-DAXP_DEBUG_PORT=Serial
-DCONFIG_BT_NIMBLE_ENABLED
-DCONFIG_BT_NIMBLE_MAX_BONDS=6 # default is 3
-DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
-DCONFIG_BT_NIMBLE_MAX_CCCDS=20
-DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
@@ -60,11 +61,11 @@ lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master
https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
h2zero/NimBLE-Arduino@^1.4.3
h2zero/NimBLE-Arduino@2.3.7
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
https://github.com/lewisxhe/XPowersLib/archive/v0.3.2.zip
# renovate: datasource=custom.pio depName=XPowersLib packageName=lewisxhe/library/XPowersLib
lewisxhe/XPowersLib@0.3.2
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto

View File

@@ -5,4 +5,4 @@ extends = esp32_common
custom_esp32_kind = esp32
build_flags =
${esp32_common.build_flags}
${esp32_common.build_flags}

View File

@@ -4,3 +4,8 @@ custom_esp32_kind = esp32c3
monitor_speed = 115200
monitor_filters = esp32_c3_exception_decoder
build_flags =
${esp32_common.build_flags}
-DCONFIG_BT_NIMBLE_EXT_ADV=1
-DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2

View File

@@ -3,3 +3,8 @@ extends = esp32_common
custom_esp32_kind = esp32s3
monitor_speed = 115200
build_flags =
${esp32_common.build_flags}
-DCONFIG_BT_NIMBLE_EXT_ADV=1
-DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2

View File

@@ -55,6 +55,5 @@
#define SX126X_RESET 14
#define SX126X_RXEN 47
#define SX126X_TXEN RADIOLIB_NC // Assuming that DIO2 is connected to TXEN pin
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#endif

View File

@@ -13,7 +13,8 @@ build_flags =
[env:rak3112]
extends = esp32s3_base
board = wiscore_rak3312
board_level = extra
board_level = pr
board_check = true
upload_protocol = esptool
build_flags =

View File

@@ -1,25 +0,0 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
#define USB_VID 0x303a
#define USB_PID 0x1001
static const uint8_t TX = 43;
static const uint8_t RX = 44;
// I2C for OLED and sensors
static const uint8_t SDA = 8;
static const uint8_t SCL = 9;
// Default SPI mapped to Radio/SD
static const uint8_t SS = 15; // LoRa CS
static const uint8_t MOSI = 11;
static const uint8_t MISO = 12;
static const uint8_t SCK = 13;
// SD Card CS
#define SDCARD_CS 10
#endif /* Pins_Arduino_h */

View File

@@ -1,14 +0,0 @@
; LilyGo T-Beam-1W (1 Watt LoRa with external PA)
[env:t-beam-1w]
extends = esp32s3_base
board = t-beam-1w
board_build.partitions = default_8MB.csv
board_check = true
lib_deps =
${esp32s3_base.lib_deps}
build_flags =
${esp32s3_base.build_flags}
-I variants/esp32s3/t-beam-1w
-D T_BEAM_1W

View File

@@ -1,97 +0,0 @@
// LilyGo T-Beam-1W variant.h
// Configuration based on LilyGO utilities.h and RF documentation
// I2C for OLED display (SH1106 at 0x3C)
#define I2C_SDA 8
#define I2C_SCL 9
// GPS - Quectel L76K
#define GPS_RX_PIN 5
#define GPS_TX_PIN 6
#define GPS_1PPS_PIN 7
#define GPS_WAKEUP_PIN 16 // GPS_EN_PIN in LilyGO code
#define HAS_GPS 1
#define GPS_BAUDRATE 9600
// Buttons
#define BUTTON_PIN 0 // BUTTON 1
#define BUTTON_PIN_ALT 17 // BUTTON 2
// SPI (shared by LoRa and SD)
#define SPI_MOSI 11
#define SPI_SCK 13
#define SPI_MISO 12
#define SPI_CS 10
// SD Card
#define HAS_SDCARD
#define SDCARD_USE_SPI1
#define SDCARD_CS SPI_CS
// LoRa Radio - SX1262 with 1W PA
#define USE_SX1262
#define LORA_SCK SPI_SCK
#define LORA_MISO SPI_MISO
#define LORA_MOSI SPI_MOSI
#define LORA_CS 15
#define LORA_RESET 3
#define LORA_DIO1 1
#define LORA_BUSY 38
// CRITICAL: Radio power enable - MUST be HIGH before lora.begin()!
// GPIO 40 powers the SX1262 + PA module via LDO
#define SX126X_POWER_EN 40
// TX power offset for external PA (0 = no offset, full SX1262 power)
#define TX_GAIN_LORA 10
#ifdef USE_SX1262
#define SX126X_CS LORA_CS
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY LORA_BUSY
#define SX126X_RESET LORA_RESET
// RF switching configuration for 1W PA module
// DIO2 controls PA (via SX126X_DIO2_AS_RF_SWITCH)
// CTRL PIN (GPIO 21) controls LNA - must be HIGH during RX
// Truth table: DIO2=1,CTRL=0 → TX (PA on, LNA off)
// DIO2=0,CTRL=1 → RX (PA off, LNA on)
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_RXEN 21 // LNA enable - HIGH during RX
// TCXO voltage - required for radio init
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define SX126X_MAX_POWER 22
#endif
// LED
#define LED_PIN 18
#define LED_STATE_ON 1 // HIGH = ON
// Battery ADC
#define BATTERY_PIN 4
#define ADC_CHANNEL ADC1_GPIO4_CHANNEL
#define BATTERY_SENSE_SAMPLES 30
#define ADC_MULTIPLIER 2.9333
// NTC temperature sensor
#define NTC_PIN 14
// Fan control
#define FAN_CTRL_PIN 41
// Meshtastic standard fan control pin macro
#define RF95_FAN_EN FAN_CTRL_PIN
// PA Ramp Time - T-Beam 1W requires >800us stabilization (default is 200us)
// Value 0x05 = RADIOLIB_SX126X_PA_RAMP_800U
#define SX126X_PA_RAMP_US 0x05
// Display - SH1106 OLED (128x64)
#define USE_SH1106
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
// 32768 Hz crystal present
#define HAS_32768HZ 1

View File

@@ -34,8 +34,6 @@ lib_deps =
adafruit/Adafruit seesaw Library@1.7.9
# renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main
https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip
# renovate: datasource=custom.pio depName=adafruit/Adafruit BME680 Library packageName=adafruit/library/Adafruit BME680
adafruit/Adafruit BME680 Library@^2.0.5
build_flags =
${arduino_base.build_flags}

View File

@@ -2,9 +2,6 @@
extends = portduino_base
build_flags = ${portduino_base.build_flags} -I variants/native/portduino
-I /usr/include
-I /opt/homebrew/include
-L /opt/homebrew/lib -largp
-DUSE_ADAPTIVE_CODING_RATE
board = cross_platform
board_level = extra
lib_deps =
@@ -12,11 +9,6 @@ lib_deps =
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
melopero/Melopero RV3028@1.2.0
; Disable LovyanGFX for native test builds to avoid missing macOS system headers
lib_ignore =
${portduino_base.lib_ignore}
LovyanGFX
build_src_filter = ${portduino_base.build_src_filter}
[env:native]

View File

@@ -7,7 +7,6 @@ build_flags =
${nrf52840_base.build_flags}
-Ivariants/nrf52840/ELECROW-ThinkNode-M3
-DELECROW_ThinkNode_M3
-DUSE_ADAPTIVE_CODING_RATE
-DGPS_POWER_TOGGLE
-D CONFIG_NFCT_PINS_AS_GPIOS=1
-L "${platformio.libdeps_dir}/${this.__env__}/bsec2/src/cortex-m4/fpv4-sp-d16-hard"

View File

@@ -9,7 +9,6 @@ build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/heltec_mesh_pocket
-DHELTEC_MESH_POCKET
-DHELTEC_MESH_POCKET_BATTERY_5000
-DUSE_ADAPTIVE_CODING_RATE
-DUSE_EINK
-DEINK_DISPLAY_MODEL=GxEPD2_213_B74
-DEINK_WIDTH=250
@@ -39,7 +38,6 @@ build_flags =
-I variants/nrf52840/heltec_mesh_pocket
-D HELTEC_MESH_POCKET
-D HELTEC_MESH_POCKET_BATTERY_5000
-DUSE_ADAPTIVE_CODING_RATE
lib_deps =
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
${nrf52840_base.lib_deps}
@@ -56,7 +54,6 @@ build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/heltec_mesh_pocket
-DHELTEC_MESH_POCKET
-DHELTEC_MESH_POCKET_BATTERY_10000
-DUSE_ADAPTIVE_CODING_RATE
-DUSE_EINK
-DEINK_DISPLAY_MODEL=GxEPD2_213_B74
-DEINK_WIDTH=250
@@ -86,7 +83,6 @@ build_flags =
-I variants/nrf52840/heltec_mesh_pocket
-D HELTEC_MESH_POCKET
-D HELTEC_MESH_POCKET_BATTERY_10000
-DUSE_ADAPTIVE_CODING_RATE
lib_deps =
${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX
${nrf52840_base.lib_deps}

View File

@@ -7,7 +7,6 @@ build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/rak_wismeshtag
-D WISMESH_TAG
-D RAK_4631
-DUSE_ADAPTIVE_CODING_RATE
-DRADIOLIB_EXCLUDE_SX128X=1
-DRADIOLIB_EXCLUDE_SX127X=1
-DRADIOLIB_EXCLUDE_LR11X0=1

View File

@@ -7,7 +7,6 @@ build_flags = ${nrf52840_base.build_flags}
-Isrc/platform/nrf52/softdevice
-Isrc/platform/nrf52/softdevice/nrf52
-DTRACKER_T1000_E
-DUSE_ADAPTIVE_CODING_RATE
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL=1
-DMESHTASTIC_EXCLUDE_CANNEDMESSAGES=1
-DMESHTASTIC_EXCLUDE_SCREEN=1

View File

@@ -1,4 +1,4 @@
[VERSION]
major = 2
minor = 7
build = 18
build = 17