Skip to content

feat: Add support for PAYLOAD_TYPE_GRP_DATA#1928

Open
dz0ny wants to merge 4 commits intomeshcore-dev:mainfrom
dz0ny:feat/grp-data-upstream
Open

feat: Add support for PAYLOAD_TYPE_GRP_DATA#1928
dz0ny wants to merge 4 commits intomeshcore-dev:mainfrom
dz0ny:feat/grp-data-upstream

Conversation

@dz0ny
Copy link

@dz0ny dz0ny commented Mar 5, 2026

  • Docs changes are to reflect how it is currently in fw
  • This adds ability to send datagram data to everyone in channel

Copilot AI review requested due to automatic review settings March 5, 2026 12:34
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds channel-wide binary datagram support by introducing PAYLOAD_TYPE_GRP_DATA handling and exposing it through the companion radio example + protocol docs, enabling non-text payloads to be broadcast to all members of a group channel.

Changes:

  • Add a new onChannelDataRecv() hook and sendGroupData() API to the chat mesh helper layer.
  • Handle inbound PAYLOAD_TYPE_GRP_DATA packets and forward them to the new callback.
  • Update the companion radio example and companion protocol docs to support/send/parse channel payloads beyond plain UTF-8 text.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/helpers/BaseChatMesh.h Adds onChannelDataRecv() virtual + sendGroupData() API.
src/helpers/BaseChatMesh.cpp Routes PAYLOAD_TYPE_GRP_DATA to the new callback; implements sendGroupData().
examples/companion_radio/MyMesh.h Declares override for onChannelDataRecv().
examples/companion_radio/MyMesh.cpp Implements channel data receive framing + sends non-plain channel payloads via sendGroupData().
docs/companion_protocol.md Updates protocol docs to describe channel payload semantics and OK/error responses.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +455 to +463
if (data_len < 0) return false;
if (data_len > MAX_PACKET_PAYLOAD - 5) data_len = MAX_PACKET_PAYLOAD - 5;

uint8_t temp[MAX_PACKET_PAYLOAD];
memcpy(temp, &timestamp, 4);
temp[4] = txt_type;
if (data_len > 0) memcpy(&temp[5], data, data_len);

auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_DATA, channel, temp, 5 + data_len);
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

sendGroupData() clamps data_len to MAX_PACKET_PAYLOAD - 5, but Mesh::createGroupDatagram() will reject plaintext lengths above MAX_PACKET_PAYLOAD - CIPHER_BLOCK_SIZE (168 bytes). That means any data_len > 163 (since you add the 5-byte header) will still fail with pkt == NULL, and the caller reports a generic send failure. Clamp to the maximum plaintext length accepted by createGroupDatagram() (accounting for the 5-byte header), or explicitly detect/return a distinct error for oversize payloads so the failure mode is deterministic and debuggable.

Copilot uses AI. Check for mistakes.
@dz0ny dz0ny force-pushed the feat/grp-data-upstream branch from a54a351 to 965c40c Compare March 5, 2026 12:52
@liamcottle
Copy link
Member

Howdy, thanks for the PR!

We have a previously opened PR for similar functionality here #1530.

We left some review comments on it, but it was never followed up so didn't get merged.
Please have a read through for more info.

I noticed in this PR you have merged both group text and group data into the single CMD_SEND_GROUP_TEXT command, personally I'd prefer to have separate commands.

  • CMD_SEND_CHANNEL_TXT_MSG
  • CMD_SEND_CHANNEL_DATA

We also need to have a distinct payload type at the start of the raw group data as well, so we can reserve certain types for specific tasks.

I have some more info about this in the previous PR comments.

@dz0ny
Copy link
Author

dz0ny commented Mar 5, 2026

Ah, the SOS, reaction thing yep. Pushed changes, so one can specify type and then binary 0xff is for custom apps.

dz0ny added 2 commits March 5, 2026 14:04
Docs changes are to reflect how it is currently in fw

This adds ability to send datagram data to everyone in channel
@dz0ny dz0ny force-pushed the feat/grp-data-upstream branch from 965c40c to d448c1e Compare March 5, 2026 13:05
#endif
}

void MyMesh::onChannelDataRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t timestamp, uint8_t txt_type,
Copy link
Member

Choose a reason for hiding this comment

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

please refactor txt_type to data_type, since this isn't intended for text based data at this time.

const uint8_t *data, size_t data_len) {
int i = 0;
if (app_target_ver >= 3) {
out_frame[i++] = RESP_CODE_CHANNEL_MSG_RECV_V3;
Copy link
Member

Choose a reason for hiding this comment

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

we should probably use a new RESP_CODE_CHANNEL_DATA_RECV

i += 4;

size_t available = MAX_FRAME_SIZE - i;
if (data_len > available) data_len = available;
Copy link
Member

Choose a reason for hiding this comment

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

I would probably prefer to have an error returned if provided data is too long, rather than truncating and sending anyway

writeOKFrame();
} else {
writeErrFrame(ERR_CODE_NOT_FOUND); // bad channel_idx
writeErrFrame(ERR_CODE_TABLE_FULL);
Copy link
Member

@liamcottle liamcottle Mar 6, 2026

Choose a reason for hiding this comment

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

we should probably revert all the changes made to CMD_SEND_CHANNEL_TXT_MSG as they're not relevant for the new CMD_SEND_CHANNEL_DATA. Changes to the error codes and lookup should be a separate PR :)

}
} else if (cmd_frame[0] == CMD_SEND_CHANNEL_DATA) { // send GroupChannel datagram
int i = 1;
uint8_t txt_type = cmd_frame[i++];
Copy link
Member

Choose a reason for hiding this comment

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

Please refactor txt_type to data_type


auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_DATA, channel, temp, 5 + data_len);
if (pkt) {
sendFloodScoped(channel, pkt);
Copy link
Member

Choose a reason for hiding this comment

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

currently group text messages are always flood routed to the whole network, I wonder if we should allow for the ability for group data to have an optional path supplied, to allow for targeted group data to a specific area via specific repeaters... maybe something for @ripplebiz to comment on?

Copy link
Author

Choose a reason for hiding this comment

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

I think as an optional param, not required. Or maybe hop based, so only allow one hop.But yea heavily depends on use case and how to prevent spamming.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah definitely optional, just thinking it could be useful to be able to send some sort of flood advert locally, but without propagating to the entire network, since you could pick the direct path.

virtual uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const = 0;
virtual void onSendTimeout() = 0;
virtual void onChannelMessageRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t timestamp, const char *text) = 0;
virtual void onChannelDataRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t timestamp, uint8_t txt_type,
Copy link
Member

Choose a reason for hiding this comment

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

please change txt_type to data_type

int sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout);
int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout);
bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len);
bool sendGroupData(uint32_t timestamp, mesh::GroupChannel& channel, uint8_t txt_type, const uint8_t* data, int data_len);
Copy link
Member

Choose a reason for hiding this comment

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

please change txt_type to data_type

#define TXT_TYPE_PLAIN 0 // a plain text message
#define TXT_TYPE_CLI_DATA 1 // a CLI command
#define TXT_TYPE_SIGNED_PLAIN 2 // plain text, signed by sender
#define TXT_TYPE_CUSTOM_BINARY 0xFF // custom app binary payload (group/channel datagrams)
Copy link
Member

Choose a reason for hiding this comment

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

since this will be a new type dedicated to group data, instead of group text, we should prob rename to DATA_TYPE_CUSTOM

if (data_len < 0) return false;
// createGroupDatagram() accepts at most (MAX_PACKET_PAYLOAD - CIPHER_BLOCK_SIZE)
// plaintext bytes; subtract our 5-byte {timestamp, txt_type} header.
const int max_group_data_len = (MAX_PACKET_PAYLOAD - CIPHER_BLOCK_SIZE) - 5;
Copy link
Member

Choose a reason for hiding this comment

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

we should probably move max_group_data_len to a constant defined in Meshcore.h near MAX_PACKET_PAYLOAD. probably named MAX_GROUP_DATA_LENGTH

@dz0ny
Copy link
Author

dz0ny commented Mar 8, 2026

@liamcottle Should users be allowed to send packets on idx=0 (the public channel)? I think maybe not the 0xff type, but the others should be allowed. What do you think?

I know users want SOS, REACTION types, so those should be allowed. Just custom protocols probably not.

Copy link
Member

@liamcottle liamcottle left a comment

Choose a reason for hiding this comment

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

Looking good, thanks for your changes so far. Please see some extra comments.


2. **Contact Messages**:
2. **Channel Data**:
- `PACKET_CHANNEL_DATA_RECV` (0x1B) - Standard format
Copy link
Member

Choose a reason for hiding this comment

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

We can probably just have a single type PACKET_CHANNEL_DATA_RECV which also includes the SNR and reserved bytes. The reason we have two types for text message packets, is legacy reasons. We didn't have a way to add SNR data to existing response, because there was no data length byte. So we couldn't add it to the end of the payload.

| 0x11 | PACKET_CHANNEL_MSG_RECV_V3 | Channel message (V3 with SNR) |
| 0x12 | PACKET_CHANNEL_INFO | Channel information |
| 0x1B | PACKET_CHANNEL_DATA_RECV | Channel data (standard) |
| 0x1C | PACKET_CHANNEL_DATA_RECV_V3| Channel data (V3 with SNR) |
Copy link
Member

Choose a reason for hiding this comment

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

Suggested to remove PACKET_CHANNEL_DATA_RECV_V3 and only have PACKET_CHANNEL_DATA_RECV. SNR would be included in PACKET_CHANNEL_DATA_RECV.

#define RESP_CODE_AUTOADD_CONFIG 25
#define RESP_ALLOWED_REPEAT_FREQ 26
#define RESP_CODE_CHANNEL_DATA_RECV 27
#define RESP_CODE_CHANNEL_DATA_RECV_V3 28
Copy link
Member

Choose a reason for hiding this comment

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

No need for _V3 based on previous review comments

int i = 0;
if (app_target_ver >= 3) {
out_frame[i++] = RESP_CODE_CHANNEL_MSG_RECV_V3;
out_frame[i++] = RESP_CODE_CHANNEL_DATA_RECV_V3;
Copy link
Member

Choose a reason for hiding this comment

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

Rename RESP_CODE_CHANNEL_DATA_RECV_V3 to RESP_CODE_CHANNEL_DATA_RECV.

void MyMesh::onChannelDataRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t timestamp, uint8_t data_type,
const uint8_t *data, size_t data_len) {
int i = 0;
if (app_target_ver >= 3) {
Copy link
Member

Choose a reason for hiding this comment

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

No need for the target version check, since these are brand new request/response codes, which can be checked against a protocol version bump later on.

}
int copy_len = (int)data_len;
if (copy_len > 0) {
memcpy(&out_frame[i], data, copy_len);
Copy link
Member

Choose a reason for hiding this comment

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

I feel like we should also have a data_length byte that's in the payload so we can in a future update, add extra data at the end if there is room for it. This will help prevent the issue where we needed new packet format versions to add the SNR to txt messages.

memcpy(temp, &timestamp, 4);
temp[4] = txt_type;
temp[4] = data_type;
if (data_len > 0) memcpy(&temp[5], data, data_len);
Copy link
Member

Choose a reason for hiding this comment

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

As mentioned in previous review comment, wondering if we should have a data_length byte as well.

This would allow adding extra data at the end later on, as clients shouldn't be reading more than data_length. This should also fix the issue where packets have extra zero padding passed back to the client, since the firmware has no idea what the actual data length is. This is a problem we currently have with cayenne lpp. Where there's extra zeros at the end, and clients have to do funky things to know to stop reading.

I would prefer to do this at the sendGroupData level, e.g temp[5] = data_len; otherwise we would have to add this to all other payload types. Probably best to just make it standard that we know the length up front.

@liamcottle
Copy link
Member

Should users be allowed to send packets on idx=0 (the public channel)? I think maybe not the 0xff type, but the others should be allowed. What do you think?

I'll discuss this one with Scott.

@liamcottle
Copy link
Member

liamcottle commented Mar 9, 2026

We should probably go with uint16_t for data_type, instead of uint8_t so we have a larger pool of data types to hand out to the community for custom applications etc. ~256 might be too limiting, and we'd have to be careful how many IDs we give out. An ID could be given to an application/organisation/person, rather than a specific purpose, and the app author can embed their own internal type inside their data payload.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants