From eeb5fbbadd586e6913d4cc7e8dc5f510af514e31 Mon Sep 17 00:00:00 2001 From: Jake Date: Mon, 29 Sep 2025 00:14:09 +0800 Subject: [PATCH] single file transfer system implemented --- HansonServo.ino | 494 ++++++++++++++++++------------------------------ 1 file changed, 189 insertions(+), 305 deletions(-) diff --git a/HansonServo.ino b/HansonServo.ino index 6164cbf..95c6b14 100644 --- a/HansonServo.ino +++ b/HansonServo.ino @@ -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(¤tAnimation.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();