mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 21:08:19 +00:00
companion CLI
This commit is contained in:
@@ -640,6 +640,7 @@ else()
|
||||
if(CONFIG_LOG OR CONFIG_ZEPHCORE_COMPANION_USB)
|
||||
target_sources(app PRIVATE
|
||||
adapters/usb/ZephyrCompanionUSB.cpp
|
||||
helpers/CommonCLI.cpp # backs the USB-only text CLI dispatch
|
||||
)
|
||||
if(NOT CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT AND (CONFIG_USB_CDC_ACM OR CONFIG_USBD_CDC_ACM_CLASS))
|
||||
target_sources(app PRIVATE
|
||||
|
||||
@@ -40,12 +40,15 @@ LOG_MODULE_REGISTER(zephcore_usb, CONFIG_ZEPHCORE_USB_LOG_LEVEL);
|
||||
#define USB_FRAME_TX_SYNC '>'
|
||||
|
||||
enum usb_rx_state {
|
||||
USB_RX_IDLE = 0, /* waiting for '<' sync byte */
|
||||
USB_RX_IDLE = 0, /* waiting for '<' sync byte or first text byte */
|
||||
USB_RX_LEN_LO, /* got sync, waiting len LSB */
|
||||
USB_RX_LEN_HI, /* got len LSB, waiting len MSB */
|
||||
USB_RX_PAYLOAD, /* accumulating payload */
|
||||
USB_RX_PAYLOAD, /* accumulating V3 payload */
|
||||
USB_RX_TEXT, /* text CLI line mode — accumulate until CR/LF */
|
||||
};
|
||||
|
||||
#define USB_TEXT_LINE_MAX 128
|
||||
|
||||
/* USB CDC state */
|
||||
static const struct device *usb_dev;
|
||||
static uint8_t usb_ring_buf_data[USB_RING_BUF_SIZE];
|
||||
@@ -75,6 +78,33 @@ static void (*s_session_end_cb)(void);
|
||||
/* TX-drained callback (mirrors BLE on_tx_idle) — re-kicks the contact pump. */
|
||||
static void (*s_tx_drain_cb)(void);
|
||||
|
||||
/* CLI text line callback — fired when a complete line arrives in text mode. */
|
||||
static void (*s_cli_line_cb)(const char *line);
|
||||
static char usb_text_line[USB_TEXT_LINE_MAX];
|
||||
static uint8_t usb_text_len;
|
||||
|
||||
/* Banner shown once per text-CLI session, matching the repeater's serial CLI so
|
||||
* the flasher.meshcore.io console renders identically. Emitted only after text
|
||||
* mode is detected (first printable byte) — never on the binary V3 path, so an
|
||||
* official client connected over USB never receives stray text. CRLF endings:
|
||||
* the console's LineBreakTransformer only splits on "\r\n". */
|
||||
#define USB_CLI_BANNER "\r\n=== ZephCore Companion ===\r\n"
|
||||
static bool usb_text_banner_sent;
|
||||
|
||||
/* True when the USB interface was claimed by the text CLI (not a binary V3
|
||||
* companion app). While set, main suppresses binary frame/push output to USB so
|
||||
* the serial console only ever sees text. Set on the claiming transition, reset
|
||||
* when the session ends. */
|
||||
static bool usb_session_is_text;
|
||||
|
||||
/* Echo text-mode bytes back via the interrupt-driven TX ring (NOT uart_poll_out,
|
||||
* unlike the repeater) so echo stays off the polling path and ordered with the
|
||||
* reply. Only ever called from the text branches, so binary sessions see no echo. */
|
||||
static inline void usb_cli_echo(const char *s, size_t n)
|
||||
{
|
||||
zephcore_usb_companion_write_text(s, n);
|
||||
}
|
||||
|
||||
/* Work items */
|
||||
static void usb_rx_work_fn(struct k_work *work);
|
||||
|
||||
@@ -126,6 +156,39 @@ static void usb_uart_isr(const struct device *dev, void *user_data)
|
||||
}
|
||||
}
|
||||
|
||||
/* Claim the interface for USB on the first inbound traffic of a session —
|
||||
* a binary frame or a complete CLI line — mirroring BLE, which claims on
|
||||
* connect. The official client opens with CMD_DEVICE_QUERY (0x16), not
|
||||
* CMD_APP_START, so the claim is gated on any first traffic, not a specific
|
||||
* opcode. Returns true when USB owns the interface and the caller should
|
||||
* process the input; false while a BLE session holds it. */
|
||||
static bool usb_claim_active(uint8_t log_tag, bool is_text)
|
||||
{
|
||||
if (zephcore_ble_get_active_iface() != ZEPHCORE_IFACE_USB) {
|
||||
/* try_claim succeeds when idle or already USB (reconnect) and fails
|
||||
* only while a BLE session is live, so USB can't steal it — and the
|
||||
* compare-and-set can't race a concurrent BLE claim. */
|
||||
if (zephcore_ble_iface_try_claim(ZEPHCORE_IFACE_USB)) {
|
||||
/* Record session kind on the claiming transition so main can
|
||||
* suppress binary output for a text-CLI session. */
|
||||
usb_session_is_text = is_text;
|
||||
zephcore_ble_set_enabled(false);
|
||||
LOG_INF("usb_rx: first traffic 0x%02x → IFACE_USB (%s)", log_tag,
|
||||
is_text ? "text" : "binary");
|
||||
/* New USB session — mirror BLE's on-connect UI notification
|
||||
* (Arduino shows "connected" for serial transports too). Fires
|
||||
* once per session: try_claim only returns true on NONE→USB. */
|
||||
if (s_session_start_cb) {
|
||||
s_session_start_cb();
|
||||
}
|
||||
} else {
|
||||
LOG_INF("usb_rx: traffic 0x%02x ignored, BLE is active", log_tag);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* USB RX work - parses V3 frames from ring buffer */
|
||||
static void usb_rx_work_fn(struct k_work *work)
|
||||
{
|
||||
@@ -133,24 +196,86 @@ static void usb_rx_work_fn(struct k_work *work)
|
||||
|
||||
uint8_t byte;
|
||||
|
||||
/* Timeout partial frames — if we've been mid-frame too long without
|
||||
* completing, reset the parser state and resync on the next sync byte. */
|
||||
/* Timeout partial input — if we've been mid-frame or mid-text-line too
|
||||
* long without completing, reset the parser and resync. usb_frame_start_time
|
||||
* is refreshed on every text byte (below), so for USB_RX_TEXT this acts as an
|
||||
* inactivity watchdog: it recovers a stray printable byte back to IDLE (so a
|
||||
* later binary frame parses) without truncating a line that is actively being
|
||||
* typed. Note this only runs when bytes arrive — it is not a timer and never
|
||||
* wakes a sleeping node. */
|
||||
if (usb_rx_st != USB_RX_IDLE &&
|
||||
(k_uptime_get_32() - usb_frame_start_time) > USB_FRAME_TIMEOUT_MS) {
|
||||
LOG_WRN("usb_rx: partial frame timeout (state=%d, expected=%u), resync",
|
||||
LOG_WRN("usb_rx: partial input timeout (state=%d, expected=%u), resync",
|
||||
usb_rx_st, usb_frame_len);
|
||||
usb_rx_st = USB_RX_IDLE;
|
||||
usb_frame_len = 0;
|
||||
usb_rx_idx = 0;
|
||||
usb_text_len = 0;
|
||||
}
|
||||
|
||||
while (ring_buf_get(&usb_ring_buf, &byte, 1) == 1) {
|
||||
switch (usb_rx_st) {
|
||||
case USB_RX_IDLE:
|
||||
/* Ignore stray bytes until the '<' sync byte arrives. */
|
||||
if (byte == USB_FRAME_RX_SYNC) {
|
||||
/* V3 binary framing — normal app protocol. */
|
||||
usb_rx_st = USB_RX_LEN_LO;
|
||||
usb_frame_start_time = k_uptime_get_32();
|
||||
} else if (byte >= 0x20 && byte <= 0x7E) {
|
||||
/* Printable ASCII. Enter text CLI mode only when the
|
||||
* interface is idle, or we're already in a text session
|
||||
* (subsequent command lines). Never during a BLE session,
|
||||
* nor a binary USB companion session — an official client's
|
||||
* bytes must not be parsed as CLI. The iface read is
|
||||
* side-effect-free; the claim happens later, on Enter. */
|
||||
enum zephcore_iface ifc = zephcore_ble_get_active_iface();
|
||||
if (ifc == ZEPHCORE_IFACE_NONE ||
|
||||
(ifc == ZEPHCORE_IFACE_USB && usb_session_is_text)) {
|
||||
/* Arm the inactivity watchdog so a stray byte resyncs
|
||||
* to IDLE on its own. */
|
||||
usb_text_len = 0;
|
||||
usb_text_line[usb_text_len++] = (char)byte;
|
||||
usb_frame_start_time = k_uptime_get_32();
|
||||
usb_rx_st = USB_RX_TEXT;
|
||||
/* Banner once per session, then echo. */
|
||||
if (!usb_text_banner_sent) {
|
||||
usb_text_banner_sent = true;
|
||||
usb_cli_echo(USB_CLI_BANNER, sizeof(USB_CLI_BANNER) - 1);
|
||||
}
|
||||
usb_cli_echo((const char *)&byte, 1);
|
||||
}
|
||||
/* else: printable but iface busy/binary — ignore */
|
||||
}
|
||||
/* else: ignore control bytes / noise */
|
||||
break;
|
||||
|
||||
case USB_RX_TEXT:
|
||||
/* Any text byte is activity — refresh the inactivity watchdog. */
|
||||
usb_frame_start_time = k_uptime_get_32();
|
||||
if (byte == '\n' || byte == '\r') {
|
||||
/* Line complete — dispatch if non-empty. Claim USB first
|
||||
* (refused while BLE owns the session) so a CLI command can't
|
||||
* mutate state out from under an active BLE app. The reply
|
||||
* (companion_cli_dispatch) emits its own leading CRLF, so the
|
||||
* Enter keystroke itself is not echoed — matching the repeater. */
|
||||
if (usb_text_len > 0) {
|
||||
usb_text_line[usb_text_len] = '\0';
|
||||
if (usb_claim_active((uint8_t)usb_text_line[0], true) && s_cli_line_cb) {
|
||||
s_cli_line_cb(usb_text_line);
|
||||
}
|
||||
}
|
||||
usb_text_len = 0;
|
||||
usb_rx_st = USB_RX_IDLE;
|
||||
} else if (byte == 0x7F || byte == '\b') {
|
||||
/* Backspace — erase one char on the terminal too. */
|
||||
if (usb_text_len > 0) {
|
||||
usb_text_len--;
|
||||
usb_cli_echo("\b \b", 3);
|
||||
}
|
||||
} else if (byte >= 0x20 && byte <= 0x7E) {
|
||||
if (usb_text_len < USB_TEXT_LINE_MAX - 1) {
|
||||
usb_text_line[usb_text_len++] = (char)byte;
|
||||
usb_cli_echo((const char *)&byte, 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case USB_RX_LEN_LO:
|
||||
@@ -178,34 +303,8 @@ static void usb_rx_work_fn(struct k_work *work)
|
||||
|
||||
LOG_DBG("usb_rx: frame complete len=%u hdr=0x%02x", payload_len, payload[0]);
|
||||
|
||||
/* Claim the interface for USB on the FIRST inbound frame of
|
||||
* any opcode — mirroring BLE, which claims on connect. The
|
||||
* official client opens with CMD_DEVICE_QUERY (0x16), not
|
||||
* CMD_APP_START (0x01); gating the claim on 0x01 dropped that
|
||||
* first query and the client timed out waiting for a reply. */
|
||||
if (zephcore_ble_get_active_iface() != ZEPHCORE_IFACE_USB) {
|
||||
/* Atomically claim the interface for USB unless BLE
|
||||
* already owns it. try_claim succeeds when idle or
|
||||
* already USB (reconnect) and fails only while a BLE
|
||||
* session is live, so USB can't steal it — and the
|
||||
* compare-and-set can't race a concurrent BLE claim. */
|
||||
if (zephcore_ble_iface_try_claim(ZEPHCORE_IFACE_USB)) {
|
||||
zephcore_ble_set_enabled(false);
|
||||
LOG_INF("usb_rx: first frame 0x%02x → IFACE_USB", payload[0]);
|
||||
/* New USB session — mirror BLE's on-connect UI
|
||||
* notification (Arduino shows "connected" for serial
|
||||
* transports too). Fires once per session: try_claim
|
||||
* only returns true on the NONE→USB transition. */
|
||||
if (s_session_start_cb) {
|
||||
s_session_start_cb();
|
||||
}
|
||||
} else {
|
||||
LOG_INF("usb_rx: frame 0x%02x ignored, BLE is active", payload[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Only process if USB is active interface */
|
||||
if (zephcore_ble_get_active_iface() == ZEPHCORE_IFACE_USB) {
|
||||
/* Claim USB (refused while BLE owns the session), then process. */
|
||||
if (usb_claim_active(payload[0], false)) {
|
||||
struct {
|
||||
uint16_t len;
|
||||
uint8_t buf[MAX_FRAME_SIZE];
|
||||
@@ -265,6 +364,9 @@ static void on_dtr_change(bool dtr_active)
|
||||
usb_rx_st = USB_RX_IDLE;
|
||||
usb_frame_len = 0;
|
||||
usb_rx_idx = 0;
|
||||
usb_text_len = 0;
|
||||
usb_text_banner_sent = false; /* re-banner the next text session */
|
||||
usb_session_is_text = false;
|
||||
|
||||
/* Discard any pending TX from the closed session. */
|
||||
uart_irq_tx_disable(usb_dev);
|
||||
@@ -327,6 +429,9 @@ void zephcore_usb_companion_reset_rx(void)
|
||||
usb_rx_st = USB_RX_IDLE;
|
||||
usb_frame_len = 0;
|
||||
usb_rx_idx = 0;
|
||||
usb_text_len = 0;
|
||||
usb_text_banner_sent = false;
|
||||
usb_session_is_text = false;
|
||||
|
||||
/* Drop any half-sent TX too — the session it belonged to is gone. */
|
||||
if (usb_dev) {
|
||||
@@ -337,6 +442,11 @@ void zephcore_usb_companion_reset_rx(void)
|
||||
k_spin_unlock(&usb_tx_lock, key);
|
||||
}
|
||||
|
||||
bool zephcore_usb_companion_is_text_session(void)
|
||||
{
|
||||
return usb_session_is_text;
|
||||
}
|
||||
|
||||
void zephcore_usb_companion_set_session_start_cb(void (*cb)(void))
|
||||
{
|
||||
s_session_start_cb = cb;
|
||||
@@ -352,6 +462,22 @@ void zephcore_usb_companion_set_tx_drain_cb(void (*cb)(void))
|
||||
s_tx_drain_cb = cb;
|
||||
}
|
||||
|
||||
void zephcore_usb_companion_set_cli_line_cb(void (*cb)(const char *line))
|
||||
{
|
||||
s_cli_line_cb = cb;
|
||||
}
|
||||
|
||||
void zephcore_usb_companion_write_text(const char *text, size_t len)
|
||||
{
|
||||
if (!usb_dev || !text || len == 0) {
|
||||
return;
|
||||
}
|
||||
k_spinlock_key_t key = k_spin_lock(&usb_tx_lock);
|
||||
ring_buf_put(&usb_tx_ring_buf, (const uint8_t *)text, len);
|
||||
k_spin_unlock(&usb_tx_lock, key);
|
||||
uart_irq_tx_enable(usb_dev);
|
||||
}
|
||||
|
||||
void zephcore_usb_companion_init(struct k_event *mesh_events,
|
||||
struct k_work *rx_work,
|
||||
uint32_t mesh_event_ble_rx,
|
||||
|
||||
@@ -71,6 +71,27 @@ void zephcore_usb_companion_set_session_start_cb(void (*cb)(void));
|
||||
*/
|
||||
void zephcore_usb_companion_set_session_end_cb(void (*cb)(void));
|
||||
|
||||
/**
|
||||
* Register a callback fired when a complete text CLI line arrives over USB.
|
||||
* Activated when the first byte of a session is not the V3 sync byte ('<').
|
||||
* The line is null-terminated and has any trailing CR/LF stripped. May be NULL.
|
||||
*/
|
||||
void zephcore_usb_companion_set_cli_line_cb(void (*cb)(const char *line));
|
||||
|
||||
/**
|
||||
* Write a raw text reply to the USB CDC port (no V3 framing).
|
||||
* Used to send CLI responses when in text mode.
|
||||
*/
|
||||
void zephcore_usb_companion_write_text(const char *text, size_t len);
|
||||
|
||||
/**
|
||||
* True when the active USB session was opened by the text CLI rather than a
|
||||
* binary V3 companion app. Main uses this to suppress binary frame/push output
|
||||
* to USB so the serial console only ever receives text. False when no USB
|
||||
* session or when a binary companion owns the interface.
|
||||
*/
|
||||
bool zephcore_usb_companion_is_text_session(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -35,13 +35,15 @@ public:
|
||||
virtual void eraseLogFile() = 0;
|
||||
virtual void dumpLogFile() = 0;
|
||||
virtual void setTxPower(int8_t power_dbm) = 0;
|
||||
virtual void formatNeighborsReply(char* reply) = 0;
|
||||
/* Repeater-specific — default replies keep companion builds clean.
|
||||
* Repeater overrides all four; companions get "not available". */
|
||||
virtual void formatNeighborsReply(char* reply) { strcpy(reply, "not available"); }
|
||||
virtual void removeNeighbor(const uint8_t* pubkey, int key_len) {
|
||||
// no-op by default
|
||||
(void)pubkey; (void)key_len;
|
||||
}
|
||||
virtual void formatStatsReply(char* reply) = 0;
|
||||
virtual void formatRadioStatsReply(char* reply) = 0;
|
||||
virtual void formatPacketStatsReply(char* reply) = 0;
|
||||
virtual void formatStatsReply(char* reply) { strcpy(reply, "not available"); }
|
||||
virtual void formatRadioStatsReply(char* reply) { strcpy(reply, "not available"); }
|
||||
virtual void formatPacketStatsReply(char* reply) { strcpy(reply, "not available"); }
|
||||
virtual mesh::LocalIdentity& getSelfId() = 0;
|
||||
virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0;
|
||||
virtual void clearStats() = 0;
|
||||
|
||||
@@ -54,6 +54,9 @@ LOG_MODULE_REGISTER(zephcore_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
|
||||
#include <mesh/RadioIncludes.h>
|
||||
#ifdef ZEPHCORE_LORA
|
||||
#include <app/CompanionMesh.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <helpers/ClientACL.h>
|
||||
#include <helpers/battery_curve.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -253,6 +256,12 @@ static size_t write_frame(const uint8_t *src, size_t len)
|
||||
* send queue is never drained while USB is active (tx_drain no-ops on
|
||||
* IFACE_USB), so routing through zephcore_ble_send would strand the frame. */
|
||||
if (zephcore_ble_get_active_iface() == ZEPHCORE_IFACE_USB) {
|
||||
/* A text-CLI session owns USB — swallow binary V3 output so the serial
|
||||
* console never sees framed garbage. Report success so callers don't
|
||||
* retry/back off on a frame we intentionally dropped. */
|
||||
if (zephcore_usb_companion_is_text_session()) {
|
||||
return len;
|
||||
}
|
||||
return zephcore_usb_companion_write_frame(src, len);
|
||||
}
|
||||
#endif
|
||||
@@ -271,8 +280,10 @@ static void push_callback(uint8_t code, const uint8_t *data, size_t len)
|
||||
* dropped on a USB-attached client. */
|
||||
bool transport_up = zephcore_ble_is_connected();
|
||||
#if ZEPHCORE_USB_STACK
|
||||
/* A text-CLI session is not a binary companion — don't push V3 frames to it. */
|
||||
transport_up = transport_up ||
|
||||
(zephcore_ble_get_active_iface() == ZEPHCORE_IFACE_USB);
|
||||
(zephcore_ble_get_active_iface() == ZEPHCORE_IFACE_USB &&
|
||||
!zephcore_usb_companion_is_text_session());
|
||||
#endif
|
||||
if (!transport_up) return;
|
||||
|
||||
@@ -552,6 +563,81 @@ static void save_prefs_to_flash(void)
|
||||
{
|
||||
data_store.savePrefs(companion_mesh_ptr->prefs);
|
||||
}
|
||||
|
||||
/* ========== Companion CLI (USB text sideband only) ==========
|
||||
* Text CLI lives on USB CDC only: its '<' sync byte cleanly separates binary
|
||||
* V3 frames from text, whereas BLE NUS frames have no prefix and V3 opcodes
|
||||
* overlap printable ASCII, so a BLE text sideband can't be disambiguated. */
|
||||
#if ZEPHCORE_USB_STACK
|
||||
|
||||
class CompanionCLICallbacks : public CommonCLICallbacks {
|
||||
public:
|
||||
void savePrefs() override {
|
||||
data_store.savePrefs(companion_mesh.prefs);
|
||||
}
|
||||
const char* getFirmwareVer() override { return "v1.15.6-zephyr"; }
|
||||
const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; }
|
||||
const char* getRole() override { return "companion"; }
|
||||
bool formatFileSystem() override { return data_store.formatFileSystem(); }
|
||||
|
||||
/* Advert / timer controls — mesh-internal; stub for now. */
|
||||
void sendSelfAdvertisement(int delay_millis, bool flood) override {
|
||||
(void)delay_millis; (void)flood;
|
||||
}
|
||||
void updateAdvertTimer() override {}
|
||||
void updateFloodAdvertTimer() override {}
|
||||
|
||||
/* Log control — no log file on companion. */
|
||||
void setLoggingOn(bool enable) override { (void)enable; }
|
||||
void eraseLogFile() override {}
|
||||
void dumpLogFile() override {}
|
||||
|
||||
/* TX power — Zephyr LoRa driver has no runtime API; log only (matches repeater). */
|
||||
void setTxPower(int8_t power_dbm) override {
|
||||
LOG_INF("TX power %d dBm requested (reboot to apply)", power_dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity& getSelfId() override { return companion_mesh.self_id; }
|
||||
|
||||
void saveIdentity(const mesh::LocalIdentity& new_id) override {
|
||||
companion_mesh.self_id = new_id;
|
||||
data_store.saveMainIdentity(new_id);
|
||||
}
|
||||
|
||||
void clearStats() override {
|
||||
lora_radio.resetStats();
|
||||
companion_mesh.resetStats();
|
||||
}
|
||||
|
||||
/* Temp radio params — deferred; stub for now. */
|
||||
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr,
|
||||
int timeout_mins) override {
|
||||
(void)freq; (void)bw; (void)sf; (void)cr; (void)timeout_mins;
|
||||
}
|
||||
};
|
||||
|
||||
static CompanionCLICallbacks companion_cli_cbs;
|
||||
static ClientACL companion_acl; /* unused by CommonCLI but required by constructor */
|
||||
static CommonCLI companion_cli(zephyr_board, rtc_clock, companion_acl,
|
||||
&companion_mesh.prefs, &companion_cli_cbs);
|
||||
|
||||
/* Dispatch a CLI text line from USB, reply back over USB CDC. Output mirrors
|
||||
* the repeater's serial CLI ("\r\n -> <reply>\r\n", CRLF) so the
|
||||
* flasher.meshcore.io serial console renders companion replies identically. */
|
||||
static void companion_cli_dispatch(const char *line)
|
||||
{
|
||||
char reply[CLI_REPLY_SIZE];
|
||||
reply[0] = '\0';
|
||||
companion_cli.handleCommand(0, line, reply);
|
||||
if (reply[0] != '\0') {
|
||||
zephcore_usb_companion_write_text("\r\n -> ", 7);
|
||||
zephcore_usb_companion_write_text(reply, strlen(reply));
|
||||
}
|
||||
zephcore_usb_companion_write_text("\r\n", 2);
|
||||
}
|
||||
|
||||
#endif /* ZEPHCORE_USB_STACK */
|
||||
|
||||
#endif
|
||||
|
||||
/* GPS enable callback - logs state changes
|
||||
@@ -879,6 +965,8 @@ int main(void)
|
||||
zephcore_usb_companion_set_session_end_cb(usb_on_session_end);
|
||||
/* Resume the contact pump when the USB TX ring drains (≈ BLE on_tx_idle). */
|
||||
zephcore_usb_companion_set_tx_drain_cb(usb_on_tx_drain);
|
||||
/* Text CLI sideband — activates when first byte is not '<'. */
|
||||
zephcore_usb_companion_set_cli_line_cb(companion_cli_dispatch);
|
||||
#endif
|
||||
|
||||
#if IS_ENABLED(CONFIG_BT)
|
||||
|
||||
Reference in New Issue
Block a user