Skip to content
Merged
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
28 changes: 6 additions & 22 deletions src/graphics/draw/MessageRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include "MessageRenderer.h"

// Core includes
#include "Channels.h"
#include "MeshService.h"
#include "MessageStore.h"
#include "NodeDB.h"
#include "UIRenderer.h"
Expand Down Expand Up @@ -1138,13 +1140,7 @@ void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const mesht
// still happens so a message can light the screen back up.
const bool menuShowing = NotificationRenderer::isMenuShowing();

// Determine if message belongs to a muted channel
bool isChannelMuted = false;
if (sm.type == MessageType::BROADCAST) {
const meshtastic_Channel channel = channels.getByIndex(packet.channel ? packet.channel : channels.getPrimaryIndex());
if (channel.settings.has_module_settings && channel.settings.module_settings.is_muted)
isChannelMuted = true;
}
const bool isMuted = isMutedForPacket(packet);

// Banner logic
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from);
Expand All @@ -1164,30 +1160,18 @@ void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const mesht
char truncatedLongName[64];
graphics::UIRenderer::truncateStringWithEmotes(display, longName, truncatedLongName, sizeof(truncatedLongName),
availWidth);
const char *msgRaw = reinterpret_cast<const char *>(packet.decoded.payload.bytes);

char banner[256];
bool isAlert = false;

// Check if alert detection is enabled via external notification module
if (moduleConfig.external_notification.alert_bell || moduleConfig.external_notification.alert_bell_vibra ||
moduleConfig.external_notification.alert_bell_buzzer) {
for (size_t i = 0; i < packet.decoded.payload.size && i < 100; i++) {
if (msgRaw[i] == '\x07') {
isAlert = true;
break;
}
}
}
const bool isAlert = MeshService::isAlertPayload(packet);

if (isAlert) {
if (truncatedLongName[0])
snprintf(banner, sizeof(banner), "Alert Received from\n%s", truncatedLongName);
else
strcpy(banner, "Alert Received");
} else {
// Skip muted channels unless it's an alert
if (isChannelMuted)
// Skip muted channels/senders unless it's an alert
if (isMuted)
return;

if (truncatedLongName[0]) {
Expand Down
9 changes: 9 additions & 0 deletions src/mesh/Channels.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -584,3 +584,12 @@ int16_t Channels::setActiveByIndex(ChannelIndex channelIndex)
{
return setCrypto(channelIndex);
}

bool isMutedForPacket(const meshtastic_MeshPacket &mp)
{
if (!isBroadcast(mp.to) && isToUs(&mp))
return nodeInfoLiteIsMuted(nodeDB->getMeshNode(mp.from));

const meshtastic_Channel &ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex());
return ch.settings.has_module_settings && ch.settings.module_settings.is_muted;
}
4 changes: 4 additions & 0 deletions src/mesh/Channels.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ extern Channels channels;
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01};

/// True if the user muted the source of this packet: the sender for a DM addressed to us,
/// otherwise the channel it arrived on.
bool isMutedForPacket(const meshtastic_MeshPacket &mp);

/// True if a getKey()-resolved key offers no privacy: length 0 (off) or the public defaultpsk family. Pure; for tests.
bool cryptoKeyIsPublic(const CryptoKey &key);

Expand Down
15 changes: 15 additions & 0 deletions src/mesh/MeshService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,21 @@ bool MeshService::trySendPosition(NodeNum dest, bool wantReplies)
return false;
}

// ASCII BEL, the in-band alert marker. Numeric so no control byte sits in the source, and
// file-local because ASCII_BELL is already a macro in Screen.cpp and ExternalNotificationModule.cpp.
static const uint8_t kAsciiBell = 7;

bool MeshService::isAlertPayload(const meshtastic_MeshPacket &p)
{
if (!moduleConfig.external_notification.alert_bell && !moduleConfig.external_notification.alert_bell_vibra &&
!moduleConfig.external_notification.alert_bell_buzzer)
return false;
for (pb_size_t i = 0; i < p.decoded.payload.size; i++)
if (p.decoded.payload.bytes[i] == kAsciiBell)
return true;
return false;
}

// Re-decode nested string-bearing payloads before local phone delivery so PB_VALIDATE_UTF8 rejects
// malformed NodeInfo/Waypoint data a strict phone decoder could crash on. Mesh relay is unaffected.
bool MeshService::phonePayloadIsDecodable(const meshtastic_Data &d)
Expand Down
4 changes: 4 additions & 0 deletions src/mesh/MeshService.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ class MeshService
p->decoded.portnum == meshtastic_PortNum_ALERT_APP;
}

/// True if the sender flagged this text as an alert: an ASCII BEL in the payload while at least
/// one alert_bell_* output is enabled. Alerts deliberately break through a mute.
static bool isAlertPayload(const meshtastic_MeshPacket &p);

/// Returns false when a decoded NodeInfo/Waypoint payload fails nested protobuf decode (invalid
/// UTF-8 under PB_VALIDATE_UTF8, etc.); other portnums pass through. Callers gate on the variant.
static bool phonePayloadIsDecodable(const meshtastic_Data &decoded);
Expand Down
10 changes: 2 additions & 8 deletions src/modules/ExternalNotificationModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* @date [Insert Date]
*/
#include "ExternalNotificationModule.h"
#include "Channels.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "Router.h"
Expand Down Expand Up @@ -421,15 +422,8 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}

const meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex());

// If we receive a broadcast message, apply channel mute setting
// If we receive a direct message and the receipent is us, apply DM mute setting
// Else we just handle it as not muted.
const bool isDmToUs = !isBroadcast(mp.to) && isToUs(&mp);
bool is_muted = isDmToUs ? nodeInfoLiteIsMuted(sender)
: (ch.settings.has_module_settings && ch.settings.module_settings.is_muted);
const bool is_muted = isMutedForPacket(mp);

const bool buzzerModeIsDirectOnly =
(config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY);
Expand Down
7 changes: 5 additions & 2 deletions src/modules/TextMessageModule.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "TextMessageModule.h"
#include "Channels.h"
#include "MeshService.h"
#include "MessageStore.h"
#include "NodeDB.h"
Expand Down Expand Up @@ -34,8 +35,10 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp
auto *display = screen ? screen->getDisplayDevice() : nullptr;
graphics::MessageRenderer::handleNewMessage(display, *sm, mp);
})
// Only trigger screen wake if configuration allows it
if (shouldWakeOnReceivedMessage()) {
// Only trigger screen wake if configuration allows it and the channel/sender isn't muted.
// An alert breaks through the mute: in COLOR display mode handleNewMessage() above never runs,
// so this trigger is the only wake an alert would get.
if (shouldWakeOnReceivedMessage() && (!isMutedForPacket(mp) || MeshService::isAlertPayload(mp))) {
powerFSM.trigger(EVENT_RECEIVED_MSG);
}

Expand Down
1 change: 1 addition & 0 deletions test/state-manifest.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ test_hop_start_policy writes=config.proto,module.proto,device.proto,channels.pro
test_mesh_beacon writes=module.proto exercises the beacon's module-config save path
test_mesh_module writes=config.proto,module.proto,device.proto,channels.proto,nodes.proto,warm.dat module framework tests construct a NodeDB
test_mqtt writes=config.proto,module.proto,device.proto,channels.proto,nodes.proto errors=1000..12000 constructs a NodeDB for node lookups in the MQTT paths
test_muted_source writes=config.proto,module.proto,device.proto,channels.proto,nodes.proto constructs a NodeDB (isToUs needs nodeDB->getNodeNum(), and the DM branch looks the sender up), whose constructor persists a default set when the prefs directory is empty
test_nexthop_routing writes=config.proto,module.proto,device.proto,channels.proto,nodes.proto next-hop selection reads and updates the node DB
test_nodedb_blocked state=per-suite writes=config.proto,module.proto,device.proto,channels.proto,nodes.proto,warm.dat saturates the DB with MAX_NUM_NODES-2 favourited nodes to test the protected cap; a later test's removeNodeByNum() persists that state, and the cap test depends on the fill from the test before it
test_nodedb_boot_recovery state=per-suite writes=config.proto,module.proto,device.proto,channels.proto,nodes.proto deliberate boot-recovery ladder: corrupts/deletes/restores the pref files and reboots a NodeDB per test to pin the DECODE_FAILED identity freeze, so each test observes the previous test's on-disk state
Expand Down
Loading
Loading