From: joeycastillo Date: Sun, 22 Sep 2024 19:34:51 +0000 (-0400) Subject: bring in filesysten and shell, rename LFS globals for clarity X-Git-Url: https://git.earman.xyz/?a=commitdiff_plain;h=e29134bf9fc9f703ebe3d95c5f715a6026ec3021;p=sensor-watch.git bring in filesysten and shell, rename LFS globals for clarity --- diff --git a/Makefile b/Makefile index 0210e95..695e355 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,9 @@ endif INCLUDES += \ -I./ \ -I./tinyusb/src \ + -I./littlefs \ + -I./filesystem \ + -I./shell \ -I./watch-library/shared/watch \ -I./watch-library/hardware/watch \ -I./watch-faces/clock \ @@ -36,6 +39,11 @@ INCLUDES += \ # Add your source files here. SRCS += \ + ./littlefs/lfs.c \ + ./littlefs/lfs_util.c \ + ./filesystem/filesystem.c \ + ./shell/shell.c \ + ./shell/shell_cmd_list.c \ ./watch-library/shared/watch/watch_common_buzzer.c \ ./watch-library/shared/watch/watch_common_display.c \ ./watch-library/shared/watch/watch_utility.c \ diff --git a/filesystem/filesystem.c b/filesystem/filesystem.c new file mode 100644 index 0000000..e265a86 --- /dev/null +++ b/filesystem/filesystem.c @@ -0,0 +1,333 @@ +/* + * MIT License + * + * Copyright (c) 2022-2024 Joey Castillo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include "filesystem.h" +#include "watch.h" +#include "lfs.h" + +#ifndef min +#define min(x, y) ((x) > (y) ? (y) : (x)) +#endif + +int lfs_storage_read(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size); +int lfs_storage_prog(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size); +int lfs_storage_erase(const struct lfs_config *cfg, lfs_block_t block); +int lfs_storage_sync(const struct lfs_config *cfg); + +int lfs_storage_read(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { + (void) cfg; + return !watch_storage_read(block, off, (void *)buffer, size); +} + +int lfs_storage_prog(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { + (void) cfg; + return !watch_storage_write(block, off, (void *)buffer, size); +} + +int lfs_storage_erase(const struct lfs_config *cfg, lfs_block_t block) { + (void) cfg; + return !watch_storage_erase(block); +} + +int lfs_storage_sync(const struct lfs_config *cfg) { + (void) cfg; + return !watch_storage_sync(); +} + +const struct lfs_config watch_lfs_cfg = { + // block device operations + .read = lfs_storage_read, + .prog = lfs_storage_prog, + .erase = lfs_storage_erase, + .sync = lfs_storage_sync, + + // block device configuration + .read_size = 16, + .prog_size = NVMCTRL_PAGE_SIZE, + .block_size = NVMCTRL_ROW_SIZE, + .block_count = NVMCTRL_RWWEE_PAGES / 4, + .cache_size = NVMCTRL_PAGE_SIZE, + .lookahead_size = 16, + .block_cycles = 100, +}; + +lfs_t eeprom_filesystem; +static lfs_file_t file; +static struct lfs_info info; + +static int _traverse_df_cb(void *p, lfs_block_t block) { + (void) block; + uint32_t *nb = p; + *nb += 1; + return 0; +} + +int32_t filesystem_get_free_space(void) { + int err; + + uint32_t free_blocks = 0; + err = lfs_fs_traverse(&eeprom_filesystem, _traverse_df_cb, &free_blocks); + if(err < 0){ + return err; + } + + uint32_t available = watch_lfs_cfg.block_count * watch_lfs_cfg.block_size - free_blocks * watch_lfs_cfg.block_size; + + return (int32_t)available; +} + +static int filesystem_ls(lfs_t *lfs, const char *path) { + lfs_dir_t dir; + int err = lfs_dir_open(lfs, &dir, path); + if (err < 0) { + return err; + } + + struct lfs_info info; + while (true) { + int res = lfs_dir_read(lfs, &dir, &info); + if (res < 0) { + return res; + } + + if (res == 0) { + break; + } + + switch (info.type) { + case LFS_TYPE_REG: printf("file "); break; + case LFS_TYPE_DIR: printf("dir "); break; + default: printf("? "); break; + } + + printf("%4ld bytes ", info.size); + + printf("%s\r\n", info.name); + } + + err = lfs_dir_close(lfs, &dir); + if (err < 0) { + return err; + } + + return 0; +} + +bool filesystem_init(void) { + int err = lfs_mount(&eeprom_filesystem, &watch_lfs_cfg); + + // reformat if we can't mount the filesystem + // this should only happen on the first boot + if (1) { + printf("Ignore that error! Formatting filesystem...\r\n"); + err = lfs_format(&eeprom_filesystem, &watch_lfs_cfg); + if (err < 0) return false; + err = lfs_mount(&eeprom_filesystem, &watch_lfs_cfg) == LFS_ERR_OK; + printf("Filesystem mounted with %ld bytes free.\r\n", filesystem_get_free_space()); + } + + return err == LFS_ERR_OK; +} + +int _filesystem_format(void); +int _filesystem_format(void) { + int err = lfs_unmount(&eeprom_filesystem); + if (err < 0) { + printf("Couldn't unmount - continuing to format, but you should reboot afterwards!\r\n"); + } + + err = lfs_format(&eeprom_filesystem, &watch_lfs_cfg); + if (err < 0) return err; + + err = lfs_mount(&eeprom_filesystem, &watch_lfs_cfg); + if (err < 0) return err; + printf("Filesystem re-mounted with %ld bytes free.\r\n", filesystem_get_free_space()); + return 0; +} + +bool filesystem_file_exists(char *filename) { + info.type = 0; + lfs_stat(&eeprom_filesystem, filename, &info); + return info.type == LFS_TYPE_REG; +} + +bool filesystem_rm(char *filename) { + info.type = 0; + lfs_stat(&eeprom_filesystem, filename, &info); + if (filesystem_file_exists(filename)) { + return lfs_remove(&eeprom_filesystem, filename) == LFS_ERR_OK; + } else { + printf("rm: %s: No such file\r\n", filename); + return false; + } +} + +int32_t filesystem_get_file_size(char *filename) { + if (filesystem_file_exists(filename)) { + return info.size; // info struct was just populated by filesystem_file_exists + } + + return -1; +} + +bool filesystem_read_file(char *filename, char *buf, int32_t length) { + memset(buf, 0, length); + int32_t file_size = filesystem_get_file_size(filename); + if (file_size > 0) { + int err = lfs_file_open(&eeprom_filesystem, &file, filename, LFS_O_RDONLY); + if (err < 0) return false; + err = lfs_file_read(&eeprom_filesystem, &file, buf, min(length, file_size)); + if (err < 0) return false; + return lfs_file_close(&eeprom_filesystem, &file) == LFS_ERR_OK; + } + + return false; +} + +bool filesystem_read_line(char *filename, char *buf, int32_t *offset, int32_t length) { + memset(buf, 0, length + 1); + int32_t file_size = filesystem_get_file_size(filename); + if (file_size > 0) { + int err = lfs_file_open(&eeprom_filesystem, &file, filename, LFS_O_RDONLY); + if (err < 0) return false; + err = lfs_file_seek(&eeprom_filesystem, &file, *offset, LFS_SEEK_SET); + if (err < 0) return false; + err = lfs_file_read(&eeprom_filesystem, &file, buf, min(length - 1, file_size - *offset)); + if (err < 0) return false; + for(int i = 0; i < length; i++) { + (*offset)++; + if (buf[i] == '\n') { + buf[i] = 0; + break; + } + } + return lfs_file_close(&eeprom_filesystem, &file) == LFS_ERR_OK; + } + + return false; +} + +static void filesystem_cat(char *filename) { + info.type = 0; + lfs_stat(&eeprom_filesystem, filename, &info); + if (filesystem_file_exists(filename)) { + if (info.size > 0) { + char *buf = malloc(info.size + 1); + filesystem_read_file(filename, buf, info.size); + buf[info.size] = '\0'; + printf("%s\r\n", buf); + free(buf); + } else { + printf("\r\n"); + } + } else { + printf("cat: %s: No such file\r\n", filename); + } +} + +bool filesystem_write_file(char *filename, char *text, int32_t length) { + int err = lfs_file_open(&eeprom_filesystem, &file, filename, LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC); + if (err < 0) return false; + err = lfs_file_write(&eeprom_filesystem, &file, text, length); + if (err < 0) return false; + return lfs_file_close(&eeprom_filesystem, &file) == LFS_ERR_OK; +} + +bool filesystem_append_file(char *filename, char *text, int32_t length) { + int err = lfs_file_open(&eeprom_filesystem, &file, filename, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND); + if (err < 0) return false; + err = lfs_file_write(&eeprom_filesystem, &file, text, length); + if (err < 0) return false; + return lfs_file_close(&eeprom_filesystem, &file) == LFS_ERR_OK; +} + +int filesystem_cmd_ls(int argc, char *argv[]) { + if (argc >= 2) { + filesystem_ls(&eeprom_filesystem, argv[1]); + } else { + filesystem_ls(&eeprom_filesystem, "/"); + } + return 0; +} + +int filesystem_cmd_cat(int argc, char *argv[]) { + (void) argc; + filesystem_cat(argv[1]); + return 0; +} + +int filesystem_cmd_df(int argc, char *argv[]) { + (void) argc; + (void) argv; + printf("free space: %ld bytes\r\n", filesystem_get_free_space()); + return 0; +} + +int filesystem_cmd_rm(int argc, char *argv[]) { + (void) argc; + filesystem_rm(argv[1]); + return 0; +} + +int filesystem_cmd_format(int argc, char *argv[]) { + (void) argc; + if(strcmp(argv[1], "YES") == 0) { + return _filesystem_format(); + } + printf("usage: format YES\r\n"); + return 1; +} + + +int filesystem_cmd_echo(int argc, char *argv[]) { + (void) argc; + + char *line = argv[1]; + size_t line_len = strlen(line); + if (line[0] == '"' || line[0] == '\'') { + line++; + line_len -= 2; + line[line_len] = '\0'; + } + + if (strchr(argv[3], '/')) { + printf("subdirectories are not supported\r\n"); + return -2; + } + + if (!strcmp(argv[2], ">")) { + filesystem_write_file(argv[3], line, line_len); + filesystem_append_file(argv[3], "\n", 1); + } else if (!strcmp(argv[2], ">>")) { + filesystem_append_file(argv[3], line, line_len); + filesystem_append_file(argv[3], "\n", 1); + } else { + return -2; + } + + return 0; +} diff --git a/filesystem/filesystem.h b/filesystem/filesystem.h new file mode 100644 index 0000000..a54650a --- /dev/null +++ b/filesystem/filesystem.h @@ -0,0 +1,104 @@ +/* + * MIT License + * + * Copyright (c) 2022-2024 Joey Castillo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +#include +#include +#include "watch.h" + +/** @brief Initializes and mounts the tiny 8kb filesystem, formatting it if need be. + * @return true if the filesystem was mounted successfully. + */ +bool filesystem_init(void); + +/** @brief Gets the space available on the filesystem. + * @return the free space in bytes + */ +int32_t filesystem_get_free_space(void); + +/** @brief Checks for the existence of a file on the filesystem. + * @param filename the file you wish to check + * @return true if the file exists; false otherwise + */ +bool filesystem_file_exists(char *filename); + +/** @brief Removes a file on the filesystem. + * @param filename the file you wish to remove + * @return true if the file was deleted successfully; false otherwise + */ +bool filesystem_rm(char *filename); + +/** @brief Gets the size of a file on the filesystem. + * @param filename the file whose size you wish to determine + * @return the file's size in bytes, or -1 if the file does not exist. + */ +int32_t filesystem_get_file_size(char *filename); + +/** @brief Reads a file from the filesystem into a buffer + * @param filename the file you wish to read + * @param buf A buffer of at least length bytes; the file will be read into this buffer + * @param length The number of bytes to read + * @return true if the read was successful; false otherwise + * @note This function will set buf to zero and read all bytes of the file into the buffer. + * If you are reading a raw value (say you wrote a uint32 to a file), you can read back + * the value by passing in the file's length for length. If you wish to treat the buffer + * as a null-terminated string, allocate a buffer one byte longer than the file's length, + * and the last byte will be guaranteed to be 0. + */ +bool filesystem_read_file(char *filename, char *buf, int32_t length); + +/** @brief Reads a line from a file into a buffer + * @param filename the file you wish to read + * @param buf A buffer of at least length + 1 bytes; the file will be read into this buffer, + * and the last byte (buf[length]) will be set to 0 as a null terminator. + * @param offset Pointer to an int representing the offset into the file. This will be updated + * to reflect the offset of the next line. + * @param length The maximum number of bytes to read + * @return true if the read was successful; false otherwise + */ +bool filesystem_read_line(char *filename, char *buf, int32_t *offset, int32_t length); + +/** @brief Writes file to the filesystem + * @param filename the file you wish to write + * @param text The contents of the file + * @param length The number of bytes to write + * @return true if the write was successful; false otherwise + */ +bool filesystem_write_file(char *filename, char *text, int32_t length); + +/** @brief Appends text to file on the filesystem + * @param filename the file you wish to write + * @param text The contents to write + * @param length The number of bytes to write + * @return true if the write was successful; false otherwise + */ +bool filesystem_append_file(char *filename, char *text, int32_t length); + +int filesystem_cmd_ls(int argc, char *argv[]); +int filesystem_cmd_cat(int argc, char *argv[]); +int filesystem_cmd_df(int argc, char *argv[]); +int filesystem_cmd_rm(int argc, char *argv[]); +int filesystem_cmd_format(int argc, char *argv[]); +int filesystem_cmd_echo(int argc, char *argv[]); diff --git a/movement.c b/movement.c index 96e30c5..58af793 100644 --- a/movement.c +++ b/movement.c @@ -36,10 +36,10 @@ #include "watch_usb_cdc.h" #include "watch_private.h" #include "movement.h" +#include "filesystem.h" +#include "shell.h" -/// FIXMME: #SecondMovement needs to bring back the following includes (and remove the default signal_tune) -// #include "filesystem.h" -// #include "shell.h" +/// FIXME: #SecondMovement needs to bring back the following include (and remove the default signal_tune) // #include "movement_custom_signal_tunes.h" int8_t signal_tune[] = { BUZZER_NOTE_C8, 5, @@ -429,8 +429,7 @@ void app_init(void) { movement_state.next_available_backup_register = 4; _movement_reset_inactivity_countdown(); -/// FIXME: #SecondMovement needs filesystem support - // filesystem_init(); + filesystem_init(); #if __EMSCRIPTEN__ int32_t time_zone_offset = EM_ASM_INT({ @@ -636,8 +635,7 @@ bool app_loop(void) { // if we are plugged into USB, handle the serial shell if (usb_is_enabled()) { - /// FIXME: #SecondMovement needs to bring back the shell - // shell_task(); + shell_task(); } event.subsecond = 0; diff --git a/movement/filesystem.c b/movement/filesystem.c deleted file mode 100644 index 326db74..0000000 --- a/movement/filesystem.c +++ /dev/null @@ -1,307 +0,0 @@ -#include -#include -#include -#include -#include "filesystem.h" -#include "watch.h" -#include "lfs.h" -#include "hpl_flash.h" - -int lfs_storage_read(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size); -int lfs_storage_prog(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size); -int lfs_storage_erase(const struct lfs_config *cfg, lfs_block_t block); -int lfs_storage_sync(const struct lfs_config *cfg); - -int lfs_storage_read(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { - (void) cfg; - return !watch_storage_read(block, off, (void *)buffer, size); -} - -int lfs_storage_prog(const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { - (void) cfg; - return !watch_storage_write(block, off, (void *)buffer, size); -} - -int lfs_storage_erase(const struct lfs_config *cfg, lfs_block_t block) { - (void) cfg; - return !watch_storage_erase(block); -} - -int lfs_storage_sync(const struct lfs_config *cfg) { - (void) cfg; - return !watch_storage_sync(); -} - -const struct lfs_config cfg = { - // block device operations - .read = lfs_storage_read, - .prog = lfs_storage_prog, - .erase = lfs_storage_erase, - .sync = lfs_storage_sync, - - // block device configuration - .read_size = 16, - .prog_size = NVMCTRL_PAGE_SIZE, - .block_size = NVMCTRL_ROW_SIZE, - .block_count = NVMCTRL_RWWEE_PAGES / 4, - .cache_size = NVMCTRL_PAGE_SIZE, - .lookahead_size = 16, - .block_cycles = 100, -}; - -static lfs_t lfs; -static lfs_file_t file; -static struct lfs_info info; - -static int _traverse_df_cb(void *p, lfs_block_t block) { - (void) block; - uint32_t *nb = p; - *nb += 1; - return 0; -} - -int32_t filesystem_get_free_space(void) { - int err; - - uint32_t free_blocks = 0; - err = lfs_fs_traverse(&lfs, _traverse_df_cb, &free_blocks); - if(err < 0){ - return err; - } - - uint32_t available = cfg.block_count * cfg.block_size - free_blocks * cfg.block_size; - - return (int32_t)available; -} - -static int filesystem_ls(lfs_t *lfs, const char *path) { - lfs_dir_t dir; - int err = lfs_dir_open(lfs, &dir, path); - if (err < 0) { - return err; - } - - struct lfs_info info; - while (true) { - int res = lfs_dir_read(lfs, &dir, &info); - if (res < 0) { - return res; - } - - if (res == 0) { - break; - } - - switch (info.type) { - case LFS_TYPE_REG: printf("file "); break; - case LFS_TYPE_DIR: printf("dir "); break; - default: printf("? "); break; - } - - printf("%4ld bytes ", info.size); - - printf("%s\r\n", info.name); - } - - err = lfs_dir_close(lfs, &dir); - if (err < 0) { - return err; - } - - return 0; -} - -bool filesystem_init(void) { - int err = lfs_mount(&lfs, &cfg); - - // reformat if we can't mount the filesystem - // this should only happen on the first boot - if (err < 0) { - printf("Ignore that error! Formatting filesystem...\r\n"); - err = lfs_format(&lfs, &cfg); - if (err < 0) return false; - err = lfs_mount(&lfs, &cfg); - printf("Filesystem mounted with %ld bytes free.\r\n", filesystem_get_free_space()); - } - - return err == LFS_ERR_OK; -} - -int _filesystem_format(void); -int _filesystem_format(void) { - int err = lfs_unmount(&lfs); - if (err < 0) { - printf("Couldn't unmount - continuing to format, but you should reboot afterwards!\r\n"); - } - - err = lfs_format(&lfs, &cfg); - if (err < 0) return err; - - err = lfs_mount(&lfs, &cfg); - if (err < 0) return err; - printf("Filesystem re-mounted with %ld bytes free.\r\n", filesystem_get_free_space()); - return 0; -} - -bool filesystem_file_exists(char *filename) { - info.type = 0; - lfs_stat(&lfs, filename, &info); - return info.type == LFS_TYPE_REG; -} - -bool filesystem_rm(char *filename) { - info.type = 0; - lfs_stat(&lfs, filename, &info); - if (filesystem_file_exists(filename)) { - return lfs_remove(&lfs, filename) == LFS_ERR_OK; - } else { - printf("rm: %s: No such file\r\n", filename); - return false; - } -} - -int32_t filesystem_get_file_size(char *filename) { - if (filesystem_file_exists(filename)) { - return info.size; // info struct was just populated by filesystem_file_exists - } - - return -1; -} - -bool filesystem_read_file(char *filename, char *buf, int32_t length) { - memset(buf, 0, length); - int32_t file_size = filesystem_get_file_size(filename); - if (file_size > 0) { - int err = lfs_file_open(&lfs, &file, filename, LFS_O_RDONLY); - if (err < 0) return false; - err = lfs_file_read(&lfs, &file, buf, min(length, file_size)); - if (err < 0) return false; - return lfs_file_close(&lfs, &file) == LFS_ERR_OK; - } - - return false; -} - -bool filesystem_read_line(char *filename, char *buf, int32_t *offset, int32_t length) { - memset(buf, 0, length + 1); - int32_t file_size = filesystem_get_file_size(filename); - if (file_size > 0) { - int err = lfs_file_open(&lfs, &file, filename, LFS_O_RDONLY); - if (err < 0) return false; - err = lfs_file_seek(&lfs, &file, *offset, LFS_SEEK_SET); - if (err < 0) return false; - err = lfs_file_read(&lfs, &file, buf, min(length - 1, file_size - *offset)); - if (err < 0) return false; - for(int i = 0; i < length; i++) { - (*offset)++; - if (buf[i] == '\n') { - buf[i] = 0; - break; - } - } - return lfs_file_close(&lfs, &file) == LFS_ERR_OK; - } - - return false; -} - -static void filesystem_cat(char *filename) { - info.type = 0; - lfs_stat(&lfs, filename, &info); - if (filesystem_file_exists(filename)) { - if (info.size > 0) { - char *buf = malloc(info.size + 1); - filesystem_read_file(filename, buf, info.size); - buf[info.size] = '\0'; - printf("%s\r\n", buf); - free(buf); - } else { - printf("\r\n"); - } - } else { - printf("cat: %s: No such file\r\n", filename); - } -} - -bool filesystem_write_file(char *filename, char *text, int32_t length) { - int err = lfs_file_open(&lfs, &file, filename, LFS_O_RDWR | LFS_O_CREAT | LFS_O_TRUNC); - if (err < 0) return false; - err = lfs_file_write(&lfs, &file, text, length); - if (err < 0) return false; - return lfs_file_close(&lfs, &file) == LFS_ERR_OK; -} - -bool filesystem_append_file(char *filename, char *text, int32_t length) { - int err = lfs_file_open(&lfs, &file, filename, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_APPEND); - if (err < 0) return false; - err = lfs_file_write(&lfs, &file, text, length); - if (err < 0) return false; - return lfs_file_close(&lfs, &file) == LFS_ERR_OK; -} - -int filesystem_cmd_ls(int argc, char *argv[]) { - if (argc >= 2) { - filesystem_ls(&lfs, argv[1]); - } else { - filesystem_ls(&lfs, "/"); - } - return 0; -} - -int filesystem_cmd_cat(int argc, char *argv[]) { - (void) argc; - filesystem_cat(argv[1]); - return 0; -} - -int filesystem_cmd_df(int argc, char *argv[]) { - (void) argc; - (void) argv; - printf("free space: %ld bytes\r\n", filesystem_get_free_space()); - return 0; -} - -int filesystem_cmd_rm(int argc, char *argv[]) { - (void) argc; - filesystem_rm(argv[1]); - return 0; -} - -int filesystem_cmd_format(int argc, char *argv[]) { - (void) argc; - if(strcmp(argv[1], "YES") == 0) { - return _filesystem_format(); - } - printf("usage: format YES\r\n"); - return 1; -} - - -int filesystem_cmd_echo(int argc, char *argv[]) { - (void) argc; - - char *line = argv[1]; - size_t line_len = strlen(line); - if (line[0] == '"' || line[0] == '\'') { - line++; - line_len -= 2; - line[line_len] = '\0'; - } - - if (strchr(argv[3], '/')) { - printf("subdirectories are not supported\r\n"); - return -2; - } - - if (!strcmp(argv[2], ">")) { - filesystem_write_file(argv[3], line, line_len); - filesystem_append_file(argv[3], "\n", 1); - } else if (!strcmp(argv[2], ">>")) { - filesystem_append_file(argv[3], line, line_len); - filesystem_append_file(argv[3], "\n", 1); - } else { - return -2; - } - - return 0; -} diff --git a/movement/filesystem.h b/movement/filesystem.h deleted file mode 100644 index 472f281..0000000 --- a/movement/filesystem.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2022 Joey Castillo - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef FILESYSTEM_H_ -#define FILESYSTEM_H_ -#include -#include -#include "watch.h" - -/** @brief Initializes and mounts the tiny 8kb filesystem, formatting it if need be. - * @return true if the filesystem was mounted successfully. - */ -bool filesystem_init(void); - -/** @brief Gets the space available on the filesystem. - * @return the free space in bytes - */ -int32_t filesystem_get_free_space(void); - -/** @brief Checks for the existence of a file on the filesystem. - * @param filename the file you wish to check - * @return true if the file exists; false otherwise - */ -bool filesystem_file_exists(char *filename); - -/** @brief Removes a file on the filesystem. - * @param filename the file you wish to remove - * @return true if the file was deleted successfully; false otherwise - */ -bool filesystem_rm(char *filename); - -/** @brief Gets the size of a file on the filesystem. - * @param filename the file whose size you wish to determine - * @return the file's size in bytes, or -1 if the file does not exist. - */ -int32_t filesystem_get_file_size(char *filename); - -/** @brief Reads a file from the filesystem into a buffer - * @param filename the file you wish to read - * @param buf A buffer of at least length bytes; the file will be read into this buffer - * @param length The number of bytes to read - * @return true if the read was successful; false otherwise - * @note This function will set buf to zero and read all bytes of the file into the buffer. - * If you are reading a raw value (say you wrote a uint32 to a file), you can read back - * the value by passing in the file's length for length. If you wish to treat the buffer - * as a null-terminated string, allocate a buffer one byte longer than the file's length, - * and the last byte will be guaranteed to be 0. - */ -bool filesystem_read_file(char *filename, char *buf, int32_t length); - -/** @brief Reads a line from a file into a buffer - * @param filename the file you wish to read - * @param buf A buffer of at least length + 1 bytes; the file will be read into this buffer, - * and the last byte (buf[length]) will be set to 0 as a null terminator. - * @param offset Pointer to an int representing the offset into the file. This will be updated - * to reflect the offset of the next line. - * @param length The maximum number of bytes to read - * @return true if the read was successful; false otherwise - */ -bool filesystem_read_line(char *filename, char *buf, int32_t *offset, int32_t length); - -/** @brief Writes file to the filesystem - * @param filename the file you wish to write - * @param text The contents of the file - * @param length The number of bytes to write - * @return true if the write was successful; false otherwise - */ -bool filesystem_write_file(char *filename, char *text, int32_t length); - -/** @brief Appends text to file on the filesystem - * @param filename the file you wish to write - * @param text The contents to write - * @param length The number of bytes to write - * @return true if the write was successful; false otherwise - */ -bool filesystem_append_file(char *filename, char *text, int32_t length); - -int filesystem_cmd_ls(int argc, char *argv[]); -int filesystem_cmd_cat(int argc, char *argv[]); -int filesystem_cmd_df(int argc, char *argv[]); -int filesystem_cmd_rm(int argc, char *argv[]); -int filesystem_cmd_format(int argc, char *argv[]); -int filesystem_cmd_echo(int argc, char *argv[]); - -#endif // FILESYSTEM_H_ diff --git a/movement/shell.c b/movement/shell.c deleted file mode 100644 index 3782068..0000000 --- a/movement/shell.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023 Edward Shin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "shell.h" - -#include -#include -#include -#include -#include -#include -#include - -#if __EMSCRIPTEN__ -#include -#endif - -#include "watch.h" -#include "shell_cmd_list.h" - -extern shell_command_t g_shell_commands[]; -extern const size_t g_num_shell_commands; - -#define NEWLINE "\r\n" - -#define SHELL_BUF_SZ (256) -#define SHELL_MAX_ARGS (16) -#define SHELL_PROMPT "swsh> " - -static char s_buf[SHELL_BUF_SZ] = {0}; -static size_t s_buf_len = 0; -// Pointer to the first invalid byte after the end of input. -static char *const s_buf_end = s_buf + SHELL_BUF_SZ; - -static char *prv_skip_whitespace(char *c) { - while (c >= s_buf && c < s_buf_end) { - if (*c == 0) { - return NULL; - } - if ((!isspace((int) *c)) != 0) { - return c; - } - c++; - } - return NULL; -} - -static char *prv_skip_non_whitespace(char *c) { - bool in_quote = false; - char quote_char; - while (c >= s_buf && c < s_buf_end) { - if (*c == 0) { - return NULL; - } - // Basic handling of quoted arguments. - // Can't handle recursive quotes. :( - if (in_quote || *c == '"' || *c == '\'') { - if (!in_quote) { - quote_char = *c; - in_quote = true; - } else if (*c == quote_char) { - in_quote = false; - } - } else { - if (isspace((int) *c) != 0) { - return c; - } - } - c++; - } - return NULL; -} - -static int prv_handle_command() { - char *argv[SHELL_MAX_ARGS] = {0}; - int argc = 0; - - char *c = &s_buf[0]; - s_buf[SHELL_BUF_SZ - 1] = '\0'; - - while (argc < SHELL_MAX_ARGS) { - // Skip contiguous whitespace - c = prv_skip_whitespace(c); - if (c == NULL) { - // Reached end of buffer - break; - } - - // We hit non-whitespace, set argv and argc for this upcoming argument - argv[argc++] = c; - - // Skip contiguous non-whitespace - c = prv_skip_non_whitespace(c); - if (c == NULL) { - // Reached end of buffer - break; - } - - // NULL-terminate this arg string and then increment. - *(c++) = '\0'; - } - - if (argc == 0) { - return -1; - } - - // Match against the command list - for (size_t i = 0; i < g_num_shell_commands; i++) { - if (!strcasecmp(g_shell_commands[i].name, argv[0])) { - // If argc isn't valid for this command, display its help instead. - if (((argc - 1) < g_shell_commands[i].min_args) || - ((argc - 1) > g_shell_commands[i].max_args)) { - if (g_shell_commands[i].help != NULL) { - printf(NEWLINE "%s" NEWLINE, g_shell_commands[i].help); - } - return -2; - } - // Call the command's callback - if (g_shell_commands[i].cb != NULL) { - printf(NEWLINE); - int ret = g_shell_commands[i].cb(argc, argv); - if (ret == -2) { - printf(NEWLINE "%s" NEWLINE, g_shell_commands[i].help); - } - return ret; - } - } - } - - return -1; -} - -void shell_task(void) { -#if __EMSCRIPTEN__ - // This is a terrible hack; ideally this should be handled deeper in the watch library. - // Alas, emscripten treats read() as something that should pop up an input box, so I - // wasn't able to implement this over there. I sense that this relates to read() being - // the wrong way to read data from USB (like we should be using fgets or something), but - // until I untangle that, this will have to do. - char *received_data = (char*)EM_ASM_INT({ - var len = lengthBytesUTF8(tx) + 1; - var s = _malloc(len); - stringToUTF8(tx, s, len); - return s; - }); - s_buf_len = min((SHELL_BUF_SZ - 2), strlen(received_data)); - memcpy(s_buf, received_data, s_buf_len); - free(received_data); - s_buf[s_buf_len++] = '\n'; - s_buf[s_buf_len++] = '\0'; - prv_handle_command(); - EM_ASM({ - tx = ""; - }); -#else - // Read one character at a time until we run out. - while (true) { - if (s_buf_len >= (SHELL_BUF_SZ - 1)) { - printf(NEWLINE "Command too long, clearing."); - printf(NEWLINE SHELL_PROMPT); - s_buf_len = 0; - break; - } - - int c = getchar(); - - if (c < 0) { - // Nothing left to read, we're done. - break; - } - - if (c == '\b') { - // Handle backspace character. - // We need to emit a backspace, overwrite the character on the - // screen with a space, and then backspace again to move the cursor. - if (s_buf_len > 0) { - printf("\b \b"); - s_buf_len--; - } - continue; - } else if (c != '\n' && c != '\r') { - // Print regular characters to the screen. - putchar(c); - } - - s_buf[s_buf_len] = c; - - if (c == '\n' || c == '\r') { - // Newline! Handle the command. - s_buf[s_buf_len+1] = '\0'; - (void) prv_handle_command(); - s_buf_len = 0; - printf(NEWLINE SHELL_PROMPT); - break; - } else { - s_buf_len++; - } - } -#endif -} diff --git a/movement/shell.h b/movement/shell.h deleted file mode 100644 index 27dbf67..0000000 --- a/movement/shell.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023 Edward Shin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef SHELL_H_ -#define SHELL_H_ - -/** @brief Called periodically from the app loop to handle shell commands. - * When a full command is complete, parses and executes its matching - * callback. - */ -void shell_task(void); - -#endif diff --git a/movement/shell_cmd_list.c b/movement/shell_cmd_list.c deleted file mode 100644 index e2d700b..0000000 --- a/movement/shell_cmd_list.c +++ /dev/null @@ -1,165 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023 Edward Shin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "shell_cmd_list.h" - -#include -#include -#include - -#include "filesystem.h" -#include "watch.h" - -static int help_cmd(int argc, char *argv[]); -static int flash_cmd(int argc, char *argv[]); -static int stress_cmd(int argc, char *argv[]); - -shell_command_t g_shell_commands[] = { - { - .name = "?", - .help = "print command list", - .min_args = 0, - .max_args = 0, - .cb = help_cmd, - }, - { - .name = "help", - .help = "print command list", - .min_args = 0, - .max_args = 0, - .cb = help_cmd, - }, - { - .name = "flash", - .help = "reboot to UF2 bootloader", - .min_args = 0, - .max_args = 0, - .cb = flash_cmd, - }, - { - .name = "ls", - .help = "usage: ls [PATH]", - .min_args = 0, - .max_args = 1, - .cb = filesystem_cmd_ls, - }, - { - .name = "cat", - .help = "usage: cat ", - .min_args = 1, - .max_args = 1, - .cb = filesystem_cmd_cat, - }, - { - .name = "df", - .help = "print filesystem free space", - .min_args = 0, - .max_args = 0, - .cb = filesystem_cmd_df, - }, - { - .name = "rm", - .help = "usage: rm [PATH]", - .min_args = 1, - .max_args = 1, - .cb = filesystem_cmd_rm, - }, - { - .name = "format", - .help = "usage: format YES", - .min_args = 1, - .max_args = 1, - .cb = filesystem_cmd_format, - }, - { - .name = "echo", - .help = "usage: echo TEXT {>,>>} FILE", - .min_args = 3, - .max_args = 3, - .cb = filesystem_cmd_echo, - }, - { - .name = "stress", - .help = "test CDC write; usage: stress [LEN] [DELAY_MS]", - .min_args = 0, - .max_args = 2, - .cb = stress_cmd, - }, -}; - -const size_t g_num_shell_commands = sizeof(g_shell_commands) / sizeof(shell_command_t); - -static int help_cmd(int argc, char *argv[]) { - (void) argc; - (void) argv; - - printf("Command List:\r\n"); - for (size_t i = 0; i < g_num_shell_commands; i++) { - printf(" %s\t%s\r\n", - g_shell_commands[i].name, - (g_shell_commands[i].help) ? g_shell_commands[i].help : "" - ); - } - - return 0; -} - -static int flash_cmd(int argc, char *argv[]) { - (void) argc; - (void) argv; - - watch_reset_to_bootloader(); - return 0; -} - -#define STRESS_CMD_MAX_LEN (512) -static int stress_cmd(int argc, char *argv[]) { - char test_str[STRESS_CMD_MAX_LEN+1] = {0}; - - int max_len = 512; - int delay = 0; - - if (argc >= 2) { - if ((max_len = atoi(argv[1])) == 0) { - return -1; - } - if (max_len > 512) { - return -1; - } - } - - if (argc >= 3) { - delay = atoi(argv[2]); - } - - for (int i = 0; i < max_len; i++) { - snprintf(&test_str[i], 2, "%u", (i+1)%10); - printf("%u:\t%s\r\n", (i+1), test_str); - if (delay > 0) { - delay_ms(delay); - } - } - - return 0; -} diff --git a/movement/shell_cmd_list.h b/movement/shell_cmd_list.h deleted file mode 100644 index 89031a5..0000000 --- a/movement/shell_cmd_list.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023 Edward Shin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef SHELL_CMD_LIST_H_ -#define SHELL_CMD_LIST_H_ - -#include - -typedef struct { - const char *name; // Name used to invoke the command - const char *help; // Help string - int8_t min_args; // Minimum number of arguments (_excluding_ the command name) - int8_t max_args; // Maximum number of arguments (_excluding_ the command name) - int (*cb)(int argc, char *argv[]); // Callback for the command -} shell_command_t; - -#endif diff --git a/shell/shell.c b/shell/shell.c new file mode 100644 index 0000000..c028ed2 --- /dev/null +++ b/shell/shell.c @@ -0,0 +1,222 @@ +/* + * MIT License + * + * Copyright (c) 2023 Edward Shin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "shell.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#if __EMSCRIPTEN__ +#include +#endif + +#include "watch.h" +#include "shell_cmd_list.h" + +extern shell_command_t g_shell_commands[]; +extern const size_t g_num_shell_commands; + +#define NEWLINE "\r\n" + +#define SHELL_BUF_SZ (256) +#define SHELL_MAX_ARGS (16) +#define SHELL_PROMPT "swsh> " + +static char s_buf[SHELL_BUF_SZ] = {0}; +static size_t s_buf_len = 0; +// Pointer to the first invalid byte after the end of input. +static char *const s_buf_end = s_buf + SHELL_BUF_SZ; + +static char *prv_skip_whitespace(char *c) { + while (c >= s_buf && c < s_buf_end) { + if (*c == 0) { + return NULL; + } + if ((!isspace((int) *c)) != 0) { + return c; + } + c++; + } + return NULL; +} + +static char *prv_skip_non_whitespace(char *c) { + bool in_quote = false; + char quote_char; + while (c >= s_buf && c < s_buf_end) { + if (*c == 0) { + return NULL; + } + // Basic handling of quoted arguments. + // Can't handle recursive quotes. :( + if (in_quote || *c == '"' || *c == '\'') { + if (!in_quote) { + quote_char = *c; + in_quote = true; + } else if (*c == quote_char) { + in_quote = false; + } + } else { + if (isspace((int) *c) != 0) { + return c; + } + } + c++; + } + return NULL; +} + +static int prv_handle_command() { + char *argv[SHELL_MAX_ARGS] = {0}; + int argc = 0; + + char *c = &s_buf[0]; + s_buf[SHELL_BUF_SZ - 1] = '\0'; + + while (argc < SHELL_MAX_ARGS) { + // Skip contiguous whitespace + c = prv_skip_whitespace(c); + if (c == NULL) { + // Reached end of buffer + break; + } + + // We hit non-whitespace, set argv and argc for this upcoming argument + argv[argc++] = c; + + // Skip contiguous non-whitespace + c = prv_skip_non_whitespace(c); + if (c == NULL) { + // Reached end of buffer + break; + } + + // NULL-terminate this arg string and then increment. + *(c++) = '\0'; + } + + if (argc == 0) { + return -1; + } + + // Match against the command list + for (size_t i = 0; i < g_num_shell_commands; i++) { + if (!strcasecmp(g_shell_commands[i].name, argv[0])) { + // If argc isn't valid for this command, display its help instead. + if (((argc - 1) < g_shell_commands[i].min_args) || + ((argc - 1) > g_shell_commands[i].max_args)) { + if (g_shell_commands[i].help != NULL) { + printf(NEWLINE "%s" NEWLINE, g_shell_commands[i].help); + } + return -2; + } + // Call the command's callback + if (g_shell_commands[i].cb != NULL) { + printf(NEWLINE); + int ret = g_shell_commands[i].cb(argc, argv); + if (ret == -2) { + printf(NEWLINE "%s" NEWLINE, g_shell_commands[i].help); + } + return ret; + } + } + } + + return -1; +} + +void shell_task(void) { +#if __EMSCRIPTEN__ + // This is a terrible hack; ideally this should be handled deeper in the watch library. + // Alas, emscripten treats read() as something that should pop up an input box, so I + // wasn't able to implement this over there. I sense that this relates to read() being + // the wrong way to read data from USB (like we should be using fgets or something), but + // until I untangle that, this will have to do. + char *received_data = (char*)EM_ASM_INT({ + var len = lengthBytesUTF8(tx) + 1; + var s = _malloc(len); + stringToUTF8(tx, s, len); + return s; + }); + s_buf_len = min((SHELL_BUF_SZ - 2), strlen(received_data)); + memcpy(s_buf, received_data, s_buf_len); + free(received_data); + s_buf[s_buf_len++] = '\n'; + s_buf[s_buf_len++] = '\0'; + prv_handle_command(); + EM_ASM({ + tx = ""; + }); +#else + // Read one character at a time until we run out. + while (true) { + if (s_buf_len >= (SHELL_BUF_SZ - 1)) { + printf(NEWLINE "Command too long, clearing."); + printf(NEWLINE SHELL_PROMPT); + s_buf_len = 0; + break; + } + + int c = getchar(); + + if (c < 0) { + // Nothing left to read, we're done. + break; + } + + if (c == '\b') { + // Handle backspace character. + // We need to emit a backspace, overwrite the character on the + // screen with a space, and then backspace again to move the cursor. + if (s_buf_len > 0) { + printf("\b \b"); + s_buf_len--; + } + continue; + } else if (c != '\n' && c != '\r') { + // Print regular characters to the screen. + putchar(c); + } + + s_buf[s_buf_len] = c; + + if (c == '\n' || c == '\r') { + // Newline! Handle the command. + s_buf[s_buf_len+1] = '\0'; + (void) prv_handle_command(); + s_buf_len = 0; + printf(NEWLINE SHELL_PROMPT); + break; + } else { + s_buf_len++; + } + } +#endif +} diff --git a/shell/shell.h b/shell/shell.h new file mode 100644 index 0000000..27dbf67 --- /dev/null +++ b/shell/shell.h @@ -0,0 +1,34 @@ +/* + * MIT License + * + * Copyright (c) 2023 Edward Shin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SHELL_H_ +#define SHELL_H_ + +/** @brief Called periodically from the app loop to handle shell commands. + * When a full command is complete, parses and executes its matching + * callback. + */ +void shell_task(void); + +#endif diff --git a/shell/shell_cmd_list.c b/shell/shell_cmd_list.c new file mode 100644 index 0000000..1e49539 --- /dev/null +++ b/shell/shell_cmd_list.c @@ -0,0 +1,166 @@ +/* + * MIT License + * + * Copyright (c) 2023 Edward Shin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "shell_cmd_list.h" + +#include +#include +#include + +#include "filesystem.h" +#include "watch.h" +#include "delay.h" + +static int help_cmd(int argc, char *argv[]); +static int flash_cmd(int argc, char *argv[]); +static int stress_cmd(int argc, char *argv[]); + +shell_command_t g_shell_commands[] = { + { + .name = "?", + .help = "print command list", + .min_args = 0, + .max_args = 0, + .cb = help_cmd, + }, + { + .name = "help", + .help = "print command list", + .min_args = 0, + .max_args = 0, + .cb = help_cmd, + }, + { + .name = "flash", + .help = "reboot to UF2 bootloader", + .min_args = 0, + .max_args = 0, + .cb = flash_cmd, + }, + { + .name = "ls", + .help = "usage: ls [PATH]", + .min_args = 0, + .max_args = 1, + .cb = filesystem_cmd_ls, + }, + { + .name = "cat", + .help = "usage: cat ", + .min_args = 1, + .max_args = 1, + .cb = filesystem_cmd_cat, + }, + { + .name = "df", + .help = "print filesystem free space", + .min_args = 0, + .max_args = 0, + .cb = filesystem_cmd_df, + }, + { + .name = "rm", + .help = "usage: rm [PATH]", + .min_args = 1, + .max_args = 1, + .cb = filesystem_cmd_rm, + }, + { + .name = "format", + .help = "usage: format YES", + .min_args = 1, + .max_args = 1, + .cb = filesystem_cmd_format, + }, + { + .name = "echo", + .help = "usage: echo TEXT {>,>>} FILE", + .min_args = 3, + .max_args = 3, + .cb = filesystem_cmd_echo, + }, + { + .name = "stress", + .help = "test CDC write; usage: stress [LEN] [DELAY_MS]", + .min_args = 0, + .max_args = 2, + .cb = stress_cmd, + }, +}; + +const size_t g_num_shell_commands = sizeof(g_shell_commands) / sizeof(shell_command_t); + +static int help_cmd(int argc, char *argv[]) { + (void) argc; + (void) argv; + + printf("Command List:\r\n"); + for (size_t i = 0; i < g_num_shell_commands; i++) { + printf(" %s\t%s\r\n", + g_shell_commands[i].name, + (g_shell_commands[i].help) ? g_shell_commands[i].help : "" + ); + } + + return 0; +} + +static int flash_cmd(int argc, char *argv[]) { + (void) argc; + (void) argv; + + watch_reset_to_bootloader(); + return 0; +} + +#define STRESS_CMD_MAX_LEN (512) +static int stress_cmd(int argc, char *argv[]) { + char test_str[STRESS_CMD_MAX_LEN+1] = {0}; + + int max_len = 512; + int delay = 0; + + if (argc >= 2) { + if ((max_len = atoi(argv[1])) == 0) { + return -1; + } + if (max_len > 512) { + return -1; + } + } + + if (argc >= 3) { + delay = atoi(argv[2]); + } + + for (int i = 0; i < max_len; i++) { + snprintf(&test_str[i], 2, "%u", (i+1)%10); + printf("%u:\t%s\r\n", (i+1), test_str); + if (delay > 0) { + delay_ms(delay); + } + } + + return 0; +} diff --git a/shell/shell_cmd_list.h b/shell/shell_cmd_list.h new file mode 100644 index 0000000..89031a5 --- /dev/null +++ b/shell/shell_cmd_list.h @@ -0,0 +1,38 @@ +/* + * MIT License + * + * Copyright (c) 2023 Edward Shin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef SHELL_CMD_LIST_H_ +#define SHELL_CMD_LIST_H_ + +#include + +typedef struct { + const char *name; // Name used to invoke the command + const char *help; // Help string + int8_t min_args; // Minimum number of arguments (_excluding_ the command name) + int8_t max_args; // Maximum number of arguments (_excluding_ the command name) + int (*cb)(int argc, char *argv[]); // Callback for the command +} shell_command_t; + +#endif