summaryrefslogtreecommitdiff
path: root/firmware/s1224/src/common/usb_cmd.c
diff options
context:
space:
mode:
authorinfo@mode42.com <info@mode42.com>2026-08-07 18:09:58 +0000
committerinfo@mode42.com <info@mode42.com>2026-08-07 18:09:58 +0000
commita7362dc14bca1233eb34b41aefebe9cfb22684ac (patch)
treef727c87f92aeb503038deeecc24d3e074dc0197c /firmware/s1224/src/common/usb_cmd.c
Initial pushmain
Diffstat (limited to 'firmware/s1224/src/common/usb_cmd.c')
-rw-r--r--firmware/s1224/src/common/usb_cmd.c267
1 files changed, 267 insertions, 0 deletions
diff --git a/firmware/s1224/src/common/usb_cmd.c b/firmware/s1224/src/common/usb_cmd.c
new file mode 100644
index 0000000..e4a5fc7
--- /dev/null
+++ b/firmware/s1224/src/common/usb_cmd.c
@@ -0,0 +1,267 @@
+/*
+ * USB CDC multiplexer: KISS binary (0xC0 frames) + ASCII service commands.
+ * Transparent Host↔Chip bit bridge stays abandoned — KISS half-TNC is the exception.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+#include "usb_cmd.h"
+#include "pico/stdlib.h"
+#include "ptt.h"
+#include "status.h"
+#include "config.h"
+#include "sense.h"
+#include "board_pins.h"
+#include "afsk_soft.h"
+#include "kiss.h"
+#include "hardware/gpio.h"
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#define LINE_MAX 96
+
+static char g_line[LINE_MAX];
+static unsigned g_len;
+static kiss_rx_t g_kiss;
+static uint8_t g_kiss_payload[KISS_MAX_PAYLOAD];
+
+static void trim_copy_upper_cmd(char *dst, size_t dstsz, const char *src)
+{
+ while (*src && isspace((unsigned char)*src)) {
+ src++;
+ }
+ size_t i = 0;
+ while (src[i] && i + 1 < dstsz) {
+ dst[i] = (char)toupper((unsigned char)src[i]);
+ i++;
+ }
+ dst[i] = '\0';
+ while (i > 0 && isspace((unsigned char)dst[i - 1])) {
+ dst[--i] = '\0';
+ }
+}
+
+static void handle_line(void)
+{
+ char cmd[LINE_MAX];
+ trim_copy_upper_cmd(cmd, sizeof(cmd), g_line);
+ if (cmd[0] == '\0') {
+ return;
+ }
+
+ if (strcmp(cmd, "PTT ON") == 0 || strcmp(cmd, "PTT 1") == 0) {
+ ptt_set(true);
+ status_tick();
+ printf("OK PTT ON\r\n");
+ return;
+ }
+ if (strcmp(cmd, "PTT OFF") == 0 || strcmp(cmd, "PTT 0") == 0) {
+ ptt_set(false);
+ status_tick();
+ printf("OK PTT OFF\r\n");
+ return;
+ }
+ if (strcmp(cmd, "STATUS") == 0) {
+ printf("STATUS state=%s ptt=%d signal=%d packet=%d flag=%d "
+ "carrier=%d bitrate=%u softmodem=1 kiss_half_tnc=1\r\n",
+ s1224_state_string(status_get()),
+ (int)ptt_get(),
+ (int)sense_signal_active(),
+ (int)sense_packet_active(),
+ (int)sense_packet_flag(),
+ (int)afsk_rx_carrier_active(),
+ afsk_bitrate());
+ return;
+ }
+ if (strcmp(cmd, "HELP") == 0) {
+ printf("Commands:\r\n");
+ printf(" PTT ON | PTT OFF\r\n");
+ printf(" STATUS | HELP | ID\r\n");
+ printf(" QUIET ON|OFF | VERBOSE ON|OFF\r\n");
+ printf(" INI | GET <key> | SET <key>=<value>\r\n");
+ printf(" SAVE | RELOAD | DEFAULTS\r\n");
+ printf("Dual protocol: byte 0xC0 → KISS; line ASCII → service.\r\n");
+ printf("Packet: use KISS-only (any host). Service cmds optional.\r\n");
+ printf("Boot banner always on (source). quiet=1 damps extra ASCII.\r\n");
+ printf("PTT = GP9 (auto during KISS TX; manual PTT ON/OFF kept).\r\n");
+ printf("Softmodem AFSK · S1224_BITRATE=%u (build: 300/1200/2400).\r\n",
+ afsk_bitrate());
+ printf("PWM AF_TX GP6 · ADC AF_RX GP26 · no chip modem · no baud switch.\r\n");
+ printf("INI: texts + callsign device_id quiet + sense keys\r\n");
+ return;
+ }
+ if (strcmp(cmd, "ID") == 0) {
+ const s1224_config_t *cfg = config_get();
+ printf("ID device_id=%s callsign=%s bitrate=%u quiet=%d\r\n",
+ cfg->device_id[0] ? cfg->device_id : "s1224",
+ cfg->callsign,
+ afsk_bitrate(),
+ (int)cfg->quiet);
+ return;
+ }
+ if (strcmp(cmd, "QUIET ON") == 0 || strcmp(cmd, "QUIET 1") == 0) {
+ config_mutable()->quiet = true;
+ printf("OK QUIET ON\r\n");
+ return;
+ }
+ if (strcmp(cmd, "QUIET OFF") == 0 || strcmp(cmd, "QUIET 0") == 0) {
+ config_mutable()->quiet = false;
+ printf("OK QUIET OFF\r\n");
+ return;
+ }
+ if (strcmp(cmd, "VERBOSE ON") == 0 || strcmp(cmd, "VERBOSE 1") == 0) {
+ config_mutable()->quiet = false;
+ printf("OK VERBOSE ON\r\n");
+ return;
+ }
+ if (strcmp(cmd, "VERBOSE OFF") == 0 || strcmp(cmd, "VERBOSE 0") == 0) {
+ config_mutable()->quiet = true;
+ printf("OK VERBOSE OFF\r\n");
+ return;
+ }
+ if (strcmp(cmd, "INI") == 0) {
+ config_dump_ini();
+ return;
+ }
+ if (strcmp(cmd, "SAVE") == 0) {
+ printf(config_save_flash() ? "OK SAVE\r\n" : "ERR SAVE\r\n");
+ return;
+ }
+ if (strcmp(cmd, "RELOAD") == 0) {
+ config_load_defaults();
+ if (config_load_flash()) {
+ printf("OK RELOAD flash\r\n");
+ } else {
+ printf("OK RELOAD defaults (no flash)\r\n");
+ }
+ status_publish();
+ return;
+ }
+ if (strcmp(cmd, "DEFAULTS") == 0) {
+ config_load_defaults();
+ printf("OK DEFAULTS (RAM; SAVE to persist)\r\n");
+ status_publish();
+ return;
+ }
+ if (strncmp(cmd, "GET ", 4) == 0) {
+ char key[32];
+ const char *p = g_line;
+ while (*p && isspace((unsigned char)*p)) {
+ p++;
+ }
+ if (strncasecmp(p, "GET", 3) == 0) {
+ p += 3;
+ }
+ while (*p && isspace((unsigned char)*p)) {
+ p++;
+ }
+ size_t i = 0;
+ while (*p && !isspace((unsigned char)*p) && i + 1 < sizeof(key)) {
+ key[i++] = *p++;
+ }
+ key[i] = '\0';
+ char val[S1224_TEXT_MAX];
+ if (config_get_kv(key, val, sizeof(val))) {
+ printf("OK %s=%s\r\n", key, val);
+ } else {
+ printf("ERR unknown key\r\n");
+ }
+ return;
+ }
+ if (strncmp(cmd, "SET ", 4) == 0) {
+ const char *p = g_line;
+ while (*p && isspace((unsigned char)*p)) {
+ p++;
+ }
+ if (strncasecmp(p, "SET", 3) == 0) {
+ p += 3;
+ }
+ while (*p && isspace((unsigned char)*p)) {
+ p++;
+ }
+ char tmp[LINE_MAX];
+ snprintf(tmp, sizeof(tmp), "%s", p);
+ char *eq = strchr(tmp, '=');
+ if (!eq) {
+ printf("ERR SET key=value\r\n");
+ return;
+ }
+ *eq = '\0';
+ char *k = tmp;
+ char *v = eq + 1;
+ while (*k && isspace((unsigned char)*k)) {
+ k++;
+ }
+ char *ke = k + strlen(k);
+ while (ke > k && isspace((unsigned char)ke[-1])) {
+ *--ke = '\0';
+ }
+ while (*v && isspace((unsigned char)*v)) {
+ v++;
+ }
+ char *ve = v + strlen(v);
+ while (ve > v && isspace((unsigned char)ve[-1])) {
+ *--ve = '\0';
+ }
+ if (config_set_kv(k, v)) {
+ printf("OK SET %s=%s (SAVE to persist)\r\n", k, v);
+ status_publish();
+ } else {
+ printf("ERR unknown key or bad value\r\n");
+ }
+ return;
+ }
+
+ printf("ERR unknown (try HELP)\r\n");
+}
+
+void usb_cmd_on_rx_frame(const uint8_t *info, size_t len)
+{
+ uint8_t frame[KISS_MAX_PAYLOAD + 16];
+ size_t n = kiss_encode_data(info, len, frame, sizeof(frame));
+ for (size_t i = 0; i < n; i++) {
+ putchar_raw((int)frame[i]);
+ }
+}
+
+void usb_cmd_poll(void)
+{
+ int c;
+ while ((c = getchar_timeout_us(0)) != PICO_ERROR_TIMEOUT) {
+ uint8_t b = (uint8_t)c;
+
+ /* KISS path: FEND or mid-frame */
+ if (b == KISS_FEND || g_kiss.in_frame) {
+ size_t plen = 0;
+ if (kiss_rx_byte(&g_kiss, b, g_kiss_payload, &plen)) {
+ if (plen > 0 && !afsk_tx_busy()) {
+ afsk_tx_frame(g_kiss_payload, plen);
+ }
+ }
+ continue;
+ }
+
+ if (c == '\r' || c == '\n') {
+ if (g_len > 0) {
+ g_line[g_len] = '\0';
+ handle_line();
+ g_len = 0;
+ }
+ continue;
+ }
+ if (c == 0x08 || c == 0x7f) {
+ if (g_len > 0) {
+ g_len--;
+ }
+ continue;
+ }
+ if (g_len + 1 < LINE_MAX && c >= 32 && c < 127) {
+ g_line[g_len++] = (char)c;
+ }
+ }
+}
+
+void usb_cmd_init(void)
+{
+ g_len = 0;
+ kiss_rx_init(&g_kiss);
+}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com