diff --git a/radio/src/cli.cpp b/radio/src/cli.cpp index baaafd53e77..37abab70a3c 100644 --- a/radio/src/cli.cpp +++ b/radio/src/cli.cpp @@ -59,7 +59,13 @@ #define CLI_COMMAND_MAX_LEN 256 // CLI receive buffer size +#if defined(CLI_FILE_ACCESS) +// Holds one fput block (CLI_FPUT_MAX) while the SD card is busy +#define CLI_FPUT_MAX 4096 +#define CLI_RX_BUFFER_SIZE (CLI_FPUT_MAX + 64) +#else #define CLI_RX_BUFFER_SIZE 256 +#endif #define CLI_PRINT_BUFFER_SIZE 128 @@ -381,6 +387,186 @@ int cliLs(const char ** argv) return 0; } +#if defined(CLI_FILE_ACCESS) +#if defined(USB_SERIAL) +void usbSerialPutc(void*, uint8_t); +uint32_t usbSerialFreeSpace(); +#endif + +// The USB TX ring overwrites instead of blocking: wait until it can take `need` more bytes +static void cliTxWait(uint32_t need) +{ +#if defined(USB_SERIAL) + if (cliSendCb == usbSerialPutc) { + for (int i = 0; i < 1000 && usbSerialFreeSpace() < need; i++) sleep_ms(1); + } +#endif +} + +static const char * cliArg(const char ** argv, int index) +{ + return argv[index] ? argv[index] : ""; +} + +static bool cliUInt(const char ** argv, int index, uint32_t * val) +{ + const char * s = cliArg(argv, index); + char * end = nullptr; + *val = strtoul(s, &end, 0); + if (end == s || *end != '\0') { + cliSerialPrint("%s: Invalid argument \"%s\"", argv[0], s); + return false; + } + return true; +} + +// lsl : " " per entry +int cliLsl(const char ** argv) +{ + FILINFO fno; + DIR dir; + FRESULT res = f_opendir(&dir, cliArg(argv, 1)); + if (res != FR_OK) { + cliSerialPrint("%s: Invalid directory \"%s\"", argv[0], cliArg(argv, 1)); + return 0; + } + for (;;) { + res = f_readdir(&dir, &fno); + if (res != FR_OK || fno.fname[0] == 0) break; + cliSerialPrint("%c %lu %s", (fno.fattrib & AM_DIR) ? 'd' : 'f', + (unsigned long)fno.fsize, fno.fname); + } + f_closedir(&dir); + return 0; +} + +// fread : one line of hex, len <= CLI_FPUT_MAX +int cliFRead(const char ** argv) +{ + uint32_t offset, len; + if (!cliUInt(argv, 2, &offset) || !cliUInt(argv, 3, &len)) return 0; + if (len > CLI_FPUT_MAX) { + cliSerialPrint("%s: len > %d", argv[0], CLI_FPUT_MAX); + return 0; + } + FIL file; + if (f_open(&file, cliArg(argv, 1), FA_OPEN_EXISTING | FA_READ) != FR_OK) { + cliSerialPrint("%s: File not found \"%s\"", argv[0], cliArg(argv, 1)); + return 0; + } + uint8_t buf[256]; + static const char hex[] = "0123456789abcdef"; + bool ok = f_lseek(&file, offset) == FR_OK; + cliTxWait(64); + cliSerialPrintf("OK "); + while (ok && len) { + UINT n = 0; + if (f_read(&file, buf, len < sizeof(buf) ? len : sizeof(buf), &n) != FR_OK || n == 0) break; + for (UINT i = 0; i < n; i++) { + if ((i & 63) == 0) cliTxWait(256); + cliSerialPutc(hex[buf[i] >> 4]); + cliSerialPutc(hex[buf[i] & 15]); + } + len -= n; + } + f_close(&file); + cliSerialCrlf(); + return 0; +} + +// fwrite []: offset 0 truncates the file first +int cliFWrite(const char ** argv) +{ + uint32_t offset; + if (!cliUInt(argv, 2, &offset)) return 0; + const char * h = cliArg(argv, 3); + size_t hl = strlen(h); + if (hl & 1) { + cliSerialPrint("%s: odd hex length", argv[0]); + return 0; + } + uint8_t buf[CLI_COMMAND_MAX_LEN / 2]; + size_t n = hl / 2; + if (n > sizeof(buf)) n = sizeof(buf); + for (size_t i = 0; i < n; i++) { + unsigned v; + char pair[3] = { h[2 * i], h[2 * i + 1], 0 }; + char * end = nullptr; + v = strtoul(pair, &end, 16); + if (*end != '\0') { + cliSerialPrint("%s: bad hex", argv[0]); + return 0; + } + buf[i] = v; + } + FIL file; + BYTE mode = FA_WRITE | (offset == 0 ? FA_CREATE_ALWAYS : FA_OPEN_EXISTING); + FRESULT res = f_open(&file, cliArg(argv, 1), mode); + if (res == FR_OK) res = f_lseek(&file, offset); + UINT written = 0; + if (res == FR_OK && n) res = f_write(&file, buf, n, &written); + if (res == FR_OK) res = f_close(&file); else f_close(&file); + if (res != FR_OK || written != n) + cliSerialPrint("ERR %d", (int)res); + else + cliSerialPrint("OK %u", (unsigned)written); + return 0; +} + +// fput : raw bytes follow the command line; offset 0 truncates +int cliFPut(const char ** argv) +{ + uint32_t offset, len; + if (!cliUInt(argv, 2, &offset) || !cliUInt(argv, 3, &len)) return 0; + if (len > CLI_FPUT_MAX) { + cliSerialPrint("ERR len > %d", CLI_FPUT_MAX); + return 0; + } + FIL file; + BYTE mode = FA_WRITE | (offset == 0 ? FA_CREATE_ALWAYS : FA_OPEN_EXISTING); + FRESULT res = f_open(&file, cliArg(argv, 1), mode); + bool open = res == FR_OK; + if (open) res = f_lseek(&file, offset); + uint32_t got = 0; + uint8_t buf[256]; + // Always consume the whole block so a failed write does not leave data in the line parser + while (got < len) { + size_t n = xStreamBufferReceive(cliRxBuffer, buf, len - got < sizeof(buf) ? len - got : sizeof(buf), + 1000 / portTICK_PERIOD_MS); + if (!n) break; + if (res == FR_OK) { + UINT w = 0; + res = f_write(&file, buf, n, &w); + if (res == FR_OK && w != n) res = FR_DENIED; + } + got += n; + } + if (open) { + FRESULT cr = f_close(&file); + if (res == FR_OK) res = cr; + } + if (res != FR_OK || got != len) + cliSerialPrint("ERR %d got %u of %u", (int)res, (unsigned)got, (unsigned)len); + else + cliSerialPrint("OK %u", (unsigned)got); + return 0; +} + +int cliRm(const char ** argv) +{ + FRESULT res = f_unlink(cliArg(argv, 1)); + if (res == FR_OK) cliSerialPrint("OK"); else cliSerialPrint("ERR %d", (int)res); + return 0; +} + +int cliMkdir(const char ** argv) +{ + FRESULT res = f_mkdir(cliArg(argv, 1)); + if (res == FR_OK) cliSerialPrint("OK"); else cliSerialPrint("ERR %d", (int)res); + return 0; +} +#endif // CLI_FILE_ACCESS + int cliRead(const char ** argv) { FIL file; @@ -1524,6 +1710,110 @@ void printAudioVars() #include "disk_cache.h" #endif +#if defined(CLI_INPUT_INJECT) +static uint32_t injKeys, injKeysUntil; + +// toInt() for optional arguments, which are null when omitted +static int toIntOpt(const char ** argv, int index, int * val) +{ + if (!argv[index]) return 0; + return toInt(argv, index, val); +} + +uint32_t cliInjectedKeys() +{ + return (injKeys && (int32_t)(time_get_ms() - injKeysUntil) < 0) ? injKeys : 0; +} + +static int cliKey(const char ** argv) +{ + int ms = 100; + if (!argv[1] || toIntOpt(argv, 2, &ms) < 0) { + cliSerialPrint("%s: usage: key []", argv[0]); + return -1; + } + for (int i = 0; i <= MAX_KEYS; i++) { + if (keyIsSupported((EnumKeys)i) && !strcasecmp(keysGetLabel((EnumKeys)i), argv[1])) { + injKeysUntil = time_get_ms() + ms; + injKeys = 1u << i; + return 0; + } + } + cliSerialPrint("%s: unknown key \"%s\", available:", argv[0], argv[1]); + for (int i = 0; i <= MAX_KEYS; i++) { + if (keyIsSupported((EnumKeys)i)) cliSerialPrint(" %s", keysGetLabel((EnumKeys)i)); + } + return -1; +} + +static uint32_t injTrims, injTrimsUntil; + +uint32_t cliInjectedTrims() +{ + return (injTrims && (int32_t)(time_get_ms() - injTrimsUntil) < 0) ? injTrims : 0; +} + +// trim