]> git.earman.xyz Git - sensor-watch.git/commitdiff
bring in filesysten and shell, rename LFS globals for clarity
authorjoeycastillo <joeycastillo@utexas.edu>
Sun, 22 Sep 2024 19:34:51 +0000 (15:34 -0400)
committerjoeycastillo <joeycastillo@utexas.edu>
Sun, 22 Sep 2024 19:34:51 +0000 (15:34 -0400)
14 files changed:
Makefile
filesystem/filesystem.c [new file with mode: 0644]
filesystem/filesystem.h [new file with mode: 0644]
movement.c
movement/filesystem.c [deleted file]
movement/filesystem.h [deleted file]
movement/shell.c [deleted file]
movement/shell.h [deleted file]
movement/shell_cmd_list.c [deleted file]
movement/shell_cmd_list.h [deleted file]
shell/shell.c [new file with mode: 0644]
shell/shell.h [new file with mode: 0644]
shell/shell_cmd_list.c [new file with mode: 0644]
shell/shell_cmd_list.h [new file with mode: 0644]

index 0210e959e9b7d5a2d8f51d13b61f8f91d3d67920..695e35571e16dccd889491e9d2da313d9bd09e4f 100644 (file)
--- 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 (file)
index 0000000..e265a86
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#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 (file)
index 0000000..a54650a
--- /dev/null
@@ -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 <stdio.h>
+#include <stdbool.h>
+#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[]);
index 96e30c56d06d673f49a7da78b61170793401778f..58af7932192936b1cba85a75897a641f9cb591ab 100644 (file)
 #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 (file)
index 326db74..0000000
+++ /dev/null
@@ -1,307 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <peripheral_clk_config.h>
-#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 (file)
index 472f281..0000000
+++ /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 <stdio.h>
-#include <stdbool.h>
-#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 (file)
index 3782068..0000000
+++ /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 <ctype.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-
-#if __EMSCRIPTEN__
-#include <emscripten.h>
-#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 (file)
index 27dbf67..0000000
+++ /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 (file)
index e2d700b..0000000
+++ /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 <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#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 <PATH>",
-        .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 (file)
index 89031a5..0000000
+++ /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 <stdint.h>
-
-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 (file)
index 0000000..c028ed2
--- /dev/null
@@ -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 <ctype.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+
+#if __EMSCRIPTEN__
+#include <emscripten.h>
+#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 (file)
index 0000000..27dbf67
--- /dev/null
@@ -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 (file)
index 0000000..1e49539
--- /dev/null
@@ -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 <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 <PATH>",
+        .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 (file)
index 0000000..89031a5
--- /dev/null
@@ -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 <stdint.h>
+
+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