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 \
# 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 \
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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[]);
#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,
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({
// 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;
+++ /dev/null
-#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;
-}
+++ /dev/null
-/*
- * 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_
+++ /dev/null
-/*
- * 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
-}
+++ /dev/null
-/*
- * 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
+++ /dev/null
-/*
- * 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;
-}
+++ /dev/null
-/*
- * 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
--- /dev/null
+/*
+ * 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
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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