single file transfer system implemented
parent
069f0c405c
commit
eeb5fbbadd
494
HansonServo.ino
494
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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue