mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 18:38:17 +00:00
sync with upstream but held off the companion CLI
This commit is contained in:
@@ -1079,7 +1079,7 @@ Over USB CDC (and native-Linux TCP): framed with a length prefix — `[2B LE len
|
||||
|
||||
### Key Command Opcodes (phone → device)
|
||||
|
||||
The full set (~50 opcodes, `0x01`–`0x41`) is defined at the top of `app/CompanionMesh.cpp`; values match the Arduino MeshCore companion protocol. A sample:
|
||||
The full set (~50 opcodes, `0x01`–`0x42`) is defined at the top of `app/CompanionMesh.cpp`; values match the Arduino MeshCore companion protocol. A sample:
|
||||
|
||||
| Opcode | Name | Payload |
|
||||
|--------|------|---------|
|
||||
|
||||
@@ -84,6 +84,7 @@ LOG_MODULE_REGISTER(zephcore_companion, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
|
||||
#define CMD_SET_DEFAULT_FLOOD_SCOPE 0x3F /* v11+ */
|
||||
#define CMD_GET_DEFAULT_FLOOD_SCOPE 0x40 /* v11+ */
|
||||
#define CMD_SEND_RAW_PACKET 0x41 /* v12+ */
|
||||
#define CMD_RUN_CLI_COMMAND 0x42 /* v14+ */
|
||||
|
||||
/* Response packet types */
|
||||
#define PACKET_OK 0x00
|
||||
@@ -115,6 +116,7 @@ LOG_MODULE_REGISTER(zephcore_companion, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
|
||||
#define PACKET_ALLOWED_REPEAT_FREQ 0x1A
|
||||
#define PACKET_CHANNEL_DATA_RECV 0x1B
|
||||
#define PACKET_DEFAULT_FLOOD_SCOPE 0x1C
|
||||
#define PACKET_CLI_REPLY 0x1D /* v14+, reply to CMD_RUN_CLI_COMMAND */
|
||||
|
||||
#define MAX_CHANNEL_DATA_LENGTH (MAX_FRAME_SIZE - 9)
|
||||
|
||||
@@ -210,7 +212,7 @@ CompanionMesh::CompanionMesh(mesh::Radio &radio, mesh::MillisecondClock &ms, mes
|
||||
_dirty_channels_expiry = 0;
|
||||
memset(_send_scope.key, 0, sizeof(_send_scope.key));
|
||||
_send_scope_force_unscoped = false;
|
||||
_vcontact_cli_cb = nullptr;
|
||||
_cli_exec_cb = nullptr;
|
||||
memset(_vcontact_pubkey, 0, sizeof(_vcontact_pubkey));
|
||||
_vcontact_lastmod = 0;
|
||||
memset(_vcontact_recent_ts, 0, sizeof(_vcontact_recent_ts));
|
||||
@@ -1156,7 +1158,8 @@ bool CompanionMesh::vcontactHandleFrame(const uint8_t *data, size_t len)
|
||||
* attempt(1) + timestamp(4) + pub_key_prefix(6) + text(N). */
|
||||
if (len >= 14 && isVContactKey(&data[7], 6)) {
|
||||
uint8_t txt_type = data[1];
|
||||
if (txt_type != TXT_TYPE_PLAIN && txt_type != TXT_TYPE_CLI_DATA) {
|
||||
if (txt_type != TXT_TYPE_PLAIN && txt_type != TXT_TYPE_CLI_DATA &&
|
||||
txt_type != TXT_TYPE_CLI_COMMAND) {
|
||||
sendPacketError(ERR_UNSUPPORTED);
|
||||
return true;
|
||||
}
|
||||
@@ -1242,10 +1245,10 @@ bool CompanionMesh::vcontactHandleFrame(const uint8_t *data, size_t len)
|
||||
}
|
||||
|
||||
LOG_INF("vcontact CLI: '%s'", line);
|
||||
char reply[VCONTACT_CLI_REPLY_SIZE];
|
||||
char reply[COMPANION_CLI_REPLY_SIZE];
|
||||
reply[0] = '\0';
|
||||
if (_vcontact_cli_cb) {
|
||||
_vcontact_cli_cb(line, reply);
|
||||
if (_cli_exec_cb) {
|
||||
_cli_exec_cb(line, reply);
|
||||
} else {
|
||||
strcpy(reply, "CLI not available");
|
||||
}
|
||||
@@ -2637,16 +2640,18 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
|
||||
pub_key_prefix[3], pub_key_prefix[4], pub_key_prefix[5]);
|
||||
|
||||
ContactInfo *contact = lookupContactByPubKey(pub_key_prefix, 6);
|
||||
if (contact && (txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_CLI_DATA)) {
|
||||
if (contact && (txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_CLI_DATA ||
|
||||
txt_type == TXT_TYPE_CLI_COMMAND)) {
|
||||
LOG_DBG("CMD_SEND_TXT_MSG: contact='%s' text='%s' text_len=%u", contact->name, text, (unsigned)text_len);
|
||||
|
||||
uint32_t expected_ack = 0, est_timeout;
|
||||
int result;
|
||||
|
||||
if (txt_type == TXT_TYPE_CLI_DATA) {
|
||||
if (txt_type == TXT_TYPE_CLI_DATA || txt_type == TXT_TYPE_CLI_COMMAND) {
|
||||
// Use node's RTC instead of app timestamp
|
||||
msg_timestamp = getRTCClock()->getCurrentTimeUnique();
|
||||
result = sendCommandData(*contact, msg_timestamp, attempt, text, est_timeout);
|
||||
result = sendCommandData(*contact, msg_timestamp, attempt, txt_type, text,
|
||||
est_timeout);
|
||||
} else {
|
||||
result = sendMessage(*contact, msg_timestamp, attempt, text, expected_ack, est_timeout);
|
||||
}
|
||||
@@ -2919,7 +2924,13 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
|
||||
static const uint8_t version[20] = FIRMWARE_VERSION; // injected by CMakeLists.txt
|
||||
uint8_t rsp[82];
|
||||
rsp[0] = PACKET_DEVICE_INFO;
|
||||
rsp[1] = 13; // FIRMWARE_VER_CODE - v13 = CMD_SEND_ANON_REQ to non-contact pubkey (transient anon contacts)
|
||||
/* v14 = CMD_RUN_CLI_COMMAND / PACKET_CLI_REPLY, plus TXT_TYPE_CLI_COMMAND
|
||||
* accepted by CMD_SEND_TXT_MSG and by the repeater / room-server admin
|
||||
* CLI. Deliberately NOT the whole of upstream's v14: a companion here
|
||||
* never *executes* an over-the-air TXT_TYPE_CLI_COMMAND, so contact
|
||||
* flag 0x10 (remote-CLI-allowed) is stored and ignored. See
|
||||
* devdocs/UPSTREAM_TRACKER.md for why. */
|
||||
rsp[1] = 14; // FIRMWARE_VER_CODE
|
||||
rsp[2] = (MAX_CONTACTS / 2 > 255) ? 255 : (MAX_CONTACTS / 2); // protocol byte, app multiplies by 2
|
||||
rsp[3] = MAX_GROUP_CHANNELS;
|
||||
put_le32(&rsp[4], prefs.ble_pin ? prefs.ble_pin : 123456); // BLE PIN
|
||||
@@ -3727,6 +3738,48 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
|
||||
}
|
||||
return true;
|
||||
|
||||
case CMD_RUN_CLI_COMMAND:
|
||||
/* Runs a CLI line on behalf of the connected app. This is the same
|
||||
* local CLI the USB text sideband and the v-contact chat already reach,
|
||||
* over the same already-paired transport, so it adds no reach that an
|
||||
* app connected to this node did not already have — it is executed with
|
||||
* sender_timestamp 0 (local) for exactly that reason. */
|
||||
if (len >= 2) {
|
||||
if (_cli_exec_cb == nullptr) {
|
||||
sendPacketError(ERR_UNSUPPORTED);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* `data` is const, so the line has to be copied out to be
|
||||
* null-terminated. A command can never exceed one frame. */
|
||||
char line[MAX_FRAME_SIZE];
|
||||
size_t cmd_len = len - 1;
|
||||
if (cmd_len > sizeof(line) - 1) cmd_len = sizeof(line) - 1;
|
||||
memcpy(line, &data[1], cmd_len);
|
||||
line[cmd_len] = '\0';
|
||||
|
||||
/* Reply is built in place behind the frame type byte; the callback
|
||||
* contract requires COMPANION_CLI_REPLY_SIZE of room. */
|
||||
uint8_t rsp[1 + COMPANION_CLI_REPLY_SIZE];
|
||||
char *reply = (char *)&rsp[1];
|
||||
rsp[0] = PACKET_CLI_REPLY;
|
||||
reply[0] = '\0';
|
||||
_cli_exec_cb(line, reply);
|
||||
if (reply[0] == '\0') {
|
||||
strcpy(reply, "Unknown command");
|
||||
}
|
||||
|
||||
/* A CommonCLI reply can be longer than one companion frame and the
|
||||
* app protocol has no continuation for this response, so truncate
|
||||
* rather than drop. */
|
||||
size_t rlen = strlen(reply);
|
||||
if (rlen > MAX_FRAME_SIZE - 1) rlen = MAX_FRAME_SIZE - 1;
|
||||
writeFrame(rsp, rlen + 1);
|
||||
} else {
|
||||
sendPacketError(ERR_ILLEGAL_ARG);
|
||||
}
|
||||
return true;
|
||||
|
||||
case CMD_SEND_RAW_PACKET:
|
||||
if (len >= 4) {
|
||||
mesh::Packet *pkt = obtainNewPacket();
|
||||
|
||||
@@ -91,11 +91,12 @@ typedef void (*RadioReconfigureCallback)(void);
|
||||
/* BLE PIN change callback */
|
||||
typedef void (*PinChangeCallback)(uint32_t new_pin);
|
||||
|
||||
/* V-contact CLI execution callback — runs a text-CLI line and fills `reply`
|
||||
* (buffer is VCONTACT_CLI_REPLY_SIZE). Registered by main_companion so the
|
||||
* v-contact chat reuses the same CommonCLI instance as the USB text CLI. */
|
||||
#define VCONTACT_CLI_REPLY_SIZE 256
|
||||
typedef void (*VContactCLICallback)(const char *line, char *reply);
|
||||
/* Companion CLI execution callback — runs a text-CLI line and fills `reply`
|
||||
* (buffer is COMPANION_CLI_REPLY_SIZE). Registered by main_companion so every
|
||||
* companion-side CLI entry point shares one CommonCLI instance: the USB text
|
||||
* CLI, the v-contact chat, and CMD_RUN_CLI_COMMAND from the app. */
|
||||
#define COMPANION_CLI_REPLY_SIZE 256
|
||||
typedef void (*CompanionCLICallback)(const char *line, char *reply);
|
||||
|
||||
/**
|
||||
* CompanionMesh: Application layer for ZephCore Companion device
|
||||
@@ -170,7 +171,7 @@ public:
|
||||
* half is derived and dropped: never stored, never used. The key is never
|
||||
* registered in the RF RX matching path, so over-the-air traffic addressed
|
||||
* to it is inert. */
|
||||
void setVContactCLICallback(VContactCLICallback cb) { _vcontact_cli_cb = cb; }
|
||||
void setCLICallback(CompanionCLICallback cb) { _cli_exec_cb = cb; }
|
||||
bool isVContactEnabled() const { return prefs.v_contact_enabled != 0; }
|
||||
/** Queue an unsolicited v-contact message (battery alert, restart reason).
|
||||
* Goes through the offline queue — delivered on next app connect/sync. */
|
||||
@@ -510,7 +511,7 @@ private:
|
||||
* the clock was invalid (pre-1970s epoch) when we would have stamped it,
|
||||
* so the contact is withheld from sync/adverts until a time source
|
||||
* arrives — otherwise the app shows a 1970 last-heard timestamp. */
|
||||
VContactCLICallback _vcontact_cli_cb;
|
||||
CompanionCLICallback _cli_exec_cb;
|
||||
uint8_t _vcontact_pubkey[PUB_KEY_SIZE];
|
||||
uint32_t _vcontact_lastmod;
|
||||
/* Dedupe app resends: a retry reuses the message timestamp (only the
|
||||
|
||||
@@ -860,7 +860,11 @@ void RepeaterMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
memcpy(&sender_timestamp, data, 4);
|
||||
uint8_t flags = (data[4] >> 2);
|
||||
|
||||
if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) {
|
||||
/* TXT_TYPE_CLI_COMMAND (v1.18+) is handled exactly like TXT_TYPE_CLI_DATA
|
||||
* here: both reach this block only behind client->isAdmin(), and both
|
||||
* are covered by the monotonic sender_timestamp / is_retry gates below. */
|
||||
if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA ||
|
||||
flags == TXT_TYPE_CLI_COMMAND)) {
|
||||
LOG_DBG("onPeerDataRecv: unsupported text type: flags=%02x", flags);
|
||||
} else if (sender_timestamp >= client->last_timestamp) {
|
||||
bool is_retry = (sender_timestamp == client->last_timestamp);
|
||||
|
||||
@@ -601,7 +601,11 @@ void RoomServerMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int send
|
||||
memcpy(&sender_timestamp, data, 4);
|
||||
uint8_t flags = (data[4] >> 2);
|
||||
|
||||
if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) {
|
||||
/* TXT_TYPE_CLI_COMMAND (v1.18+) is handled exactly like TXT_TYPE_CLI_DATA
|
||||
* here: both stay behind client->isAdmin() below, and both are covered
|
||||
* by the monotonic sender_timestamp / is_retry gates. */
|
||||
if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA ||
|
||||
flags == TXT_TYPE_CLI_COMMAND)) {
|
||||
LOG_DBG("onPeerDataRecv: unsupported text type: flags=%02x", flags);
|
||||
} else if (sender_timestamp >= client->last_timestamp) {
|
||||
bool is_retry = (sender_timestamp == client->last_timestamp);
|
||||
@@ -618,7 +622,7 @@ void RoomServerMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int send
|
||||
|
||||
uint8_t temp[5 + CLI_REMOTE_REPLY_SIZE];
|
||||
bool send_ack;
|
||||
if (flags == TXT_TYPE_CLI_DATA) { // admin CLI over the air
|
||||
if (flags == TXT_TYPE_CLI_DATA || flags == TXT_TYPE_CLI_COMMAND) { // admin CLI over the air
|
||||
if (client->isAdmin()) {
|
||||
if (is_retry) {
|
||||
temp[5] = 0;
|
||||
|
||||
@@ -318,6 +318,13 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender
|
||||
} else {
|
||||
sendAckTo(from, (uint8_t *)&ack_hash, 4);
|
||||
}
|
||||
} else {
|
||||
/* Includes TXT_TYPE_CLI_COMMAND (v1.18+). Upstream's companion
|
||||
* executes it when the contact carries flag 0x10; ZephCore
|
||||
* deliberately does not — see devdocs/UPSTREAM_TRACKER.md. Logged
|
||||
* rather than dropped silently so the case is diagnosable. */
|
||||
LOG_DBG("onPeerDataRecv: unhandled text type flags=%d from '%s'",
|
||||
flags, from.name);
|
||||
}
|
||||
} else if (type == PAYLOAD_TYPE_REQ && len > 4) {
|
||||
uint32_t sender_timestamp;
|
||||
@@ -533,14 +540,14 @@ int BaseChatMesh::sendMessage(const ContactInfo &recipient, uint32_t timestamp,
|
||||
}
|
||||
|
||||
int BaseChatMesh::sendCommandData(const ContactInfo &recipient, uint32_t timestamp, uint8_t attempt,
|
||||
const char *text, uint32_t &est_timeout)
|
||||
uint8_t txt_type, const char *text, uint32_t &est_timeout)
|
||||
{
|
||||
int text_len = strlen(text);
|
||||
if (text_len > MAX_TEXT_LEN) return MSG_SEND_FAILED;
|
||||
|
||||
uint8_t temp[5 + MAX_TEXT_LEN + 1];
|
||||
memcpy(temp, ×tamp, 4);
|
||||
temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2);
|
||||
temp[4] = (attempt & 3) | (txt_type << 2);
|
||||
memcpy(&temp[5], text, text_len + 1);
|
||||
|
||||
mesh::Packet *pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id,
|
||||
|
||||
@@ -193,8 +193,11 @@ public:
|
||||
virtual bool sendSelfAdvert(bool flood) { (void)flood; return false; }
|
||||
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);
|
||||
/* @param txt_type TXT_TYPE_CLI_DATA (understood by every MeshCore version)
|
||||
* or TXT_TYPE_CLI_COMMAND (v1.18+ only). Callers that must reach older
|
||||
* repeaters have to keep using TXT_TYPE_CLI_DATA. */
|
||||
int sendCommandData(const ContactInfo &recipient, uint32_t timestamp, uint8_t attempt,
|
||||
uint8_t txt_type, const char *text, uint32_t &est_timeout);
|
||||
/* @param out_hash if non-null, filled with the FNV-1a packet hash so the
|
||||
* caller can later query the contention tracker for "how many neighbors
|
||||
* heard and retransmitted this?" — used by the joystick UI's send-feedback
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
#include <string.h>
|
||||
|
||||
#define TXT_TYPE_PLAIN 0 // a plain text message
|
||||
#define TXT_TYPE_CLI_DATA 1 // a CLI command
|
||||
#define TXT_TYPE_CLI_DATA 1 // a CLI command -or- reply
|
||||
#define TXT_TYPE_SIGNED_PLAIN 2 // plain text, signed by sender
|
||||
#define TXT_TYPE_CLI_COMMAND 3 // a CLI command (explicitly), v1.18+
|
||||
#define DATA_TYPE_RESERVED 0x0000 // reserved for future use
|
||||
#define DATA_TYPE_DEV 0xFFFF // developer namespace for experimenting with group/channel datagrams
|
||||
|
||||
|
||||
@@ -1113,7 +1113,9 @@ bool RepeaterAdminScreen::sendCLI(const char *cmd)
|
||||
}
|
||||
uint32_t ts = _rtc ? _rtc->getCurrentTimeUnique() : k_uptime_get_32();
|
||||
uint32_t est_timeout = 0;
|
||||
int result = mesh->sendCommandData(*contact, ts, 0, cmd, est_timeout);
|
||||
/* TXT_TYPE_CLI_DATA, not TXT_TYPE_CLI_COMMAND: this talks to repeaters of
|
||||
* any vintage, and type 3 is only understood from v1.18 on. */
|
||||
int result = mesh->sendCommandData(*contact, ts, 0, TXT_TYPE_CLI_DATA, cmd, est_timeout);
|
||||
if (result == MSG_SEND_FAILED) {
|
||||
_task->showAlert("Send failed", 1500);
|
||||
return false;
|
||||
|
||||
@@ -1090,9 +1090,9 @@ static bool companion_shutdown_hook(int reason)
|
||||
/* Transport-neutral CLI line execution — runs on the MAIN thread only
|
||||
* (CommonCLI::handleCommand touches mesh state shared with loop()). Both the
|
||||
* USB text sideband and the v-contact chat funnel through here. `reply` must
|
||||
* be CLI_REPLY_SIZE (== VCONTACT_CLI_REPLY_SIZE). */
|
||||
static_assert(VCONTACT_CLI_REPLY_SIZE == CLI_REPLY_SIZE,
|
||||
"v-contact reply buffer must match CommonCLI reply size");
|
||||
* be CLI_REPLY_SIZE (== COMPANION_CLI_REPLY_SIZE). */
|
||||
static_assert(COMPANION_CLI_REPLY_SIZE == CLI_REPLY_SIZE,
|
||||
"companion CLI reply buffer must match CommonCLI reply size");
|
||||
|
||||
static void companion_cli_exec(const char *line, char *reply)
|
||||
{
|
||||
@@ -1510,7 +1510,7 @@ int main(void)
|
||||
zephcore_ble_set_passkey(new_pin);
|
||||
});
|
||||
/* V-contact chat lines run the same CLI as the USB text sideband. */
|
||||
companion_mesh.setVContactCLICallback(companion_cli_exec);
|
||||
companion_mesh.setCLICallback(companion_cli_exec);
|
||||
companion_mesh_ptr = &companion_mesh;
|
||||
|
||||
/* Set LoRa callbacks for event-driven packet processing */
|
||||
|
||||
Reference in New Issue
Block a user