diff options
Diffstat (limited to 'firmware/c1224/src/common/usb_cmd.c')
| -rw-r--r-- | firmware/c1224/src/common/usb_cmd.c | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/firmware/c1224/src/common/usb_cmd.c b/firmware/c1224/src/common/usb_cmd.c new file mode 100644 index 0000000..3c412de --- /dev/null +++ b/firmware/c1224/src/common/usb_cmd.c @@ -0,0 +1,268 @@ +/* + * 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_bitbang.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 " + "cdt_raw=%d rxd_raw=%d bitrate=%u kiss_half_tnc=1\r\n", + c1224_state_string(status_get()), + (int)ptt_get(), + (int)sense_signal_active(), + (int)sense_packet_active(), + (int)sense_packet_flag(), + (int)gpio_get(PIN_MODEM_CDT), + (int)gpio_get(PIN_MODEM_RXD), + 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("Rate = stuffed crystal (FW bitbang C1224_BITRATE=%u).\r\n", + afsk_bitrate()); + printf("No softmodem · no Host↔Chip bit bridge · no baud switch.\r\n"); + printf("INI: texts + callsign device_id quiet + sense keys\r\n"); + return; + } + if (strcmp(cmd, "ID") == 0) { + const c1224_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 : "c1224", + 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[C1224_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); +} |
