Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -908,12 +908,17 @@ void setup()
// warn the user about a low entropy key
if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) {
LOG_WARN(LOW_ENTROPY_WARNING);
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING;
cn->time = getValidTime(RTCQualityFromNet);
sprintf(cn->message, LOW_ENTROPY_WARNING);
service->sendClientNotification(cn);
// Mark as warned unconditionally — the LOG_WARN above has already fired the user-visible
// notice. `hasWarned` persists in NodeDB, so this also suppresses the same warning on
// subsequent boots regardless of whether the ClientNotification pool had room this time.
nodeDB->hasWarned = true;
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
if (cn) {
cn->level = meshtastic_LogRecord_Level_WARNING;
cn->time = getValidTime(RTCQualityFromNet);
sprintf(cn->message, LOW_ENTROPY_WARNING);
service->sendClientNotification(cn);
}
Comment thread
thebentern marked this conversation as resolved.
}
#endif
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
Expand Down
12 changes: 8 additions & 4 deletions src/mesh/NodeDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1881,10 +1881,14 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
"to regenerate your public keys.";
LOG_WARN(warning, p.long_name);
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_WARNING;
cn->time = getValidTime(RTCQualityFromNet);
sprintf(cn->message, warning, p.long_name);
service->sendClientNotification(cn);
if (cn) {
cn->level = meshtastic_LogRecord_Level_WARNING;
cn->time = getValidTime(RTCQualityFromNet);
// snprintf because p.long_name is remote-controlled up to 40 bytes and the
// warning format itself is ~150 bytes — sprintf could overflow cn->message.
snprintf(cn->message, sizeof(cn->message), warning, p.long_name);
service->sendClientNotification(cn);
}
Comment thread
thebentern marked this conversation as resolved.
}
return false;
}
Expand Down
7 changes: 5 additions & 2 deletions src/mesh/PhoneAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -770,11 +770,14 @@ bool PhoneAPI::available()
void PhoneAPI::sendNotification(meshtastic_LogRecord_Level level, uint32_t replyId, const char *message)
{
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
if (!cn)
return;
cn->has_reply_id = true;
cn->reply_id = replyId;
cn->level = meshtastic_LogRecord_Level_WARNING;
cn->level = level;
cn->time = getValidTime(RTCQualityFromNet);
strncpy(cn->message, message, sizeof(cn->message));
strncpy(cn->message, message, sizeof(cn->message) - 1);
cn->message[sizeof(cn->message) - 1] = '\0';
service->sendClientNotification(cn);
}

Expand Down
14 changes: 8 additions & 6 deletions src/mesh/Router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,12 +325,14 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d mins", silentMinutes);

meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->has_reply_id = true;
cn->reply_id = p->id;
cn->level = meshtastic_LogRecord_Level_WARNING;
cn->time = getValidTime(RTCQualityFromNet);
sprintf(cn->message, "Duty cycle limit exceeded. You can send again in %d mins", silentMinutes);
service->sendClientNotification(cn);
if (cn) {
cn->has_reply_id = true;
cn->reply_id = p->id;
cn->level = meshtastic_LogRecord_Level_WARNING;
cn->time = getValidTime(RTCQualityFromNet);
sprintf(cn->message, "Duty cycle limit exceeded. You can send again in %d mins", silentMinutes);
service->sendClientNotification(cn);
}

meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT;
if (isFromUs(p)) { // only send NAK to API, not to the mesh
Expand Down
14 changes: 8 additions & 6 deletions src/modules/PositionModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,12 +397,14 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) &&
config.power.is_power_saving) {
meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed();
notification->level = meshtastic_LogRecord_Level_INFO;
notification->time = getValidTime(RTCQualityFromNet);
sprintf(notification->message, "Sending position and sleeping for %us interval in a moment",
Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs) /
1000U);
service->sendClientNotification(notification);
if (notification) {
notification->level = meshtastic_LogRecord_Level_INFO;
notification->time = getValidTime(RTCQualityFromNet);
sprintf(notification->message, "Sending position and sleeping for %us interval in a moment",
Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs) /
1000U);
service->sendClientNotification(notification);
}
sleepOnNextExecution = true;
LOG_DEBUG("Start next execution in 5s, then sleep");
setIntervalFromNow(FIVE_SECONDS_MS);
Expand Down
10 changes: 6 additions & 4 deletions src/modules/SerialModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,12 @@ bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &con
LOG_ERROR(warning);
#if !IS_RUNNING_TESTS
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_ERROR;
cn->time = getValidTime(RTCQualityFromNet);
snprintf(cn->message, sizeof(cn->message), "%s", warning);
service->sendClientNotification(cn);
if (cn) {
cn->level = meshtastic_LogRecord_Level_ERROR;
cn->time = getValidTime(RTCQualityFromNet);
snprintf(cn->message, sizeof(cn->message), "%s", warning);
service->sendClientNotification(cn);
}
#endif
return false;
}
Expand Down
32 changes: 18 additions & 14 deletions src/modules/Telemetry/AirQualityTelemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -416,27 +416,31 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)

if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) {
meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed();
notification->level = meshtastic_LogRecord_Level_INFO;
notification->time = getValidTime(RTCQualityFromNet);
sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment",
Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs) /
1000U);
service->sendClientNotification(notification);
if (notification) {
notification->level = meshtastic_LogRecord_Level_INFO;
notification->time = getValidTime(RTCQualityFromNet);
sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment",
Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs) /
1000U);
service->sendClientNotification(notification);
}
sleepOnNextExecution = true;
LOG_DEBUG("Start next execution in 5s, then sleep");
setIntervalFromNow(FIVE_SECONDS_MS);
}

if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) {
meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed();
notification->level = meshtastic_LogRecord_Level_INFO;
notification->time = getValidTime(RTCQualityFromNet);
sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment",
Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs) /
1000U);
service->sendClientNotification(notification);
if (notification) {
notification->level = meshtastic_LogRecord_Level_INFO;
notification->time = getValidTime(RTCQualityFromNet);
sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment",
Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs) /
1000U);
service->sendClientNotification(notification);
}
sleepOnNextExecution = true;
LOG_DEBUG("Start next execution in 5s, then sleep");
setIntervalFromNow(FIVE_SECONDS_MS);
Expand Down
16 changes: 9 additions & 7 deletions src/modules/Telemetry/EnvironmentTelemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -644,13 +644,15 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)

if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) {
meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed();
notification->level = meshtastic_LogRecord_Level_INFO;
notification->time = getValidTime(RTCQualityFromNet);
sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment",
Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval,
default_telemetry_broadcast_interval_secs) /
1000U);
service->sendClientNotification(notification);
if (notification) {
notification->level = meshtastic_LogRecord_Level_INFO;
notification->time = getValidTime(RTCQualityFromNet);
sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment",
Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval,
default_telemetry_broadcast_interval_secs) /
1000U);
service->sendClientNotification(notification);
}
sleepOnNextExecution = true;
LOG_DEBUG("Start next execution in 5s, then sleep");
setIntervalFromNow(FIVE_SECONDS_MS);
Expand Down
41 changes: 31 additions & 10 deletions src/mqtt/MQTT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,10 @@ bool MQTT::publish(const char *topic, const char *payload, bool retained)
{
if (moduleConfig.mqtt.proxy_to_client_enabled) {
meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed();
if (!msg) {
// MemoryPool::alloc already LOG_WARNs on exhaustion; don't double-log here.
return false;
Comment thread
thebentern marked this conversation as resolved.
}
msg->which_payload_variant = meshtastic_MqttClientProxyMessage_text_tag;
strncpy(msg->topic, topic, sizeof(msg->topic));
msg->topic[sizeof(msg->topic) - 1] = '\0';
Expand All @@ -503,6 +507,10 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo
{
if (moduleConfig.mqtt.proxy_to_client_enabled) {
meshtastic_MqttClientProxyMessage *msg = mqttClientProxyMessagePool.allocZeroed();
if (!msg) {
// MemoryPool::alloc already LOG_WARNs on exhaustion; don't double-log here.
return false;
}
Comment on lines +510 to +513
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allocZeroed()/MemoryPool already emits warnings when the pool is exhausted, so this additional LOG_WARN can double/triple-log and flood output when publishes are frequent. Prefer relying on the allocator log, lowering severity, or adding throttling.

Copilot uses AI. Check for mistakes.
msg->which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag;
strncpy(msg->topic, topic, sizeof(msg->topic));
msg->topic[sizeof(msg->topic) - 1] = '\0'; // Ensure null termination
Expand Down Expand Up @@ -685,11 +693,13 @@ bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config, MQTTC
LOG_ERROR(warning);
#if !IS_RUNNING_TESTS
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_ERROR;
cn->time = getValidTime(RTCQualityFromNet);
strncpy(cn->message, warning, sizeof(cn->message) - 1);
cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination
service->sendClientNotification(cn);
if (cn) {
cn->level = meshtastic_LogRecord_Level_ERROR;
cn->time = getValidTime(RTCQualityFromNet);
strncpy(cn->message, warning, sizeof(cn->message) - 1);
cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination
service->sendClientNotification(cn);
}
#endif
return false;
#endif
Expand All @@ -701,11 +711,13 @@ bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config, MQTTC
LOG_ERROR(warning);
#if !IS_RUNNING_TESTS
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_ERROR;
cn->time = getValidTime(RTCQualityFromNet);
strncpy(cn->message, warning, sizeof(cn->message) - 1);
cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination
service->sendClientNotification(cn);
if (cn) {
cn->level = meshtastic_LogRecord_Level_ERROR;
cn->time = getValidTime(RTCQualityFromNet);
strncpy(cn->message, warning, sizeof(cn->message) - 1);
cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination
service->sendClientNotification(cn);
}
#endif
return false;
}
Expand Down Expand Up @@ -878,6 +890,15 @@ void MQTT::perhapsReportToMap()

// Allocate MeshPacket and fill it
meshtastic_MeshPacket *mp = packetPool.allocZeroed();
if (!mp) {
// Back off to the configured publish interval on pool exhaustion. Without this,
// perhapsReportToMap() would be called on every runOnce() (20–200 ms) until the
// pool recovers, and each call would retry the log + early return — turning
// sustained pool pressure into a log-flood instead of a throttled failure.
LOG_ERROR("MQTT Map report: packet pool exhausted");
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On packetPool exhaustion this returns immediately without updating last_report_to_map, so perhapsReportToMap() will attempt again on every runOnce() iteration (20–200ms) and can spam logs while the pool remains exhausted. Consider throttling this error path (e.g., set last_report_to_map or a separate last_map_report_alloc_fail timestamp) so failures back off to the configured publish interval.

Suggested change
LOG_ERROR("MQTT Map report: packet pool exhausted");
LOG_ERROR("MQTT Map report: packet pool exhausted");
last_report_to_map = millis();

Copilot uses AI. Check for mistakes.
last_report_to_map = millis();
return;
}
mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
mp->from = nodeDB->getNodeNum();
mp->to = NODENUM_BROADCAST;
Expand Down