Compare commits

...

5 Commits

Author SHA1 Message Date
Jonathan Bennett
1f0ef2498a Don't reboot node simply for a StatusMessage config update 2026-01-20 18:10:40 -06:00
Jonathan Bennett
84cd870323 Trunk 2026-01-20 10:42:23 -06:00
Jonathan Bennett
2eda145a56 Merge branch 'develop' into status-message 2026-01-20 10:34:24 -06:00
Jonathan Bennett
2f821ca267 Merge branch 'develop' into status-message 2026-01-18 15:20:57 -06:00
Jonathan Bennett
be8506857c Add StatusMessage module and config overrides 2026-01-18 15:15:31 -06:00
7 changed files with 145 additions and 22 deletions

View File

@@ -1410,6 +1410,15 @@ void NodeDB::loadFromDisk()
if (portduino_config.has_configDisplayMode) { if (portduino_config.has_configDisplayMode) {
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode; config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode;
} }
if (portduino_config.has_statusMessage) {
moduleConfig.has_statusmessage = true;
strncpy(moduleConfig.statusmessage.node_status, portduino_config.statusMessage.c_str(),
sizeof(moduleConfig.statusmessage.node_status));
moduleConfig.statusmessage.node_status[sizeof(moduleConfig.statusmessage.node_status) - 1] = '\0';
}
if (portduino_config.enable_UDP) {
config.network.enabled_protocols = true;
}
#endif #endif
} }
@@ -1510,6 +1519,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
moduleConfig.has_ambient_lighting = true; moduleConfig.has_ambient_lighting = true;
moduleConfig.has_audio = true; moduleConfig.has_audio = true;
moduleConfig.has_paxcounter = true; moduleConfig.has_paxcounter = true;
moduleConfig.has_statusmessage = true;
success &= success &=
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig); saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);

View File

@@ -905,10 +905,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
{ {
bool shouldReboot = true;
// If we are in an open transaction or configuring MQTT or Serial (which have validation), defer disabling Bluetooth // If we are in an open transaction or configuring MQTT or Serial (which have validation), defer disabling Bluetooth
// Otherwise, disable Bluetooth to prevent the phone from interfering with the config // Otherwise, disable Bluetooth to prevent the phone from interfering with the config
if (!hasOpenEditTransaction && if (!hasOpenEditTransaction && !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag,
!IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, meshtastic_ModuleConfig_serial_tag)) { meshtastic_ModuleConfig_serial_tag, meshtastic_ModuleConfig_statusmessage_tag)) {
disableBluetooth(); disableBluetooth();
} }
@@ -1000,8 +1001,14 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
moduleConfig.has_paxcounter = true; moduleConfig.has_paxcounter = true;
moduleConfig.paxcounter = c.payload_variant.paxcounter; moduleConfig.paxcounter = c.payload_variant.paxcounter;
break; break;
case meshtastic_ModuleConfig_statusmessage_tag:
LOG_INFO("Set module config: StatusMessage");
moduleConfig.has_statusmessage = true;
moduleConfig.statusmessage = c.payload_variant.statusmessage;
shouldReboot = false;
break;
} }
saveChanges(SEGMENT_MODULECONFIG); saveChanges(SEGMENT_MODULECONFIG, shouldReboot);
return true; return true;
} }
@@ -1180,6 +1187,11 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag; res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter; res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter;
break; break;
case meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG:
LOG_INFO("Get module config: StatusMessage");
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_statusmessage_tag;
res.get_module_config_response.payload_variant.statusmessage = moduleConfig.statusmessage;
break;
} }
// NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior. // NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior.

View File

@@ -108,6 +108,9 @@
#if !MESHTASTIC_EXCLUDE_DROPZONE #if !MESHTASTIC_EXCLUDE_DROPZONE
#include "modules/DropzoneModule.h" #include "modules/DropzoneModule.h"
#endif #endif
#if !MESHTASTIC_EXCLUDE_STATUS
#include "modules/StatusMessageModule.h"
#endif
/** /**
* Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else) * Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else)
@@ -165,6 +168,9 @@ void setupModules()
#if !MESHTASTIC_EXCLUDE_DROPZONE #if !MESHTASTIC_EXCLUDE_DROPZONE
dropzoneModule = new DropzoneModule(); dropzoneModule = new DropzoneModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_STATUS
statusMessageModule = new StatusMessageModule();
#endif
#if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE #if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE
new GenericThreadModule(); new GenericThreadModule();
#endif #endif

View File

@@ -0,0 +1,41 @@
#if !MESHTASTIC_EXCLUDE_STATUS
#include "StatusMessageModule.h"
#include "MeshService.h"
#include "ProtobufModule.h"
StatusMessageModule *statusMessageModule;
int32_t StatusMessageModule::runOnce()
{
if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') {
// create and send message with the status message set
meshtastic_StatusMessage ourStatus = meshtastic_StatusMessage_init_zero;
strncpy(ourStatus.status, moduleConfig.statusmessage.node_status, sizeof(ourStatus.status));
ourStatus.status[sizeof(ourStatus.status) - 1] = '\0'; // ensure null termination
meshtastic_MeshPacket *p = allocDataPacket();
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes),
meshtastic_StatusMessage_fields, &ourStatus);
p->to = NODENUM_BROADCAST;
p->decoded.want_response = false;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
p->channel = 0;
service->sendToMesh(p);
}
return 1000 * 12 * 60 * 60;
}
ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
{
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
meshtastic_StatusMessage incomingMessage;
if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_StatusMessage_fields,
&incomingMessage)) {
LOG_INFO("Received a NodeStatus message %s", incomingMessage.status);
}
}
return ProcessMessage::CONTINUE;
}
#endif

View File

@@ -0,0 +1,35 @@
#pragma once
#if !MESHTASTIC_EXCLUDE_STATUS
#include "SinglePortModule.h"
#include "configuration.h"
class StatusMessageModule : public SinglePortModule, private concurrency::OSThread
{
public:
/** Constructor
* name is for debugging output
*/
StatusMessageModule()
: SinglePortModule("statusMessage", meshtastic_PortNum_NODE_STATUS_APP), concurrency::OSThread("StatusMessage")
{
if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') {
this->setInterval(2 * 60 * 1000);
} else {
this->setInterval(1000 * 12 * 60 * 60);
}
// TODO: If we have a string, set the initial delay (15 minutes maybe)
}
virtual int32_t runOnce() override;
protected:
/** Called to handle a particular incoming message
*/
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
private:
};
extern StatusMessageModule *statusMessageModule;
#endif

View File

@@ -65,7 +65,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state)
return ARGP_ERR_UNKNOWN; return ARGP_ERR_UNKNOWN;
else else
checkConfigPort = false; checkConfigPort = false;
printf("Using config file %d\n", TCPPort); printf("Using config file %d\n", TCPPort);
break; break;
case 'c': case 'c':
configPath = arg; configPath = arg;
@@ -847,6 +847,7 @@ bool loadConfig(const char *configPath)
} }
if (yamlConfig["Config"]) { if (yamlConfig["Config"]) {
portduino_config.has_config_overrides = true;
if (yamlConfig["Config"]["DisplayMode"]) { if (yamlConfig["Config"]["DisplayMode"]) {
portduino_config.has_configDisplayMode = true; portduino_config.has_configDisplayMode = true;
if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "TWOCOLOR") { if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "TWOCOLOR") {
@@ -859,6 +860,13 @@ bool loadConfig(const char *configPath)
portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT; portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT;
} }
} }
if (yamlConfig["Config"]["StatusMessage"]) {
portduino_config.has_statusMessage = true;
portduino_config.statusMessage = (yamlConfig["Config"]["StatusMessage"]).as<std::string>("");
}
if ((yamlConfig["Config"]["EnableUDP"]).as<bool>(false)) {
portduino_config.enable_UDP = true;
}
} }
if (yamlConfig["General"]) { if (yamlConfig["General"]) {
@@ -874,10 +882,8 @@ bool loadConfig(const char *configPath)
} }
if (checkConfigPort) { if (checkConfigPort) {
portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as<int>(-1); portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as<int>(-1);
if (portduino_config.api_port != -1 && if (portduino_config.api_port != -1 && portduino_config.api_port > 1023 && portduino_config.api_port < 65536) {
portduino_config.api_port > 1023 && TCPPort = (portduino_config.api_port);
portduino_config.api_port < 65536) {
TCPPort = (portduino_config.api_port);
} }
} }
portduino_config.mac_address = (yamlConfig["General"]["MACAddress"]).as<std::string>(""); portduino_config.mac_address = (yamlConfig["General"]["MACAddress"]).as<std::string>("");

View File

@@ -168,8 +168,12 @@ extern struct portduino_config_struct {
int hostMetrics_channel = 0; int hostMetrics_channel = 0;
// config // config
bool has_config_overrides = false;
int configDisplayMode = 0; int configDisplayMode = 0;
bool has_configDisplayMode = false; bool has_configDisplayMode = false;
std::string statusMessage = "";
bool has_statusMessage = false;
bool enable_UDP = false;
// General // General
std::string mac_address = ""; std::string mac_address = "";
@@ -485,21 +489,30 @@ extern struct portduino_config_struct {
} }
// config // config
if (has_configDisplayMode) { if (has_config_overrides) {
out << YAML::Key << "Config" << YAML::Value << YAML::BeginMap; out << YAML::Key << "Config" << YAML::Value << YAML::BeginMap;
switch (configDisplayMode) { if (has_configDisplayMode) {
case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR:
out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR"; switch (configDisplayMode) {
break; case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR:
case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED: out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR";
out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED"; break;
break; case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED:
case meshtastic_Config_DisplayConfig_DisplayMode_COLOR: out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED";
out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR"; break;
break; case meshtastic_Config_DisplayConfig_DisplayMode_COLOR:
case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT: out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR";
out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT"; break;
break; case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT:
out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT";
break;
}
}
if (has_statusMessage) {
out << YAML::Key << "StatusMessage" << YAML::Value << statusMessage;
}
if (enable_UDP) {
out << YAML::Key << "EnableUDP" << YAML::Value << true;
} }
out << YAML::EndMap; // Config out << YAML::EndMap; // Config