From 8dd6f149f8656cea6dc2785376198d48ead3cf26 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:36:36 +0100 Subject: [PATCH] sync with arduino/dev --- zephcore/app/CompanionMesh.cpp | 88 +++++++++++++++++++++++++++++-- zephcore/app/CompanionMesh.h | 2 + zephcore/app/RepeaterMesh.h | 2 +- zephcore/helpers/BaseChatMesh.cpp | 60 ++++++++++++++++++--- zephcore/helpers/BaseChatMesh.h | 4 ++ zephcore/helpers/CommonCLI.cpp | 22 ++++---- zephcore/helpers/TxtDataHelpers.h | 2 + zephcore/include/mesh/MeshCore.h | 1 + 8 files changed, 159 insertions(+), 22 deletions(-) diff --git a/zephcore/app/CompanionMesh.cpp b/zephcore/app/CompanionMesh.cpp index 7e88854..30ad229 100644 --- a/zephcore/app/CompanionMesh.cpp +++ b/zephcore/app/CompanionMesh.cpp @@ -73,6 +73,7 @@ LOG_MODULE_REGISTER(zephcore_companion, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL); #define CMD_GET_AUTOADD_CONFIG 0x3B #define CMD_GET_ALLOWED_REPEAT_FREQ 0x3C #define CMD_SET_PATH_HASH_MODE 0x3D +#define CMD_SEND_CHANNEL_DATA 0x3E /* Response packet types */ #define PACKET_OK 0x00 @@ -102,6 +103,9 @@ LOG_MODULE_REGISTER(zephcore_companion, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL); #define PACKET_STATS 0x18 #define PACKET_AUTOADD_CONFIG 0x19 #define PACKET_ALLOWED_REPEAT_FREQ 0x1A +#define PACKET_CHANNEL_DATA_RECV 0x1B + +#define MAX_CHANNEL_DATA_LENGTH (MAX_FRAME_SIZE - 9) #define MAX_SIGN_DATA_LEN (8 * 1024) @@ -355,8 +359,8 @@ size_t CompanionMesh::serializeContact(uint8_t *buf, const ContactInfo &c, uint8 static bool isChannelMessage(const uint8_t *buf) { - // Channel messages have PACKET_CHANNEL_MSG_V3 (0x11) or PACKET_CHANNEL_MSG_RECV (0x08) - return buf[0] == PACKET_CHANNEL_MSG_V3 || buf[0] == PACKET_CHANNEL_MSG_RECV; + // Channel messages have PACKET_CHANNEL_MSG_V3 (0x11) or PACKET_CHANNEL_MSG_RECV (0x08) or PACKET_CHANNEL_DATA_RECV (0x1B) + return buf[0] == PACKET_CHANNEL_MSG_V3 || buf[0] == PACKET_CHANNEL_MSG_RECV || buf[0] == PACKET_CHANNEL_DATA_RECV; } void CompanionMesh::queueOfflineMessage(const uint8_t *data, size_t len) @@ -745,6 +749,41 @@ void CompanionMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh sendPush(PUSH_CODE_MSG_WAITING); } +void CompanionMesh::onChannelDataRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, + uint16_t data_type, const uint8_t *data, size_t data_len) +{ + if (data_len > MAX_CHANNEL_DATA_LENGTH) { + LOG_WRN("onChannelDataRecv: dropping payload_len=%d exceeds frame limit=%d", + (int)data_len, (int)MAX_CHANNEL_DATA_LENGTH); + return; + } + + uint8_t frame[MAX_FRAME_SIZE]; + int i = 0; + frame[i++] = PACKET_CHANNEL_DATA_RECV; + frame[i++] = pkt ? (int8_t)(pkt->getSNR() * 4) : 0; + frame[i++] = 0; // reserved1 + frame[i++] = 0; // reserved2 + + uint8_t channel_idx = findChannelIdx(channel); + frame[i++] = channel_idx; + frame[i++] = (pkt && pkt->isRouteFlood()) ? pkt->path_len : 0xFF; + frame[i++] = (uint8_t)(data_type & 0xFF); + frame[i++] = (uint8_t)(data_type >> 8); + frame[i++] = (uint8_t)data_len; + + int copy_len = (int)data_len; + if (copy_len > 0) { + memcpy(&frame[i], data, copy_len); + i += copy_len; + } + + LOG_INF("onChannelDataRecv: frame_len=%d channel_idx=%d data_type=%d", + i, channel_idx, (int)data_type); + queueOfflineMessage(frame, i); + sendPush(PUSH_CODE_MSG_WAITING); +} + uint8_t CompanionMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data, uint8_t len, uint8_t *reply) { @@ -1772,7 +1811,7 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len) // If repeat requested, validate frequency against allowed bands if (repeat && !isValidClientRepeatFreq(freq)) { sendPacketError(ERR_ILLEGAL_ARG); - } else if (freq >= 300000 && freq <= 2500000 && + } else if (freq >= 150000 && freq <= 2500000 && bw >= 7000 && bw <= 500000 && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8) { @@ -1874,7 +1913,7 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len) #endif static const uint8_t fw_build[12] = FIRMWARE_BUILD_DATE; static const uint8_t model[40] = CONFIG_ZEPHCORE_BOARD_NAME; - static const uint8_t version[20] = "v1.14.0-zephyr"; + static const uint8_t version[20] = "v1.14.1-zephyr"; uint8_t rsp[82]; rsp[0] = PACKET_DEVICE_INFO; rsp[1] = 10; // FIRMWARE_VER_CODE - v10 = path_hash_mode support @@ -2606,6 +2645,47 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len) } return true; + case CMD_SEND_CHANNEL_DATA: { + if (len < 4) { + sendPacketError(ERR_ILLEGAL_ARG); + return true; + } + int i = 1; + uint8_t channel_idx = data[i++]; + uint8_t path_len = data[i++]; + + if (!mesh::Packet::isValidPathLen(path_len) && path_len != OUT_PATH_UNKNOWN) { + LOG_WRN("CMD_SEND_CHANNEL_DATA invalid path size: %d", path_len); + sendPacketError(ERR_ILLEGAL_ARG); + return true; + } + + uint8_t path[MAX_PATH_SIZE]; + if (path_len != OUT_PATH_UNKNOWN) { + i += mesh::Packet::writePath(path, &data[i], path_len); + } + + uint16_t data_type = ((uint16_t)data[i]) | (((uint16_t)data[i + 1]) << 8); + i += 2; + const uint8_t *payload = &data[i]; + int payload_len = (len > (size_t)i) ? (int)(len - i) : 0; + + ChannelDetails channel; + if (!getChannel(channel_idx, channel)) { + sendPacketError(ERR_NOT_FOUND); + } else if (data_type == DATA_TYPE_RESERVED) { + sendPacketError(ERR_ILLEGAL_ARG); + } else if (payload_len > MAX_CHANNEL_DATA_LENGTH) { + LOG_WRN("CMD_SEND_CHANNEL_DATA payload too long: %d > %d", payload_len, MAX_CHANNEL_DATA_LENGTH); + sendPacketError(ERR_ILLEGAL_ARG); + } else if (sendGroupData(channel.channel, path, path_len, data_type, payload, payload_len)) { + sendPacketOk(); + } else { + sendPacketError(ERR_TABLE_FULL); + } + return true; + } + case CMD_SEND_ANON_REQ: if (len >= 1 + PUB_KEY_SIZE + 1) { ContactInfo *contact = lookupContactByPubKey(&data[1], PUB_KEY_SIZE); diff --git a/zephcore/app/CompanionMesh.h b/zephcore/app/CompanionMesh.h index fb24901..e655393 100644 --- a/zephcore/app/CompanionMesh.h +++ b/zephcore/app/CompanionMesh.h @@ -230,6 +230,8 @@ protected: uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const override; void onSendTimeout() override; void onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t timestamp, const char *text) override; + void onChannelDataRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint16_t data_type, + const uint8_t *data, size_t data_len) override; uint8_t onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data, uint8_t len, uint8_t *reply) override; void onContactResponse(const ContactInfo &contact, const uint8_t *data, uint8_t len) override; diff --git a/zephcore/app/RepeaterMesh.h b/zephcore/app/RepeaterMesh.h index dd2b6d7..4c205f1 100644 --- a/zephcore/app/RepeaterMesh.h +++ b/zephcore/app/RepeaterMesh.h @@ -25,7 +25,7 @@ #include "RepeaterDataStore.h" #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.14.0-zephyr" + #define FIRMWARE_VERSION "v1.14.1-zephyr" #endif #ifndef FIRMWARE_BUILD_DATE diff --git a/zephcore/helpers/BaseChatMesh.cpp b/zephcore/helpers/BaseChatMesh.cpp index 847b09b..6ef2e71 100644 --- a/zephcore/helpers/BaseChatMesh.cpp +++ b/zephcore/helpers/BaseChatMesh.cpp @@ -388,12 +388,28 @@ int BaseChatMesh::searchChannelsByHash(const uint8_t *hash, mesh::GroupChannel d void BaseChatMesh::onGroupDataRecv(mesh::Packet *packet, uint8_t type, const mesh::GroupChannel &channel, uint8_t *data, size_t len) { - uint8_t txt_type = data[4]; - if (type == PAYLOAD_TYPE_GRP_TXT && len > 5 && (txt_type >> 2) == 0) { - uint32_t timestamp; - memcpy(×tamp, data, 4); - data[len] = 0; - onChannelMessageRecv(channel, packet, timestamp, (const char *)&data[5]); + if (type == PAYLOAD_TYPE_GRP_TXT) { + uint8_t txt_type = data[4]; + if (len > 5 && (txt_type >> 2) == 0) { + uint32_t timestamp; + memcpy(×tamp, data, 4); + data[len] = 0; + onChannelMessageRecv(channel, packet, timestamp, (const char *)&data[5]); + } + } else if (type == PAYLOAD_TYPE_GRP_DATA) { + if (len < 3) { + LOG_DBG("onGroupDataRecv: dropping short group data payload len=%d", (int)len); + return; + } + uint16_t data_type = ((uint16_t)data[0]) | (((uint16_t)data[1]) << 8); + uint8_t data_len = data[2]; + size_t available_len = len - 3; + if (data_len > available_len) { + LOG_DBG("onGroupDataRecv: dropping malformed group data type=%d len=%d available=%d", + (int)data_type, (int)data_len, (int)available_len); + return; + } + onChannelDataRecv(channel, packet, data_type, &data[3], data_len); } } @@ -505,6 +521,38 @@ bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel &chan return false; } +bool BaseChatMesh::sendGroupData(mesh::GroupChannel &channel, uint8_t *path, uint8_t path_len, + uint16_t data_type, const uint8_t *data, int data_len) +{ + if (data_len < 0) { + LOG_DBG("sendGroupData: invalid negative data_len=%d", data_len); + return false; + } + if (data_len > MAX_GROUP_DATA_LENGTH) { + LOG_DBG("sendGroupData: data_len=%d exceeds max=%d", data_len, MAX_GROUP_DATA_LENGTH); + return false; + } + + uint8_t temp[3 + MAX_GROUP_DATA_LENGTH]; + temp[0] = (uint8_t)(data_type & 0xFF); + temp[1] = (uint8_t)(data_type >> 8); + temp[2] = (uint8_t)data_len; + if (data_len > 0) memcpy(&temp[3], data, data_len); + + mesh::Packet *pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_DATA, channel, temp, 3 + data_len); + if (pkt == nullptr) { + LOG_DBG("sendGroupData: unable to create group datagram, data_len=%d", data_len); + return false; + } + + if (path_len == OUT_PATH_UNKNOWN) { + sendFloodScoped(channel, pkt); + } else { + sendDirect(pkt, path, path_len); + } + return true; +} + bool BaseChatMesh::shareContactZeroHop(const ContactInfo &contact) { int plen = getBlobByKey(contact.id.pub_key, PUB_KEY_SIZE, temp_buf); diff --git a/zephcore/helpers/BaseChatMesh.h b/zephcore/helpers/BaseChatMesh.h index 2f1b519..78a8ea5 100644 --- a/zephcore/helpers/BaseChatMesh.h +++ b/zephcore/helpers/BaseChatMesh.h @@ -133,6 +133,8 @@ protected: 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, uint16_t data_type, + const uint8_t *data, size_t data_len) {} virtual uint8_t onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data, uint8_t len, uint8_t *reply) = 0; virtual void onContactResponse(const ContactInfo &contact, const uint8_t *data, uint8_t len) = 0; @@ -168,6 +170,8 @@ public: uint32_t &est_timeout); bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel &channel, const char *sender_name, const char *text, int text_len); + bool sendGroupData(mesh::GroupChannel &channel, uint8_t *path, uint8_t path_len, + uint16_t data_type, const uint8_t *data, int data_len); int sendLogin(const ContactInfo &recipient, const char *password, uint32_t &est_timeout); int sendAnonReq(const ContactInfo &recipient, const uint8_t *data, uint8_t len, uint32_t &tag, uint32_t &est_timeout); int sendRequest(const ContactInfo &recipient, uint8_t req_type, uint32_t &tag, uint32_t &est_timeout); diff --git a/zephcore/helpers/CommonCLI.cpp b/zephcore/helpers/CommonCLI.cpp index e5cadfc..d05993b 100644 --- a/zephcore/helpers/CommonCLI.cpp +++ b/zephcore/helpers/CommonCLI.cpp @@ -137,7 +137,7 @@ void CommonCLI::loadPrefs(const char* path) { _prefs->airtime_factor *= 10.0f; } _prefs->airtime_factor = constrain(_prefs->airtime_factor, 0.0f, 99.0f); - _prefs->freq = constrain(_prefs->freq, 400.0f, 2500.0f); + _prefs->freq = constrain(_prefs->freq, 150.0f, 2500.0f); _prefs->bw = constrain(_prefs->bw, 7.8f, 500.0f); _prefs->sf = constrain(_prefs->sf, (uint8_t)5, (uint8_t)12); _prefs->cr = constrain(_prefs->cr, (uint8_t)5, (uint8_t)8); @@ -377,12 +377,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; int temp_timeout_mins = num > 4 ? atoi(parts[4]) : 0; - if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && + if (freq >= 150.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f && temp_timeout_mins > 0) { _callbacks->applyTempRadioParams(freq, bw, sf, cr, temp_timeout_mins); snprintf(reply, CLI_REPLY_SIZE, "OK - temp params for %d mins", temp_timeout_mins); } else { - strcpy(reply, "Error: freq 300-2500, bw 7-500, sf 5-12, cr 5-8, timeout>0"); + strcpy(reply, "Error: freq 150-2500, bw 7-500, sf 5-12, cr 5-8, timeout>0"); } } else if (memcmp(command, "password ", 9) == 0) { StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password)); @@ -499,7 +499,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else { snprintf(reply, CLI_REPLY_SIZE, "> %.3f", (double)adc_mult); } - } else if (memcmp(config, "rxboost", 7) == 0) { + } else if (memcmp(config, "radio.rxgain", 12) == 0) { snprintf(reply, CLI_REPLY_SIZE, "> %d", (int)_prefs->rx_boost); } else if (memcmp(config, "rxduty", 6) == 0) { snprintf(reply, CLI_REPLY_SIZE, "> %d", (int)_prefs->rx_duty_cycle); @@ -617,7 +617,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f; uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; - if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && + if (freq >= 150.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { _prefs->sf = sf; _prefs->cr = cr; @@ -626,7 +626,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _callbacks->savePrefs(); strcpy(reply, "OK - reboot to apply"); } else { - strcpy(reply, "Error: freq 300-2500, bw 7-500, sf 5-12, cr 5-8"); + strcpy(reply, "Error: freq 150-2500, bw 7-500, sf 5-12, cr 5-8"); } } else if (memcmp(config, "lat ", 4) == 0) { _prefs->node_lat = atof(&config[4]); @@ -744,12 +744,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } } else if (sender_timestamp == 0 && memcmp(config, "freq ", 5) == 0) { float f = atof(&config[5]); - if (f >= 400.0f && f <= 2500.0f) { + if (f >= 150.0f && f <= 2500.0f) { _prefs->freq = f; savePrefs(); strcpy(reply, "OK - reboot to apply"); } else { - strcpy(reply, "Error: range 400-2500 MHz"); + strcpy(reply, "Error: range 150-2500 MHz"); } } else if (memcmp(config, "adc.multiplier ", 15) == 0) { _prefs->adc_multiplier = atof(&config[15]); @@ -764,12 +764,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->adc_multiplier = 0.0f; strcpy(reply, "Error: unsupported by this board"); } - } else if (memcmp(config, "rxboost ", 8) == 0) { - int val = atoi(&config[8]); + } else if (memcmp(config, "radio.rxgain ", 13) == 0) { + int val = atoi(&config[13]); if (val == 0 || val == 1) { _prefs->rx_boost = (uint8_t)val; savePrefs(); - snprintf(reply, CLI_REPLY_SIZE, "OK - rxboost=%d (reboot to apply)", _prefs->rx_boost); + snprintf(reply, CLI_REPLY_SIZE, "OK - radio.rxgain=%d (reboot to apply)", _prefs->rx_boost); } else { strcpy(reply, "Error: must be 0 or 1"); } diff --git a/zephcore/helpers/TxtDataHelpers.h b/zephcore/helpers/TxtDataHelpers.h index 0b917f8..f0d40e9 100644 --- a/zephcore/helpers/TxtDataHelpers.h +++ b/zephcore/helpers/TxtDataHelpers.h @@ -12,6 +12,8 @@ #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 DATA_TYPE_RESERVED 0x0000 // reserved for future use +#define DATA_TYPE_DEV 0xFFFF // developer namespace for experimenting with group/channel datagrams class StrHelper { public: diff --git a/zephcore/include/mesh/MeshCore.h b/zephcore/include/mesh/MeshCore.h index e5b31b0..214f8ad 100644 --- a/zephcore/include/mesh/MeshCore.h +++ b/zephcore/include/mesh/MeshCore.h @@ -22,6 +22,7 @@ #define MAX_PACKET_PAYLOAD 184 #define MAX_PATH_SIZE 64 #define MAX_TRANS_UNIT 255 +#define MAX_GROUP_DATA_LENGTH (MAX_PACKET_PAYLOAD - CIPHER_BLOCK_SIZE - 3) #define MESH_DEBUG_PRINT(...) #define MESH_DEBUG_PRINTLN(...)