single file transfer system implemented

master
Jake 2025-09-29 00:14:09 +08:00
parent 069f0c405c
commit eeb5fbbadd
1 changed files with 189 additions and 305 deletions

View File

@ -8,20 +8,13 @@
#define HEADER1 0xAA
#define HEADER2 0x55
#define MAX_PAYLOAD_SIZE 10240 // 10 KB
uint8_t payload[MAX_PAYLOAD_SIZE]; // Global or static
#define CMD_ID_REQUEST 0x01
#define CMD_FILE_LIST 0x02
#define CMD_LOAD_FILE 0x03
#define CMD_DELETE_FILE 0x04
#define CMD_LOAD_FILE_CHUNK 0x05
#define MAX_ANIMATION_SIZE (16 + MAX_FRAMES * NUM_CHANNELS * 2 + 512) // generous buffer
uint8_t animationBuffer[MAX_ANIMATION_SIZE];
size_t receivedSize = 0;
size_t expectedSize = 0;
Animation currentAnimation;
// ESP32 S2 PINOUT
@ -30,6 +23,7 @@ Animation currentAnimation;
#define DE_PIN 33 // Driver Enable
#define RE_PIN 3 // Receiver Enable
Animation animation;
Animation sweep;
Animation stagger;
@ -45,7 +39,7 @@ uint16_t pos1[] = { 0, 0, 0, 0, 0 };
uint16_t pos2[] = { 1023, 1023, 1023, 1023, 1023 };
void setup() {
Serial.begin(115200);
Serial.begin(1000000);
for (int i = 0; i < 5; i++) {
Serial.println(i);
delay(500);
@ -107,10 +101,193 @@ void SetID(uint8_t oldID, uint8_t newID) {
delay(1000);
}
void HandleSerialRequests() {
if (Serial.available() >= 6) { // 2 headers + 1 command + 2 length + 1 checksum minimum
if (Serial.read() == HEADER1 && Serial.read() == HEADER2) {
uint8_t command = Serial.read();
uint16_t length = (Serial.read() << 8) | Serial.read();
if (length > MAX_PAYLOAD_SIZE) {
Serial.println("Payload too large");
while (Serial.available()) Serial.read(); // flush junk
return;
}
while (Serial.available() < length + 1)
; // wait for payload + checksum
uint8_t checksum = command ^ (length >> 8) ^ (length & 0xFF);
for (uint16_t i = 0; i < length; i++) {
payload[i] = Serial.read();
checksum ^= payload[i];
}
uint8_t receivedChecksum = Serial.read();
if (checksum == receivedChecksum) {
handleCommand(command, payload, length);
} else {
Serial.println("Checksum mismatch");
}
}
}
}
void handleCommand(uint8_t command, const uint8_t* payload, uint16_t length) {
switch (command) {
case CMD_ID_REQUEST: // CMD_ID_REQUEST
handleIdRequest();
break;
case CMD_FILE_LIST: //
handleFileList();
break;
case CMD_LOAD_FILE: //
handleLoadFile(payload, length);
break;
case CMD_DELETE_FILE: //
handleDeleteFile(payload, length);
break;
default:
Serial.print("Unknown command: ");
Serial.println(command, HEX);
break;
}
}
void handleIdRequest() {
String payload = String(DEVICE_NAME) + "|" + FIRMWARE_VERSION;
uint16_t length = payload.length();
uint8_t checksum = CMD_ID_REQUEST ^ (length >> 8) ^ (length & 0xFF);
for (int i = 0; i < length; i++) {
checksum ^= payload[i];
}
Serial.write(HEADER1);
Serial.write(HEADER2);
Serial.write(CMD_ID_REQUEST);
Serial.write((length >> 8) & 0xFF);
Serial.write(length & 0xFF);
Serial.write((const uint8_t*)payload.c_str(), length);
Serial.write(checksum);
}
void handleFileList() {
File root = FFat.open("/");
if (!root || !root.isDirectory()) {
sendFileListResponse(""); // empty payload
return;
}
String payload = "";
File file = root.openNextFile();
while (file) {
if (!file.isDirectory()) {
payload += String(file.name()) + "\n";
}
file = root.openNextFile();
}
sendFileListResponse(payload);
}
void sendFileListResponse(const String& payloadStr) {
uint16_t length = payloadStr.length();
if (length > MAX_PAYLOAD_SIZE) {
Serial.println("File list too large");
return;
}
uint8_t checksum = CMD_FILE_LIST ^ (length >> 8) ^ (length & 0xFF);
for (uint16_t i = 0; i < length; i++) {
checksum ^= payloadStr[i];
}
Serial.write(HEADER1);
Serial.write(HEADER2);
Serial.write(CMD_FILE_LIST);
Serial.write((length >> 8) & 0xFF);
Serial.write(length & 0xFF);
Serial.write((const uint8_t*)payloadStr.c_str(), length);
Serial.write(checksum);
}
void handleLoadFile(const uint8_t* payload, uint16_t length) {
if (length == 0 || length >= 128) {
Serial.println("Invalid filename");
return;
}
char filename[128];
memcpy(filename, payload, length);
filename[length] = '\0';
File file = FFat.open(filename, "r");
if (!file || !file.available()) {
Serial.println("File not found or empty");
return;
}
size_t fileSize = file.size();
if (fileSize > 65535) {
Serial.println("File too large for single transfer");
file.close();
return;
}
uint8_t* buffer = (uint8_t*)malloc(fileSize);
if (!buffer) {
Serial.println("Memory allocation failed");
file.close();
return;
}
size_t bytesRead = file.read(buffer, fileSize);
file.close();
if (bytesRead != fileSize) {
Serial.println("File read error");
free(buffer);
return;
}
// 🔹 Compute checksum
uint8_t checksum = CMD_LOAD_FILE ^ (fileSize >> 8) ^ (fileSize & 0xFF);
for (size_t i = 0; i < fileSize; i++) {
checksum ^= buffer[i];
}
// 🔹 Send packet
Serial.write(HEADER1);
Serial.write(HEADER2);
Serial.write(CMD_LOAD_FILE);
Serial.write((fileSize >> 8) & 0xFF);
Serial.write(fileSize & 0xFF);
Serial.write(buffer, fileSize);
Serial.write(checksum);
free(buffer);
Serial.println("File sent in one go");
}
void handleDeleteFile(const uint8_t* payload, uint16_t length) {
}
void handleFileChunk(const uint8_t* payload, uint16_t length) {
}
unsigned long lastSend = 0;
void loop() {
HandleSerialRequests();
// put your main code here, to run repeatedly:
//PingAll();
@ -128,6 +305,7 @@ void loop() {
if (millis() - lastSend > 1000) {
//sendMessageFromESP32(String(millis()));
//handleIdRequest();
//PrintFileList();
lastSend = millis();
}
@ -135,211 +313,6 @@ void loop() {
void sendOkResponse(uint8_t command, const char* note = "chunk received") {
String payload = String("{\"status\":\"ok\",\"note\":\"") + note + "\"}";
uint16_t length = payload.length();
uint8_t checksum = command ^ (length >> 8) ^ (length & 0xFF);
for (int i = 0; i < length; i++) {
checksum ^= payload[i];
}
Serial.write(HEADER1);
Serial.write(HEADER2);
Serial.write(command);
Serial.write((length >> 8) & 0xFF);
Serial.write(length & 0xFF);
Serial.write((const uint8_t*)payload.c_str(), length);
Serial.write(checksum);
}
void HandleSerialRequests() {
if (Serial.available() >= 5) {
if (Serial.read() == HEADER1 && Serial.read() == HEADER2) {
uint8_t command = Serial.read();
uint8_t lengthHigh = Serial.read();
uint8_t lengthLow = Serial.read();
uint16_t payloadLength = (lengthHigh << 8) | lengthLow;
while (Serial.available() < payloadLength)
;
uint8_t payload[payloadLength];
for (int i = 0; i < payloadLength; i++) {
payload[i] = Serial.read();
}
sendOkResponse(CMD_LOAD_FILE_CHUNK, String(command).c_str());
handleCommand(command, payload, payloadLength);
}
}
}
void handleCommand(uint8_t command, const uint8_t* payload, uint16_t length) {
sendOkResponse(CMD_LOAD_FILE_CHUNK, String(command).c_str());
switch (command) {
case CMD_ID_REQUEST:
sendIdPacket();
break;
case CMD_FILE_LIST:
sendFileList();
break;
case CMD_LOAD_FILE: {
String fileName = String((const char*)payload, length);
sendFileContent(fileName);
break;
}
case CMD_DELETE_FILE: {
String fileName = String((const char*)payload, length);
deleteFile(fileName);
break;
}
case CMD_LOAD_FILE_CHUNK:
handleAnimationChunk(payload, length);
break;
}
}
void handleAnimationChunk(const uint8_t* data, uint16_t length) {
if (length < 4) {
sendOkResponse(CMD_LOAD_FILE_CHUNK, "fail: too short");
return;
}
uint16_t offset = (data[0] << 8) | data[1];
uint16_t totalSize = (data[2] << 8) | data[3];
const uint8_t* chunk = &data[4];
size_t chunkSize = length - 4;
if (offset == 0) {
receivedSize = 0;
expectedSize = totalSize;
}
for (size_t i = 0; i < chunkSize; i++) {
if (offset + i < MAX_ANIMATION_SIZE) {
animationBuffer[offset + i] = chunk[i];
}
}
receivedSize += chunkSize;
Serial.printf("Chunk received: offset=%d size=%d\n", offset, chunkSize);
Serial.printf("Total received: %d / %d\n", receivedSize, expectedSize);
if (receivedSize >= expectedSize) {
Serial.println("Full animation received. Parsing…");
loadAnimationFromBuffer(animationBuffer, expectedSize);
currentAnimation.saveToFile("savey.anim");
sendOkResponse(CMD_LOAD_FILE_CHUNK, "final chunk");
} else {
String note = "receivedSize=" + String(receivedSize);
sendOkResponse(CMD_LOAD_FILE_CHUNK, note.c_str());
}
}
void loadAnimationFromBuffer(const uint8_t* buffer, size_t length) {
currentAnimation.clear();
memcpy(&currentAnimation.header, buffer, sizeof(AnimationHeader));
size_t offset = sizeof(AnimationHeader);
for (uint16_t frame = 0; frame < MAX_FRAMES; frame++) {
for (uint8_t channel = 0; channel < NUM_CHANNELS; channel++) {
uint16_t value = buffer[offset] | (buffer[offset + 1] << 8);
currentAnimation.setFrame(frame, channel, value);
offset += 2;
}
}
uint16_t keyframeCount = buffer[offset] | (buffer[offset + 1] << 8);
offset += 2;
for (uint16_t i = 0; i < keyframeCount; i++) {
uint8_t motorId = buffer[offset++];
uint16_t frame = buffer[offset] | (buffer[offset + 1] << 8);
offset += 2;
uint16_t position = buffer[offset] | (buffer[offset + 1] << 8);
offset += 2;
currentAnimation.addKeyframe(motorId, frame, position);
}
Serial.println("Animation loaded into memory.");
currentAnimation.printKeyframes();
}
void sendReply(uint8_t command, const String& payload) {
uint16_t length = payload.length(); // Supports up to 65,535 bytes
// Calculate checksum using both length bytes
uint8_t checksum = command ^ (length >> 8) ^ (length & 0xFF);
for (int i = 0; i < length; i++) {
checksum ^= payload[i];
}
// Send packet with 2-byte length
Serial.write(HEADER1);
Serial.write(HEADER2);
Serial.write(command);
Serial.write((length >> 8) & 0xFF); // High byte
Serial.write(length & 0xFF); // Low byte
Serial.write((const uint8_t*)payload.c_str(), length);
Serial.write(checksum);
}
void sendChunkReply(uint8_t command, const String& filename, const uint8_t* chunkData, size_t chunkSize, size_t offset, size_t totalSize) {
// Build JSON metadata
String payload = "{\"file\":\"" + filename + "\",\"offset\":" + offset + ",\"totalSize\":" + totalSize + ",\"chunk\":[";
for (size_t i = 0; i < chunkSize; i++) {
payload += String(chunkData[i]);
if (i < chunkSize - 1) payload += ",";
}
payload += "]}";
// Calculate payload length
uint16_t length = payload.length();
// Calculate checksum
uint8_t checksum = command ^ (length >> 8) ^ (length & 0xFF);
for (int i = 0; i < length; i++) {
checksum ^= payload[i];
}
// Send packet
Serial.write(HEADER1);
Serial.write(HEADER2);
Serial.write(command);
Serial.write((length >> 8) & 0xFF); // High byte
Serial.write(length & 0xFF); // Low byte
Serial.write((const uint8_t*)payload.c_str(), length);
Serial.write(checksum);
}
void sendIdPacket() {
String payload = "{\"name\":\"" + String(DEVICE_NAME) + "\",\"version\":\"" + String(FIRMWARE_VERSION) + "\"}";
sendReply(CMD_ID_REQUEST, payload);
}
void PrintFileList() {
File root = FFat.open("/");
if (!root || !root.isDirectory()) {
@ -361,95 +334,6 @@ void PrintFileList() {
Serial.println("End of file list.");
}
void sendFileList() {
File root = FFat.open("/");
if (!root || !root.isDirectory()) {
sendReply(CMD_FILE_LIST, "[]");
return;
}
File file = root.openNextFile();
String payload = "[";
bool first = true;
while (file) {
if (!file.isDirectory()) {
if (!first) payload += ",";
payload += "\"" + String(file.name()) + "\"";
first = false;
}
file = root.openNextFile();
}
payload += "]";
sendReply(CMD_FILE_LIST, payload);
}
void sendFileContent(const String& filename) {
sendFileInChunks(filename);
return;
File file = FFat.open(filename, FILE_READ);
if (!file) {
sendReply(CMD_LOAD_FILE, "{\"error\":\"File not found\"}");
return;
}
String raw;
while (file.available()) {
raw += (char)file.read();
}
file.close();
String encoded = base64::encode(raw); // This must be base64
String payload = "{\"file\":\"" + filename + "\",\"content\":\"" + encoded + "\"}";
sendReply(CMD_LOAD_FILE, payload);
}
void sendFileInChunks(const String& filename) {
File file = FFat.open(filename, FILE_READ);
if (!file) {
sendReply(CMD_LOAD_FILE, "{\"error\":\"File not found\"}");
return;
}
const size_t chunkSize = 512;
size_t totalSize = file.size();
size_t offset = 0;
size_t chunksSent = 0;
unsigned long startTime = millis();
while (offset < totalSize) {
size_t remaining = totalSize - offset;
size_t thisChunk = remaining < chunkSize ? remaining : chunkSize;
uint8_t buffer[thisChunk];
file.read(buffer, thisChunk);
sendChunkReply(CMD_LOAD_FILE_CHUNK, filename, buffer, thisChunk, offset, totalSize);
offset += thisChunk;
chunksSent++;
delay(10); // Optional pacing
}
file.close();
unsigned long duration = millis() - startTime;
String finalReply = "{\"status\":\"complete\",\"file\":\"" + filename + "\",\"chunks\":" + chunksSent + ",\"bytesSent\":" + totalSize + ",\"durationMs\":" + duration + "}";
sendReply(CMD_LOAD_FILE, finalReply);
}
void deleteFile(const String& filename) {
String payload = "{\"deleted\":\"" + filename + "\"}";
sendReply(CMD_DELETE_FILE, payload);
}
void playAnimation(Animation& anim) {
uint16_t positions[NUM_CHANNELS];
const uint16_t frameCount = anim.getFrameCount();