diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index ad0d53c..17ead0a 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -4794,6 +4794,11 @@ class MeshCoreConnector extends ChangeNotifier { case pushCodeTelemetryResponse: _handleSelfTelemetry(frame); break; + case pushCodeChannelsChanged: + // Device's channel table changed (add/update/delete). Re-poll instead + // of only refreshing on reconnect (#429 part A). + unawaited(getChannels(force: true)); + break; case respCodeChannelInfo: _handleChannelInfo(frame); break; @@ -5910,11 +5915,15 @@ class MeshCoreConnector extends ChangeNotifier { // Companion radio layout: // [code][snr?][res?][res?][prefix x6][path_len][txt_type][timestamp x4][extra?][text...] // double snr = 0; + bool isOutgoing = false; if (code == respCodeContactMsgRecvV3) { // Older firmware layout with SNR as a signed byte after the code // snr = reader.readInt8().toDouble() * 4; // SNR in dB, scaled by 4 reader.skipBytes(1); // Skip SNR byte - reader.skipBytes(2); // Skip reserved bytes + // reserved1 bit0 = outgoing flag: set for a message composed on the + // device itself, so it renders as sent-by-me (#429 part B). + isOutgoing = (reader.readByte() & 0x01) != 0; + reader.skipBytes(1); // Skip reserved2 } final senderPrefix = reader.readBytes(6); @@ -5974,13 +5983,13 @@ class MeshCoreConnector extends ChangeNotifier { senderKey: contact.publicKey, text: decodedText, timestamp: timestamp, - isOutgoing: false, + isOutgoing: isOutgoing, isCli: isCli, - status: MessageStatus.delivered, + status: isOutgoing ? MessageStatus.sent : MessageStatus.delivered, pathLength: pathLength == 0xFF ? 0 : pathLength, pathBytes: Uint8List(0), fourByteRoomContactKey: roomAuthorPrefix, - rxTime: DateTime.now(), + rxTime: isOutgoing ? null : DateTime.now(), ); } catch (e) { appLogger.warn('Error parsing contact direct message: $e'); @@ -6246,7 +6255,10 @@ class MeshCoreConnector extends ChangeNotifier { } final parsed = ChannelMessage.fromFrame(frame); if (parsed != null && parsed.channelIndex != null) { - if (_shouldDropSelfChannelMessage(parsed.senderName, parsed.pathBytes)) { + // Device-composed (outgoing) messages legitimately carry our own name and + // no path; don't let the self-echo guard drop them (#429 part B). + if (!parsed.isOutgoing && + _shouldDropSelfChannelMessage(parsed.senderName, parsed.pathBytes)) { return; } _lastChannelMsgRxTime = DateTime.now(); diff --git a/lib/connector/meshcore_protocol.dart b/lib/connector/meshcore_protocol.dart index c19b910..e2e5ff7 100644 --- a/lib/connector/meshcore_protocol.dart +++ b/lib/connector/meshcore_protocol.dart @@ -525,6 +525,8 @@ const int pushCodeTraceData = 0x89; const int pushCodeNewAdvert = 0x8A; const int pushCodeTelemetryResponse = 0x8B; const int pushCodeBinaryResponse = 0x8C; +const int pushCodeChannelsChanged = + 0x91; // #429 part A: device channel table changed; re-poll getChannels // Contact/advertisement types const int advTypeChat = 1; diff --git a/lib/models/channel_message.dart b/lib/models/channel_message.dart index 7ec8cf0..51602b3 100644 --- a/lib/models/channel_message.dart +++ b/lib/models/channel_message.dart @@ -195,18 +195,17 @@ class ChannelMessage { int txtType; Uint8List pathBytes = Uint8List(0); int channelIdx; + bool isOutgoing = false; if (code == respCodeChannelMsgRecvV3) { reader.skipBytes(1); // Skip SNR - final flags = reader.readByte(); - final hasPath = (flags & 0x01) != 0; - reader.skipBytes(1); // Skip reserved byte + // reserved1 bit0 = outgoing flag: set for a message composed on the + // device itself, so it renders as sent-by-me (#429 part B). No firmware + // appends path bytes to this frame, so path_len is metadata only. + isOutgoing = (reader.readByte() & 0x01) != 0; + reader.skipBytes(1); // Skip reserved2 channelIdx = reader.readByte(); pathLen = reader.readInt8(); txtType = reader.readByte(); - if (hasPath && pathLen > 0) { - reader.rewind(); // Rewind to read path length again for pathBytes - pathBytes = reader.readBytes(pathLen); - } } else { channelIdx = reader.readByte(); pathLen = reader.readInt8(); @@ -244,12 +243,12 @@ class ChannelMessage { senderName: senderName, text: decodedText, timestamp: DateTime.fromMillisecondsSinceEpoch(timestampRaw * 1000), - isOutgoing: false, + isOutgoing: isOutgoing, status: ChannelMessageStatus.sent, pathLength: pathLen, pathBytes: pathBytes, channelIndex: channelIdx, - rxTime: DateTime.now(), + rxTime: isOutgoing ? null : DateTime.now(), ); } catch (e) { appLogger.error('Error parsing channel message frame: $e');