Merge branch 'master' into thinknode-m5

This commit is contained in:
Ben Meadors 2025-07-31 19:11:15 -05:00 committed by GitHub
commit 2906afad0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 127 additions and 18 deletions

View File

@ -2,7 +2,7 @@
extends = arduino_base extends = arduino_base
platform = platform =
# renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32 # renovate: datasource=custom.pio depName=platformio/ststm32 packageName=platformio/platform/ststm32
platformio/ststm32@19.2.0 platformio/ststm32@19.3.0
platform_packages = platform_packages =
# TODO renovate # TODO renovate
platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32/archive/2.10.1.zip

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
PYTHON=${PYTHON:-$(which python3 python|head -n 1)} PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
CHANGE_MODE=false CHANGE_MODE=false

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
# postinst script for meshtasticd # postinst script for meshtasticd
# #
# see: dh_installdeb(1) # see: dh_installdeb(1)

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
# postrm script for meshtasticd # postrm script for meshtasticd
# #
# see: dh_installdeb(1) # see: dh_installdeb(1)

@ -1 +1 @@
Subproject commit 9bac2886f9344f25716921467a82e8b0326107cd Subproject commit 1ecf94da9898ea0b8f2745bfe6bda2a8f2ca4073

View File

@ -1511,7 +1511,7 @@ bool GPS::lookForTime()
#ifdef GNSS_AIROHA #ifdef GNSS_AIROHA
uint8_t fix = reader.fixQuality(); uint8_t fix = reader.fixQuality();
if (fix > 0) { if (fix >= 1 && fix <= 5) {
if (lastFixStartMsec > 0) { if (lastFixStartMsec > 0) {
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) { if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
return false; return false;
@ -1566,7 +1566,7 @@ bool GPS::lookForLocation()
#ifdef GNSS_AIROHA #ifdef GNSS_AIROHA
if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) { if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) {
uint8_t fix = reader.fixQuality(); uint8_t fix = reader.fixQuality();
if (fix > 0) { if (fix >= 1 && fix <= 5) {
if (lastFixStartMsec > 0) { if (lastFixStartMsec > 0) {
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) { if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
return false; return false;

View File

@ -14,7 +14,9 @@
#include "modules/AdminModule.h" #include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h" #include "modules/CannedMessageModule.h"
#include "modules/KeyVerificationModule.h" #include "modules/KeyVerificationModule.h"
#include "modules/TraceRouteModule.h" #include "modules/TraceRouteModule.h"
#include <functional>
extern uint16_t TFT_MESH; extern uint16_t TFT_MESH;
@ -118,6 +120,22 @@ void menuHandler::TwelveHourPicker()
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
// Reusable confirmation prompt function
void menuHandler::showConfirmationBanner(const char *message, std::function<void()> onConfirm)
{
static const char *confirmOptions[] = {"No", "Yes"};
BannerOverlayOptions confirmBanner;
confirmBanner.message = message;
confirmBanner.optionsArrayPtr = confirmOptions;
confirmBanner.optionsCount = 2;
confirmBanner.bannerCallback = [onConfirm](int confirmSelected) -> void {
if (confirmSelected == 1) {
onConfirm();
}
};
screen->showOverlayBanner(confirmBanner);
}
void menuHandler::ClockFacePicker() void menuHandler::ClockFacePicker()
{ {
static const char *optionsArray[] = {"Back", "Digital", "Analog"}; static const char *optionsArray[] = {"Back", "Digital", "Analog"};
@ -294,7 +312,7 @@ void menuHandler::messageResponseMenu()
void menuHandler::homeBaseMenu() void menuHandler::homeBaseMenu()
{ {
enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Bluetooth, Sleep, enumEnd }; enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Sleep, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"}; static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back}; static int optionsEnumArray[enumEnd] = {Back};
@ -316,8 +334,6 @@ void menuHandler::homeBaseMenu()
optionsArray[options] = "New Freetext Msg"; optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext; optionsEnumArray[options++] = Freetext;
} }
optionsArray[options] = "Bluetooth Toggle";
optionsEnumArray[options++] = Bluetooth;
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Home Action"; bannerOptions.message = "Home Action";
@ -342,9 +358,6 @@ void menuHandler::homeBaseMenu()
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST); cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
} else if (selected == Freetext) { } else if (selected == Freetext) {
cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST); cannedMessageModule->LaunchFreetextWithDestination(NODENUM_BROADCAST);
} else if (selected == Bluetooth) {
menuQueue = bluetooth_toggle_menu;
screen->runNow();
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
@ -381,7 +394,7 @@ void menuHandler::textMessageBaseMenu()
void menuHandler::systemBaseMenu() void menuHandler::systemBaseMenu()
{ {
enum optionsNumbers { Back, Notifications, ScreenOptions, PowerMenu, Test, enumEnd }; enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"}; static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back}; static int optionsEnumArray[enumEnd] = {Back};
int options = 1; int options = 1;
@ -394,6 +407,9 @@ void menuHandler::systemBaseMenu()
optionsEnumArray[options++] = ScreenOptions; optionsEnumArray[options++] = ScreenOptions;
#endif #endif
optionsArray[options] = "Bluetooth Toggle";
optionsEnumArray[options++] = Bluetooth;
optionsArray[options] = "Reboot/Shutdown"; optionsArray[options] = "Reboot/Shutdown";
optionsEnumArray[options++] = PowerMenu; optionsEnumArray[options++] = PowerMenu;
@ -420,6 +436,9 @@ void menuHandler::systemBaseMenu()
} else if (selected == Test) { } else if (selected == Test) {
menuHandler::menuQueue = menuHandler::test_menu; menuHandler::menuQueue = menuHandler::test_menu;
screen->runNow(); screen->runNow();
} else if (selected == Bluetooth) {
menuQueue = bluetooth_toggle_menu;
screen->runNow();
} else if (selected == Back && !test_enabled) { } else if (selected == Back && !test_enabled) {
test_count++; test_count++;
if (test_count > 4) { if (test_count > 4) {

View File

@ -43,6 +43,7 @@ class menuHandler
static void LoraRegionPicker(uint32_t duration = 30000); static void LoraRegionPicker(uint32_t duration = 30000);
static void handleMenuSwitch(OLEDDisplay *display); static void handleMenuSwitch(OLEDDisplay *display);
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
static void clockMenu(); static void clockMenu();
static void TZPicker(); static void TZPicker();
static void TwelveHourPicker(); static void TwelveHourPicker();

View File

@ -31,6 +31,9 @@
#include "Throttle.h" #include "Throttle.h"
#include <RTC.h> #include <RTC.h>
// Flag to indicate a heartbeat was received and we should send queue status
bool heartbeatReceived = false;
PhoneAPI::PhoneAPI() PhoneAPI::PhoneAPI()
{ {
lastContactMsec = millis(); lastContactMsec = millis();
@ -155,6 +158,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
#endif #endif
case meshtastic_ToRadio_heartbeat_tag: case meshtastic_ToRadio_heartbeat_tag:
LOG_DEBUG("Got client heartbeat"); LOG_DEBUG("Got client heartbeat");
heartbeatReceived = true;
break; break;
default: default:
// Ignore nop messages // Ignore nop messages
@ -194,6 +198,17 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// In case we send a FromRadio packet // In case we send a FromRadio packet
memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); memset(&fromRadioScratch, 0, sizeof(fromRadioScratch));
// Respond to heartbeat by sending queue status
if (heartbeatReceived) {
memset(&fromRadioScratch, 0, sizeof(fromRadioScratch));
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_queueStatus_tag;
fromRadioScratch.queueStatus = router->getQueueStatus();
heartbeatReceived = false;
size_t numbytes = pb_encode_to_bytes(buf, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch);
LOG_DEBUG("FromRadio=STATE_SEND_QUEUE_STATUS, numbytes=%u", numbytes);
return numbytes;
}
// Advance states as needed // Advance states as needed
switch (state) { switch (state) {
case STATE_SEND_NOTHING: case STATE_SEND_NOTHING:

View File

@ -267,6 +267,9 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_RAK3312 = 106, meshtastic_HardwareModel_RAK3312 = 106,
/* Elecrow ThinkNode M5 https://www.elecrow.com/wiki/ThinkNode_M5_Meshtastic_LoRa_Signal_Transceiver_ESP32-S3.html */ /* Elecrow ThinkNode M5 https://www.elecrow.com/wiki/ThinkNode_M5_Meshtastic_LoRa_Signal_Transceiver_ESP32-S3.html */
meshtastic_HardwareModel_THINKNODE_M5 = 107, meshtastic_HardwareModel_THINKNODE_M5 = 107,
/* MeshSolar is an integrated power management and communication solution designed for outdoor low-power devices.
https://heltec.org/project/meshsolar/ */
meshtastic_HardwareModel_HELTEC_MESH_SOLAR = 108,
/* ------------------------------------------------------------------------------------------------------------------------------------------ /* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */ ------------------------------------------------------------------------------------------------------------------------------------------ */

View File

@ -82,7 +82,10 @@ typedef enum _meshtastic_ModuleConfig_SerialConfig_Serial_Mode {
meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85 = 6, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85 = 6,
/* VE.Direct is a serial protocol used by Victron Energy products /* VE.Direct is a serial protocol used by Victron Energy products
https://beta.ivc.no/wiki/index.php/Victron_VE_Direct_DIY_Cable */ https://beta.ivc.no/wiki/index.php/Victron_VE_Direct_DIY_Cable */
meshtastic_ModuleConfig_SerialConfig_Serial_Mode_VE_DIRECT = 7 meshtastic_ModuleConfig_SerialConfig_Serial_Mode_VE_DIRECT = 7,
/* Used to configure and view some parameters of MeshSolar.
https://heltec.org/project/meshsolar/ */
meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG = 8
} meshtastic_ModuleConfig_SerialConfig_Serial_Mode; } meshtastic_ModuleConfig_SerialConfig_Serial_Mode;
/* TODO: REPLACE */ /* TODO: REPLACE */
@ -472,8 +475,8 @@ extern "C" {
#define _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Baud)(meshtastic_ModuleConfig_SerialConfig_Serial_Baud_BAUD_921600+1)) #define _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Baud)(meshtastic_ModuleConfig_SerialConfig_Serial_Baud_BAUD_921600+1))
#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN meshtastic_ModuleConfig_SerialConfig_Serial_Mode_DEFAULT #define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN meshtastic_ModuleConfig_SerialConfig_Serial_Mode_DEFAULT
#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MAX meshtastic_ModuleConfig_SerialConfig_Serial_Mode_VE_DIRECT #define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MAX meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG
#define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Mode)(meshtastic_ModuleConfig_SerialConfig_Serial_Mode_VE_DIRECT+1)) #define _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_ARRAYSIZE ((meshtastic_ModuleConfig_SerialConfig_Serial_Mode)(meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG+1))
#define _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE #define _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE
#define _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MAX meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK #define _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MAX meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK

View File

@ -235,6 +235,11 @@ bool isWifiAvailable()
#ifdef USE_WS5500 #ifdef USE_WS5500
} else if (config.network.eth_enabled) { } else if (config.network.eth_enabled) {
return true; return true;
#endif
#ifndef ARCH_PORTDUINO
} else if (WiFi.status() == WL_CONNECTED) {
// it's likely we have wifi now, but user intends to turn it off in config!
return true;
#endif #endif
} else { } else {
return false; return false;

View File

@ -40,6 +40,9 @@ extern ScanI2C::DeviceAddress cardkb_found;
extern bool graphics::isMuted; extern bool graphics::isMuted;
static const char *cannedMessagesConfigFile = "/prefs/cannedConf.proto"; static const char *cannedMessagesConfigFile = "/prefs/cannedConf.proto";
static NodeNum lastDest = NODENUM_BROADCAST;
static uint8_t lastChannel = 0;
static bool lastDestSet = false;
meshtastic_CannedMessageModuleConfig cannedMessageModuleConfig; meshtastic_CannedMessageModuleConfig cannedMessageModuleConfig;
@ -63,8 +66,18 @@ CannedMessageModule::CannedMessageModule()
void CannedMessageModule::LaunchWithDestination(NodeNum newDest, uint8_t newChannel) void CannedMessageModule::LaunchWithDestination(NodeNum newDest, uint8_t newChannel)
{ {
// Use the requested destination, unless it's "broadcast" and we have a previous node/channel
if (newDest == NODENUM_BROADCAST && lastDestSet) {
newDest = lastDest;
newChannel = lastChannel;
}
dest = newDest; dest = newDest;
channel = newChannel; channel = newChannel;
lastDest = dest;
lastChannel = channel;
lastDestSet = true;
// Rest of function unchanged...
// Always select the first real canned message on activation // Always select the first real canned message on activation
int firstRealMsgIdx = 0; int firstRealMsgIdx = 0;
for (int i = 0; i < messagesCount; ++i) { for (int i = 0; i < messagesCount; ++i) {
@ -84,10 +97,28 @@ void CannedMessageModule::LaunchWithDestination(NodeNum newDest, uint8_t newChan
notifyObservers(&e); notifyObservers(&e);
} }
void CannedMessageModule::LaunchRepeatDestination()
{
if (!lastDestSet) {
LaunchWithDestination(NODENUM_BROADCAST, 0);
} else {
LaunchWithDestination(lastDest, lastChannel);
}
}
void CannedMessageModule::LaunchFreetextWithDestination(NodeNum newDest, uint8_t newChannel) void CannedMessageModule::LaunchFreetextWithDestination(NodeNum newDest, uint8_t newChannel)
{ {
// Use the requested destination, unless it's "broadcast" and we have a previous node/channel
if (newDest == NODENUM_BROADCAST && lastDestSet) {
newDest = lastDest;
newChannel = lastChannel;
}
dest = newDest; dest = newDest;
channel = newChannel; channel = newChannel;
lastDest = dest;
lastChannel = channel;
lastDestSet = true;
runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; runState = CANNED_MESSAGE_RUN_STATE_FREETEXT;
requestFocus(); requestFocus();
UIFrameEvent e; UIFrameEvent e;
@ -479,6 +510,9 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event
if (destIndex < static_cast<int>(activeChannelIndices.size())) { if (destIndex < static_cast<int>(activeChannelIndices.size())) {
dest = NODENUM_BROADCAST; dest = NODENUM_BROADCAST;
channel = activeChannelIndices[destIndex]; channel = activeChannelIndices[destIndex];
lastDest = dest;
lastChannel = channel;
lastDestSet = true;
} else { } else {
int nodeIndex = destIndex - static_cast<int>(activeChannelIndices.size()); int nodeIndex = destIndex - static_cast<int>(activeChannelIndices.size());
if (nodeIndex >= 0 && nodeIndex < static_cast<int>(filteredNodes.size())) { if (nodeIndex >= 0 && nodeIndex < static_cast<int>(filteredNodes.size())) {
@ -486,6 +520,10 @@ int CannedMessageModule::handleDestinationSelectionInput(const InputEvent *event
if (selectedNode) { if (selectedNode) {
dest = selectedNode->num; dest = selectedNode->num;
channel = selectedNode->channel; channel = selectedNode->channel;
// Already saves here, but for clarity, also:
lastDest = dest;
lastChannel = channel;
lastDestSet = true;
} }
} }
} }
@ -595,8 +633,27 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo
// Normal canned message selection // Normal canned message selection
if (runState == CANNED_MESSAGE_RUN_STATE_INACTIVE || runState == CANNED_MESSAGE_RUN_STATE_DISABLED) { if (runState == CANNED_MESSAGE_RUN_STATE_INACTIVE || runState == CANNED_MESSAGE_RUN_STATE_DISABLED) {
} else { } else {
// Show confirmation dialog before sending canned message
NodeNum destNode = dest;
ChannelIndex chan = channel;
#if CANNED_MESSAGE_ADD_CONFIRMATION
graphics::menuHandler::showConfirmationBanner("Send message?", [this, destNode, chan, current]() {
this->sendText(destNode, chan, current, false);
payload = runState;
runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
currentMessageIndex = -1;
// Notify UI to regenerate frame set and redraw
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
screen->forceDisplay();
});
#else
payload = runState; payload = runState;
runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT;
#endif
// Do not immediately set runState; wait for confirmation
handled = true; handled = true;
} }
} }
@ -827,6 +884,9 @@ int CannedMessageModule::handleEmotePickerInput(const InputEvent *event)
void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const char *message, bool wantReplies) void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const char *message, bool wantReplies)
{ {
lastDest = dest;
lastChannel = channel;
lastDestSet = true;
// === Prepare packet === // === Prepare packet ===
meshtastic_MeshPacket *p = allocDataPacket(); meshtastic_MeshPacket *p = allocDataPacket();
p->to = dest; p->to = dest;
@ -1711,7 +1771,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
// Text: split by words and wrap inside word if needed // Text: split by words and wrap inside word if needed
String text = token.second; String text = token.second;
pos = 0; pos = 0;
while (pos < text.length()) { while (pos < static_cast<int>(text.length())) {
// Find next space (or end) // Find next space (or end)
int spacePos = text.indexOf(' ', pos); int spacePos = text.indexOf(' ', pos);
int endPos = (spacePos == -1) ? text.length() : spacePos + 1; // Include space int endPos = (spacePos == -1) ? text.length() : spacePos + 1; // Include space

View File

@ -59,6 +59,7 @@ class CannedMessageModule : public SinglePortModule, public Observable<const UIF
CannedMessageModule(); CannedMessageModule();
void LaunchWithDestination(NodeNum, uint8_t newChannel = 0); void LaunchWithDestination(NodeNum, uint8_t newChannel = 0);
void LaunchRepeatDestination();
void LaunchFreetextWithDestination(NodeNum, uint8_t newChannel = 0); void LaunchFreetextWithDestination(NodeNum, uint8_t newChannel = 0);
// === Emote Picker navigation === // === Emote Picker navigation ===

View File

@ -162,6 +162,8 @@ static const uint8_t SCL = PIN_WIRE_SCL;
#define CANNED_MESSAGE_MODULE_ENABLE 1 #define CANNED_MESSAGE_MODULE_ENABLE 1
#define CANNED_MESSAGE_ADD_CONFIRMATION 1
// trackball // trackball
#define HAS_TRACKBALL 1 #define HAS_TRACKBALL 1
#define TB_UP 25 #define TB_UP 25