can play animation files from control panel, animation now via keyframe, not individual frames
parent
d7ff7b3155
commit
366ba5fae3
103
HansonServo.ino
103
HansonServo.ino
|
|
@ -18,6 +18,7 @@ uint8_t payload[MAX_PAYLOAD_SIZE]; // Global or static
|
||||||
#define CMD_SAVE_FILE 0x05
|
#define CMD_SAVE_FILE 0x05
|
||||||
#define CMD_MESSAGE 0x06
|
#define CMD_MESSAGE 0x06
|
||||||
#define CMD_SET_POSITION 0x07
|
#define CMD_SET_POSITION 0x07
|
||||||
|
#define CMD_PLAY_FILE 0x08
|
||||||
|
|
||||||
|
|
||||||
// ESP32 S2 PINOUT
|
// ESP32 S2 PINOUT
|
||||||
|
|
@ -57,7 +58,9 @@ void setup() {
|
||||||
Serial.println("FFat mount failed");
|
Serial.println("FFat mount failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
anim.loadFromFile("/bob.anim");
|
||||||
|
anim.printKeyframes();
|
||||||
|
playAnimation(anim);
|
||||||
// ClearFiles();
|
// ClearFiles();
|
||||||
// // PrintFileList();
|
// // PrintFileList();
|
||||||
// sweep.clear();
|
// sweep.clear();
|
||||||
|
|
@ -171,6 +174,9 @@ void handleCommand(uint8_t command, const uint8_t* payload, uint16_t length) {
|
||||||
handleSetPosition(payload, length);
|
handleSetPosition(payload, length);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CMD_PLAY_FILE:
|
||||||
|
handlePlayAnimation(payload, length);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Serial.print("Unknown command: ");
|
Serial.print("Unknown command: ");
|
||||||
Serial.println(command, HEX);
|
Serial.println(command, HEX);
|
||||||
|
|
@ -341,6 +347,38 @@ void handleDeleteFile(const uint8_t* payload, uint16_t length) {
|
||||||
sendMessage(("File Deleted: " + String(filename)).c_str(), CMD_DELETE_FILE);
|
sendMessage(("File Deleted: " + String(filename)).c_str(), CMD_DELETE_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void handlePlayAnimation(const uint8_t* payload, uint16_t length) {
|
||||||
|
sendMessage("Playing FILE");
|
||||||
|
if (length < 1) {
|
||||||
|
Serial.println("Payload too short for filename length");
|
||||||
|
sendMessage("Payload too short for filename length");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔹 Parse filename
|
||||||
|
uint16_t filenameLength = payload[0] | (payload[1] << 8);
|
||||||
|
if (length < 2 + filenameLength) {
|
||||||
|
Serial.println("Payload too short for filename");
|
||||||
|
sendMessage("Payload too short for filename");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char filename[filenameLength + 1];
|
||||||
|
|
||||||
|
|
||||||
|
memcpy(filename, payload + 2, filenameLength);
|
||||||
|
filename[filenameLength] = '\0';
|
||||||
|
|
||||||
|
//deleteFile(FFat, ("/" + String(filename)).c_str());
|
||||||
|
anim.clear();
|
||||||
|
anim.loadFromFile(("/" + String(filename)).c_str());
|
||||||
|
playAnimation(anim);
|
||||||
|
|
||||||
|
sendMessage("File Played", CMD_PLAY_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
void handleSaveFile(const uint8_t* payload, uint16_t length) {
|
void handleSaveFile(const uint8_t* payload, uint16_t length) {
|
||||||
bool valid = parseAnimationPayload(payload, length, anim);
|
bool valid = parseAnimationPayload(payload, length, anim);
|
||||||
|
|
||||||
|
|
@ -574,6 +612,69 @@ void deleteFile(fs::FS& fs, const char* path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void playAnimation(Animation& anim) {
|
void playAnimation(Animation& anim) {
|
||||||
|
uint16_t positions[NUM_CHANNELS];
|
||||||
|
const uint16_t frameCount = 400;//anim.getFrameCount();
|
||||||
|
const uint32_t frameDelay = 1000 / FRAMES_PER_SECOND;
|
||||||
|
uint32_t nextFrameTime = millis();
|
||||||
|
Serial.print("Frame Count: ");
|
||||||
|
Serial.println(frameCount);
|
||||||
|
|
||||||
|
// Organize keyframes per motor
|
||||||
|
std::vector<Keyframe> motorKeyframes[NUM_CHANNELS];
|
||||||
|
for (const auto& kf : anim.getKeyframes()) {
|
||||||
|
if (kf.motorId < NUM_CHANNELS) {
|
||||||
|
motorKeyframes[kf.motorId].push_back(kf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort keyframes per motor by frame
|
||||||
|
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
|
||||||
|
std::sort(motorKeyframes[ch].begin(), motorKeyframes[ch].end(),
|
||||||
|
[](const Keyframe& a, const Keyframe& b) {
|
||||||
|
return a.frame < b.frame;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t frame = 0; frame < frameCount; frame++) {
|
||||||
|
while (millis() < nextFrameTime) {
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
|
||||||
|
const auto& kfs = motorKeyframes[ch];
|
||||||
|
uint16_t value = 512; // default position
|
||||||
|
|
||||||
|
// Find surrounding keyframes
|
||||||
|
Keyframe prev = { ch, 0, 512 }, next = { ch, frameCount, 512 };
|
||||||
|
for (size_t i = 0; i < kfs.size(); i++) {
|
||||||
|
if (kfs[i].frame <= frame) {
|
||||||
|
prev = kfs[i];
|
||||||
|
}
|
||||||
|
if (kfs[i].frame > frame) {
|
||||||
|
next = kfs[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolate
|
||||||
|
if (prev.frame == next.frame) {
|
||||||
|
value = prev.position;
|
||||||
|
} else {
|
||||||
|
float t = float(frame - prev.frame) / (next.frame - prev.frame);
|
||||||
|
value = prev.position + t * (next.position - prev.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
positions[ch] = value;
|
||||||
|
}
|
||||||
|
Serial.println(positions[0]);
|
||||||
|
servos.syncWritePos(ids, positions, NUM_CHANNELS);
|
||||||
|
nextFrameTime += frameDelay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void playAnimationOLD(Animation& anim) {
|
||||||
uint16_t positions[NUM_CHANNELS];
|
uint16_t positions[NUM_CHANNELS];
|
||||||
const uint16_t frameCount = anim.getFrameCount();
|
const uint16_t frameCount = anim.getFrameCount();
|
||||||
const uint32_t frameDelay = 1000 / FRAMES_PER_SECOND; // 20 ms
|
const uint32_t frameDelay = 1000 / FRAMES_PER_SECOND; // 20 ms
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue