Public Access
sd put: copy a file to the SD card over USB serial
scripts/sd_put.sh <file> [card path] sends a file (by default into /updates, for Update from SD) without taking the card out. The serial driver drops bytes once its receive buffer is full, so the transfer is stop-and-wait: 1 KB chunks, each acknowledged once the Storage Service has written it, into a 2 KB receive buffer. The device checks the SHA-256 before renaming <path>.part into place, and gives up after 5 s of silence or a card job that never returns. FileReceiver holds the logic, with 12 host tests. About 55 KB/s: 1.6 MB in under 30 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
+103
@@ -2,6 +2,9 @@
|
||||
#include <M5Cardputer.h>
|
||||
#include <SD.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include "app_manager.h"
|
||||
#include "apps/demo_app.h"
|
||||
#include "apps/irc_app.h"
|
||||
@@ -10,6 +13,7 @@
|
||||
#include "apps/wifi_tools_app.h"
|
||||
#include "apps/setup_app.h"
|
||||
#include "event_bus.h"
|
||||
#include "file_receiver.h"
|
||||
#include "key_mapper.h"
|
||||
#include "platform/nvs_store.h"
|
||||
#include "service_manager.h"
|
||||
@@ -97,6 +101,7 @@ extern "C" bool verifyRollbackLater() { return true; } // C linkage, or the wea
|
||||
void setup() {
|
||||
nvs.begin();
|
||||
UpdateService::bootGuard(nvs); // first: before anything that could crash on new firmware
|
||||
Serial.setRxBufferSize(2 * FileReceiver::kChunk); // before the port opens; sd put sends 1 chunk at a time
|
||||
|
||||
auto cfg = M5.config();
|
||||
M5Cardputer.begin(cfg, true);
|
||||
@@ -158,7 +163,103 @@ static void printListingWhenReady() {
|
||||
}
|
||||
}
|
||||
|
||||
// sd put: a file sent over serial by scripts/sd_put.py. The main loop reads the bytes; every card
|
||||
// access runs as a Storage Service job, which reports back through uploadResult.
|
||||
static FileReceiver upload;
|
||||
static std::atomic<int> uploadResult{-1}; // set by a job: 1 ok, 0 failed; -1 nothing new
|
||||
static bool uploadJobRunning = false;
|
||||
|
||||
static void uploadJob(std::function<bool()> job) {
|
||||
uploadJobRunning = true;
|
||||
storageService->runJob([job]() { uploadResult = job() ? 1 : 0; });
|
||||
}
|
||||
|
||||
static void startUpload(const String& args) {
|
||||
if (upload.active() || uploadJobRunning) return (void)Serial.println("sd put: error busy");
|
||||
std::string error = upload.begin(args.c_str(), millis());
|
||||
StorageState card = storageService->state();
|
||||
if (error.empty() && !card.present) error = "no SD card";
|
||||
if (error.empty() && card.totalBytes - card.usedBytes < upload.size() + 64 * 1024) error = "not enough space";
|
||||
if (!error.empty()) {
|
||||
upload.reset();
|
||||
return (void)Serial.printf("sd put: error %s\n", error.c_str());
|
||||
}
|
||||
std::string part = upload.partPath();
|
||||
uploadJob([part]() {
|
||||
for (size_t slash = part.find('/', 1); slash != std::string::npos; slash = part.find('/', slash + 1)) {
|
||||
std::string dir = part.substr(0, slash);
|
||||
if (!SD.exists(dir.c_str()) && !SD.mkdir(dir.c_str())) return false;
|
||||
}
|
||||
if (SD.exists(part.c_str())) SD.remove(part.c_str());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
static void readUploadBytes() {
|
||||
uint8_t buf[256];
|
||||
size_t want;
|
||||
while (!uploadJobRunning && (want = std::min(upload.wanted(), sizeof buf)) > 0 && Serial.available() > 0) {
|
||||
size_t n = Serial.read(buf, std::min<size_t>(want, Serial.available()));
|
||||
upload.feed(buf, n, millis());
|
||||
}
|
||||
}
|
||||
|
||||
static void uploadStep() {
|
||||
using S = FileReceiver::State;
|
||||
uint32_t now = millis();
|
||||
int result = uploadResult.exchange(-1);
|
||||
if (result >= 0) {
|
||||
uploadJobRunning = false;
|
||||
bool ok = result == 1;
|
||||
switch (upload.state()) {
|
||||
case S::Receiving: // the folder job from startUpload: the sender may start
|
||||
if (ok) Serial.printf("sd put: ready %u\n", (unsigned)FileReceiver::kChunk);
|
||||
else {
|
||||
upload.reset();
|
||||
return (void)Serial.println("sd put: error card not writable");
|
||||
}
|
||||
break;
|
||||
case S::Writing:
|
||||
upload.chunkWritten(ok, now);
|
||||
if (upload.state() == S::Receiving) Serial.printf("sd put: ok %u\n", (unsigned)upload.received());
|
||||
break;
|
||||
case S::Finishing: upload.finished(ok); break;
|
||||
default: break; // a job that finished after the transfer failed
|
||||
}
|
||||
}
|
||||
if (!upload.active()) return;
|
||||
upload.tick(now);
|
||||
std::string path = upload.path(), part = upload.partPath();
|
||||
if (upload.state() == S::Failed) {
|
||||
// Reported even if a job never came back (no card: the storage task drops it).
|
||||
Serial.printf("sd put: error %s\n", upload.error().c_str());
|
||||
storageService->runJob([part]() { SD.remove(part.c_str()); });
|
||||
upload.reset();
|
||||
uploadJobRunning = false;
|
||||
return;
|
||||
}
|
||||
if (uploadJobRunning) return;
|
||||
if (upload.state() == S::Writing) {
|
||||
auto chunk = std::make_shared<std::vector<uint8_t>>(upload.chunk());
|
||||
uploadJob([chunk, part]() {
|
||||
File f = SD.open(part.c_str(), FILE_APPEND);
|
||||
bool ok = f && f.write(chunk->data(), chunk->size()) == chunk->size();
|
||||
if (f) f.close();
|
||||
return ok;
|
||||
});
|
||||
} else if (upload.state() == S::Finishing) {
|
||||
uploadJob([path, part]() {
|
||||
if (SD.exists(path.c_str())) SD.remove(path.c_str());
|
||||
return SD.rename(part.c_str(), path.c_str());
|
||||
});
|
||||
} else if (upload.state() == S::Done) {
|
||||
Serial.printf("sd put: done %s %u B\n", path.c_str(), (unsigned)upload.size());
|
||||
upload.reset();
|
||||
}
|
||||
}
|
||||
|
||||
static void serialCommands() {
|
||||
if (upload.active()) return readUploadBytes(); // raw file bytes, not commands
|
||||
static String line;
|
||||
while (Serial.available()) {
|
||||
char c = Serial.read();
|
||||
@@ -202,6 +303,7 @@ static void serialCommands() {
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
if (line.startsWith("sd put ")) startUpload(line.substring(7)); // then raw bytes: see uploadStep()
|
||||
if (line == "sd list") {
|
||||
storageService->requestListing();
|
||||
listingWanted = true;
|
||||
@@ -260,6 +362,7 @@ void loop() {
|
||||
uint32_t now = millis();
|
||||
|
||||
serialCommands();
|
||||
uploadStep();
|
||||
printListingWhenReady();
|
||||
M5Cardputer.update();
|
||||
if (M5Cardputer.Keyboard.isChange()) {
|
||||
|
||||
Reference in New Issue
Block a user