313 lines
10 KiB
C
313 lines
10 KiB
C
/*
|
|
* cmd_sdcard.c
|
|
*
|
|
* Copyright (c) 2025 Umber Networks & Robert McMahon
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <inttypes.h>
|
|
#include "esp_console.h"
|
|
#include "argtable3/argtable3.h"
|
|
#include "app_console.h"
|
|
#include "sd_card.h"
|
|
#include "sdcard_http.h"
|
|
|
|
#define SDCARD_READ_BUF_SIZE 4096
|
|
#define SDCARD_SEND_CHUNK 256
|
|
#define SDCARD_SEND_MAX (512 * 1024) /* 512 KB max over serial */
|
|
|
|
/* Format bytes as human-readable (e.g. 1.2K, 4.5M). Writes into buf, max len chars. */
|
|
static void fmt_size_human(size_t bytes, char *buf, size_t len) {
|
|
if (bytes < 1024) {
|
|
snprintf(buf, len, "%zu B", bytes);
|
|
} else if (bytes < 1024 * 1024) {
|
|
snprintf(buf, len, "%.1f K", bytes / 1024.0);
|
|
} else if (bytes < 1024ULL * 1024 * 1024) {
|
|
snprintf(buf, len, "%.1f M", bytes / (1024.0 * 1024.0));
|
|
} else {
|
|
snprintf(buf, len, "%.1f G", bytes / (1024.0 * 1024.0 * 1024.0));
|
|
}
|
|
}
|
|
|
|
static int do_sdcard_status(int argc, char **argv) {
|
|
(void)argc;
|
|
(void)argv;
|
|
|
|
printf("SD Card Status:\n");
|
|
printf(" CD (Card Detect): ");
|
|
if (sd_card_cd_available()) {
|
|
int level = sd_card_cd_get_level();
|
|
bool inserted = sd_card_cd_is_inserted();
|
|
printf("%s (GPIO=%s)\n", inserted ? "INSERTED" : "REMOVED",
|
|
level >= 0 ? (level ? "HIGH" : "LOW") : "?");
|
|
if (!inserted && sd_card_is_ready()) {
|
|
printf(" (Card works but CD says REMOVED: wire CD to J1 Pin 12, or menuconfig -> SD Card -> uncheck 'LOW = inserted')\n");
|
|
}
|
|
} else {
|
|
printf("N/A (pin not configured)\n");
|
|
}
|
|
printf(" Mounted: %s\n", sd_card_is_ready() ? "yes" : "no");
|
|
|
|
if (sd_card_is_ready()) {
|
|
uint64_t total = 0, free_bytes = 0;
|
|
if (sd_card_get_info(&total, &free_bytes) == 0) {
|
|
printf(" Total: %.2f MB\n", total / (1024.0 * 1024.0));
|
|
printf(" Free: %.2f MB\n", free_bytes / (1024.0 * 1024.0));
|
|
}
|
|
if (sd_card_file_exists("fiwi-telemetry")) {
|
|
size_t sz = 0;
|
|
if (sd_card_get_file_size("fiwi-telemetry", &sz) == 0) {
|
|
char hr[16];
|
|
fmt_size_human(sz, hr, sizeof(hr));
|
|
printf(" fiwi-telemetry: yes, %s (%zu bytes)\n", hr, sz);
|
|
} else {
|
|
printf(" fiwi-telemetry: yes, ?\n");
|
|
}
|
|
} else {
|
|
printf(" fiwi-telemetry: none\n");
|
|
}
|
|
uint32_t attempts = 0, downloads = 0;
|
|
sdcard_http_get_telemetry_stats(&attempts, &downloads);
|
|
printf(" telemetry HTTP: %" PRIu32 " attempts, %" PRIu32 " downloads\n", attempts, downloads);
|
|
printf(" telemetry-status: %s (timestamps + bytes per download)\n",
|
|
sd_card_file_exists("telemetry-status") ? "yes" : "none");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int do_sdcard_write(int argc, char **argv) {
|
|
if (argc < 3) {
|
|
printf("Usage: sdcard write <file> <text...>\n");
|
|
return 1;
|
|
}
|
|
const char *filename = argv[1];
|
|
|
|
if (!sd_card_is_ready()) {
|
|
printf("Error: SD card not mounted\n");
|
|
return 1;
|
|
}
|
|
|
|
/* Join argv[2]..argv[argc-1] with spaces for multi-word text */
|
|
static char text_buf[512];
|
|
size_t off = 0;
|
|
for (int i = 2; i < argc && off < sizeof(text_buf) - 2; i++) {
|
|
if (i > 2) {
|
|
text_buf[off++] = ' ';
|
|
}
|
|
size_t len = strlen(argv[i]);
|
|
if (off + len >= sizeof(text_buf)) {
|
|
len = sizeof(text_buf) - off - 1;
|
|
}
|
|
memcpy(text_buf + off, argv[i], len);
|
|
off += len;
|
|
}
|
|
text_buf[off] = '\0';
|
|
|
|
esp_err_t ret = sd_card_write_file(filename, text_buf, off, false);
|
|
if (ret != 0) {
|
|
printf("Write failed: %s\n", esp_err_to_name(ret));
|
|
return 1;
|
|
}
|
|
printf("Wrote %zu bytes to %s\n", off, filename);
|
|
return 0;
|
|
}
|
|
|
|
static int do_sdcard_read(int argc, char **argv) {
|
|
if (argc < 2) {
|
|
printf("Usage: sdcard read <file>\n");
|
|
return 1;
|
|
}
|
|
const char *filename = argv[1];
|
|
|
|
if (!sd_card_is_ready()) {
|
|
printf("Error: SD card not mounted\n");
|
|
return 1;
|
|
}
|
|
|
|
if (!sd_card_file_exists(filename)) {
|
|
printf("Error: File not found: %s\n", filename);
|
|
return 1;
|
|
}
|
|
|
|
static uint8_t buf[SDCARD_READ_BUF_SIZE];
|
|
size_t bytes_read = 0;
|
|
esp_err_t ret = sd_card_read_file(filename, buf, sizeof(buf) - 1, &bytes_read);
|
|
if (ret != 0) {
|
|
printf("Read failed: %s\n", esp_err_to_name(ret));
|
|
return 1;
|
|
}
|
|
buf[bytes_read] = '\0';
|
|
printf("Read %zu bytes from %s:\n", bytes_read, filename);
|
|
printf("---\n%s\n---\n", (char *)buf);
|
|
return 0;
|
|
}
|
|
|
|
/* Serial file transfer: output hex-encoded file for host script (e.g. sdcard_recv.py) */
|
|
static int do_sdcard_send(int argc, char **argv) {
|
|
if (argc < 2) {
|
|
printf("Usage: sdcard send <file>\n");
|
|
printf(" Streams file over serial (hex). Use tools/sdcard_recv.py on host to receive.\n");
|
|
return 1;
|
|
}
|
|
const char *filename = argv[1];
|
|
|
|
if (!sd_card_is_ready()) {
|
|
printf("Error: SD card not mounted\n");
|
|
return 1;
|
|
}
|
|
if (!sd_card_file_exists(filename)) {
|
|
printf("Error: File not found: %s\n", filename);
|
|
return 1;
|
|
}
|
|
|
|
size_t file_size = 0;
|
|
if (sd_card_get_file_size(filename, &file_size) != 0) {
|
|
printf("Error: Could not get file size\n");
|
|
return 1;
|
|
}
|
|
if (file_size > SDCARD_SEND_MAX) {
|
|
printf("Error: File too large for serial transfer (max %u KB)\n", (unsigned)(SDCARD_SEND_MAX / 1024));
|
|
return 1;
|
|
}
|
|
|
|
/* Protocol: ---SDFILE--- \n filename \n SIZE: N \n ---HEX--- \n <hex lines> ---END SDFILE--- */
|
|
printf("---SDFILE---\n%s\nSIZE:%zu\n---HEX---\n", filename, file_size);
|
|
fflush(stdout);
|
|
|
|
static uint8_t chunk[SDCARD_SEND_CHUNK];
|
|
size_t offset = 0;
|
|
while (offset < file_size) {
|
|
size_t to_read = file_size - offset;
|
|
if (to_read > sizeof(chunk)) {
|
|
to_read = sizeof(chunk);
|
|
}
|
|
size_t n = 0;
|
|
if (sd_card_read_file_at(filename, offset, chunk, to_read, &n) != 0 || n == 0) {
|
|
printf("\nError: Read failed at offset %zu\n", offset);
|
|
return 1;
|
|
}
|
|
for (size_t i = 0; i < n; i++) {
|
|
printf("%02x", (unsigned char)chunk[i]);
|
|
}
|
|
printf("\n");
|
|
fflush(stdout);
|
|
offset += n;
|
|
}
|
|
printf("---END SDFILE---\n");
|
|
fflush(stdout);
|
|
return 0;
|
|
}
|
|
|
|
static int do_sdcard_list(int argc, char **argv) {
|
|
const char *path = (argc >= 2) ? argv[1] : "";
|
|
|
|
if (!sd_card_is_ready()) {
|
|
printf("Error: SD card not mounted\n");
|
|
return 1;
|
|
}
|
|
|
|
printf("SD card: %s\n", path[0] ? path : "/");
|
|
esp_err_t ret = sd_card_list_dir(path);
|
|
if (ret != ESP_OK) {
|
|
printf("Error: Cannot list directory: %s\n", esp_err_to_name(ret));
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int do_sdcard_delete(int argc, char **argv) {
|
|
if (argc < 2) {
|
|
printf("Usage: sdcard delete <file>\n");
|
|
return 1;
|
|
}
|
|
const char *filename = argv[1];
|
|
|
|
if (!sd_card_is_ready()) {
|
|
printf("Error: SD card not mounted\n");
|
|
return 1;
|
|
}
|
|
if (!sd_card_file_exists(filename)) {
|
|
printf("Error: File not found: %s\n", filename);
|
|
return 1;
|
|
}
|
|
|
|
esp_err_t ret = sd_card_delete_file(filename);
|
|
if (ret != ESP_OK) {
|
|
printf("Error: Delete failed: %s\n", esp_err_to_name(ret));
|
|
return 1;
|
|
}
|
|
printf("Deleted: %s\n", filename);
|
|
return 0;
|
|
}
|
|
|
|
static int cmd_sdcard(int argc, char **argv) {
|
|
if (argc < 2) {
|
|
printf("Usage: sdcard <status|list|write|read|send|delete> [args]\n");
|
|
printf(" status - Show CD, mounted, capacity\n");
|
|
printf(" list [path] - List files (path optional, default root)\n");
|
|
printf(" write <f> <t> - Write text to file\n");
|
|
printf(" read <f> - Read and print file\n");
|
|
printf(" send <f> - Stream file over serial (use tools/sdcard_recv.py)\n");
|
|
printf(" delete <f> - Delete a file\n");
|
|
return 0;
|
|
}
|
|
if (strcmp(argv[1], "status") == 0) {
|
|
return do_sdcard_status(argc - 1, &argv[1]);
|
|
}
|
|
if (strcmp(argv[1], "list") == 0 || strcmp(argv[1], "ls") == 0) {
|
|
return do_sdcard_list(argc - 1, &argv[1]);
|
|
}
|
|
if (strcmp(argv[1], "write") == 0) {
|
|
return do_sdcard_write(argc - 1, &argv[1]);
|
|
}
|
|
if (strcmp(argv[1], "read") == 0) {
|
|
return do_sdcard_read(argc - 1, &argv[1]);
|
|
}
|
|
if (strcmp(argv[1], "send") == 0) {
|
|
return do_sdcard_send(argc - 1, &argv[1]);
|
|
}
|
|
if (strcmp(argv[1], "delete") == 0 || strcmp(argv[1], "rm") == 0) {
|
|
return do_sdcard_delete(argc - 1, &argv[1]);
|
|
}
|
|
printf("Unknown subcommand '%s'\n", argv[1]);
|
|
return 1;
|
|
}
|
|
|
|
void register_sdcard_cmd(void) {
|
|
const esp_console_cmd_t cmd = {
|
|
.command = "sdcard",
|
|
.help = "SD card: status, list [path], write, read, send, delete <file>",
|
|
.hint = "<status|list|write|read|send|delete>",
|
|
.func = &cmd_sdcard,
|
|
};
|
|
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
|
|
}
|