added play from and stop commands
parent
d603ef2e18
commit
77a151ad95
|
|
@ -93,11 +93,13 @@ void runNodeAnimation() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset tick when animation starts
|
// Reset tick when animation starts or if currentTick is less than startFrame
|
||||||
if (!wasActive) {
|
if (!wasActive || currentTick < animState.startFrame) {
|
||||||
currentTick = 0;
|
currentTick = animState.startFrame; // Start from specified frame
|
||||||
lastTickTime = millis();
|
lastTickTime = millis();
|
||||||
wasActive = true;
|
wasActive = true;
|
||||||
|
// Debug: send startFrame via MSGE
|
||||||
|
sendMessage("Animation startFrame: " + String(animState.startFrame) + ", currentTick: " + String(currentTick));
|
||||||
}
|
}
|
||||||
|
|
||||||
config.enableAllMotors();
|
config.enableAllMotors();
|
||||||
|
|
@ -105,27 +107,27 @@ void runNodeAnimation() {
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
if (now - lastTickTime < FRAME_INTERVAL_MS)
|
if (now - lastTickTime < FRAME_INTERVAL_MS)
|
||||||
return;
|
return;
|
||||||
lastTickTime = now;
|
lastTickTime = now;
|
||||||
|
|
||||||
// Tick the node graph
|
// Tick the node graph
|
||||||
animState.current->nodeGraph.tick(currentTick, *animState.current);
|
animState.current->nodeGraph.tick(currentTick, *animState.current);
|
||||||
auto outputs = animState.current->nodeGraph.getServoOutputs();
|
auto outputs = animState.current->nodeGraph.getServoOutputs();
|
||||||
|
|
||||||
// Collect motor commands
|
// Collect motor commands
|
||||||
std::vector<uint8_t> motorIDs;
|
std::vector<uint8_t> motorIDs;
|
||||||
std::vector<uint16_t> positions;
|
std::vector<uint16_t> positions;
|
||||||
std::vector<uint16_t> speeds;
|
std::vector<uint16_t> speeds;
|
||||||
|
|
||||||
for (const auto &[motorID, value] : outputs) {
|
for (const auto &[motorID, value] : outputs) {
|
||||||
if (value != 65535) {
|
if (value != 65535) {
|
||||||
motorIDs.push_back(motorID);
|
motorIDs.push_back(motorID);
|
||||||
positions.push_back(value);
|
positions.push_back(value);
|
||||||
speeds.push_back(0);
|
speeds.push_back(0);
|
||||||
config.setMotorPosition(motorID, value);
|
config.setMotorPosition(motorID, value);
|
||||||
config.setMotorEnabled(motorID, true);
|
config.setMotorEnabled(motorID, true);
|
||||||
} else {
|
} else {
|
||||||
// Only disable torque for motors that should be limp
|
// Only disable torque for motors that should be limp
|
||||||
if (config.setMotorEnabled(motorID, false)) {
|
if (config.setMotorEnabled(motorID, false)) {
|
||||||
servoManager[0]->disableTorque(motorID);
|
servoManager[0]->disableTorque(motorID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -138,6 +140,7 @@ void runNodeAnimation() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit per-frame event: [frameLo, frameHi, playMode, status=0]
|
// Emit per-frame event: [frameLo, frameHi, playMode, status=0]
|
||||||
|
// Send actual frame number (currentTick), not relative frame
|
||||||
{
|
{
|
||||||
uint8_t payload[4];
|
uint8_t payload[4];
|
||||||
payload[0] = currentTick & 0xFF;
|
payload[0] = currentTick & 0xFF;
|
||||||
|
|
@ -147,27 +150,40 @@ void runNodeAnimation() {
|
||||||
sendPacket(Tag::FRAME, payload, 4);
|
sendPacket(Tag::FRAME, payload, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTick++;
|
currentTick++;
|
||||||
|
|
||||||
// Handle animation end (0 = run indefinitely for variable-only animations)
|
// Handle animation end (0 = run indefinitely for variable-only animations)
|
||||||
if (animState.current->getFrameCount() > 0 &&
|
// Calculate total frames played: currentTick - startFrame
|
||||||
currentTick > animState.current->getFrameCount()) {
|
uint16_t framesPlayed = currentTick - animState.startFrame;
|
||||||
|
// Calculate remaining frames: total frames minus startFrame
|
||||||
|
// If animation has 100 frames and we start at 50, we should only play 50 frames
|
||||||
|
uint16_t totalFrames = animState.current->getFrameCount();
|
||||||
|
uint16_t remainingFrames = (totalFrames > animState.startFrame) ? (totalFrames - animState.startFrame) : 0;
|
||||||
|
|
||||||
|
// Debug: show completion check values every 10 frames
|
||||||
|
if (framesPlayed % 10 == 0 || framesPlayed >= remainingFrames - 1) {
|
||||||
|
sendMessage("Frame check - played: " + String(framesPlayed) + ", remaining: " + String(remainingFrames) + ", total: " + String(totalFrames) + ", startFrame: " + String(animState.startFrame));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalFrames > 0 && remainingFrames > 0 && framesPlayed >= remainingFrames) {
|
||||||
switch (animState.playMode) {
|
switch (animState.playMode) {
|
||||||
case PLAY_ONCE:
|
case PLAY_ONCE:
|
||||||
animState.stop();
|
animState.stop();
|
||||||
{
|
{
|
||||||
uint8_t done[4];
|
uint8_t done[4];
|
||||||
done[0] = currentTick & 0xFF;
|
done[0] = currentTick & 0xFF;
|
||||||
done[1] = (currentTick >> 8) & 0xFF;
|
done[1] = (currentTick >> 8) & 0xFF;
|
||||||
done[2] = static_cast<uint8_t>(animState.playMode);
|
done[2] = static_cast<uint8_t>(animState.playMode);
|
||||||
done[3] = 1; // complete
|
done[3] = 1; // complete
|
||||||
sendPacket(Tag::FRAME, done, 4);
|
sendPacket(Tag::FRAME, done, 4);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PLAY_LOOP:
|
case PLAY_LOOP:
|
||||||
// Continue looping
|
// Reset to start frame for seamless looping
|
||||||
break;
|
currentTick = animState.startFrame;
|
||||||
case PLAY_REPEAT:
|
sendMessage("Looping back to startFrame: " + String(animState.startFrame));
|
||||||
|
break;
|
||||||
|
case PLAY_REPEAT:
|
||||||
if (--animState.repeatsRemaining == 0) {
|
if (--animState.repeatsRemaining == 0) {
|
||||||
animState.stop();
|
animState.stop();
|
||||||
uint8_t done[4];
|
uint8_t done[4];
|
||||||
|
|
@ -176,12 +192,16 @@ void runNodeAnimation() {
|
||||||
done[2] = static_cast<uint8_t>(animState.playMode);
|
done[2] = static_cast<uint8_t>(animState.playMode);
|
||||||
done[3] = 1; // complete
|
done[3] = 1; // complete
|
||||||
sendPacket(Tag::FRAME, done, 4);
|
sendPacket(Tag::FRAME, done, 4);
|
||||||
|
// Animation stopped, don't reset tick
|
||||||
|
} else {
|
||||||
|
// Reset to start frame for next repeat
|
||||||
|
currentTick = animState.startFrame;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
currentTick = 0;
|
// Don't reset currentTick here - each case handles it if needed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ ushort Crc16Ccitt(byte[] data)
|
||||||
| CMD_MESSAGE | 0x06 | `MSGE` | Log/debug message |
|
| CMD_MESSAGE | 0x06 | `MSGE` | Log/debug message |
|
||||||
| CMD_SET_POSITION | 0x07 | `MSET` | Set motor positions |
|
| CMD_SET_POSITION | 0x07 | `MSET` | Set motor positions |
|
||||||
| CMD_PLAY_FILE | 0x08 | `FPLY` | Play animation |
|
| CMD_PLAY_FILE | 0x08 | `FPLY` | Play animation |
|
||||||
|
| CMD_STOP_FILE | 0x09 | `FSTP` | Stop animation |
|
||||||
| CMD_SCAN_CHANNEL | 0x09 | `MSCN` | Scan for motors |
|
| CMD_SCAN_CHANNEL | 0x09 | `MSCN` | Scan for motors |
|
||||||
| CMD_WRITE_DATA | 0x10 | `MWRT` | Write motor register |
|
| CMD_WRITE_DATA | 0x10 | `MWRT` | Write motor register |
|
||||||
| CMD_WRITE_CONFIG_UPDATE | 0x12 | `CONF` | Update config |
|
| CMD_WRITE_CONFIG_UPDATE | 0x12 | `CONF` | Update config |
|
||||||
|
|
@ -160,9 +161,23 @@ ushort Crc16Ccitt(byte[] data)
|
||||||
[filename: N bytes]
|
[filename: N bytes]
|
||||||
[play_mode: 1 byte] // 0=idle, 1=once, 2=loop, 3=repeat
|
[play_mode: 1 byte] // 0=idle, 1=once, 2=loop, 3=repeat
|
||||||
[repeat_count: 1 byte]
|
[repeat_count: 1 byte]
|
||||||
|
[start_frame: 2 bytes LE] // Frame number to start playback from (0-based)
|
||||||
```
|
```
|
||||||
**Response:** `ACK!` on success, `NACK` if file not found
|
**Response:** `ACK!` on success, `NACK` if file not found
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- `start_frame` allows resuming playback from a specific frame
|
||||||
|
- FRAME packets report actual frame numbers (i.e., if start_frame=163, FRAME packets will show 163, 164, 165...)
|
||||||
|
|
||||||
|
#### `FSTP` - Stop Animation
|
||||||
|
**Request:** Empty payload (0 bytes)
|
||||||
|
**Response:** `ACK!` on success
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Immediately stops the currently playing animation regardless of play mode
|
||||||
|
- Motors remain in their current positions (torque not disabled)
|
||||||
|
- No FRAME completion packet is sent
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Motor Control
|
### Motor Control
|
||||||
|
|
|
||||||
32
commands.cpp
32
commands.cpp
|
|
@ -42,11 +42,12 @@ static void resetSaveSession() {
|
||||||
// AnimationState
|
// AnimationState
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
void AnimationState::play(PlayMode mode, uint8_t repeats) {
|
void AnimationState::play(PlayMode mode, uint8_t repeats, uint16_t startFrame) {
|
||||||
current = &animation;
|
current = &animation;
|
||||||
current->setActive(true);
|
current->setActive(true);
|
||||||
playMode = mode;
|
playMode = mode;
|
||||||
repeatsRemaining = repeats;
|
repeatsRemaining = repeats;
|
||||||
|
this->startFrame = startFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationState::stop() {
|
void AnimationState::stop() {
|
||||||
|
|
@ -110,6 +111,9 @@ void dispatchCommand() {
|
||||||
else if (tagMatches(tag, Tag::FPLAY)) {
|
else if (tagMatches(tag, Tag::FPLAY)) {
|
||||||
handleFilePlay(payload, len);
|
handleFilePlay(payload, len);
|
||||||
}
|
}
|
||||||
|
else if (tagMatches(tag, Tag::FSTP)) {
|
||||||
|
handleFileStop(payload, len);
|
||||||
|
}
|
||||||
// Motor Control
|
// Motor Control
|
||||||
else if (tagMatches(tag, Tag::MSET)) {
|
else if (tagMatches(tag, Tag::MSET)) {
|
||||||
handleMotorSet(payload, len);
|
handleMotorSet(payload, len);
|
||||||
|
|
@ -370,14 +374,14 @@ void handleFileDelete(const uint8_t* payload, uint16_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleFilePlay(const uint8_t* payload, uint16_t len) {
|
void handleFilePlay(const uint8_t* payload, uint16_t len) {
|
||||||
if (len < 4) {
|
if (len < 6) { // Minimum: filenameLen(2) + filename(1) + mode(1) + repeats(1) + startFrame(2)
|
||||||
sendNack(Tag::FPLAY, "Invalid request");
|
sendNack(Tag::FPLAY, "Invalid request");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t filenameLen = payload[0] | (payload[1] << 8);
|
uint16_t filenameLen = payload[0] | (payload[1] << 8);
|
||||||
if (len < 2 + filenameLen + 2) {
|
if (len < 2 + filenameLen + 4) { // filenameLen + filename + mode + repeats + startFrame
|
||||||
sendNack(Tag::FPLAY, "Invalid filename");
|
sendNack(Tag::FPLAY, "Invalid payload length");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -388,19 +392,31 @@ void handleFilePlay(const uint8_t* payload, uint16_t len) {
|
||||||
uint16_t offset = 2 + filenameLen;
|
uint16_t offset = 2 + filenameLen;
|
||||||
PlayMode mode = static_cast<PlayMode>(payload[offset]);
|
PlayMode mode = static_cast<PlayMode>(payload[offset]);
|
||||||
uint8_t repeats = payload[offset + 1];
|
uint8_t repeats = payload[offset + 1];
|
||||||
|
uint16_t startFrame = payload[offset + 2] | (payload[offset + 3] << 8);
|
||||||
|
|
||||||
|
// Debug: show parsed startFrame
|
||||||
|
sendMessage("FPLAY parsed - startFrame bytes: [" + String(payload[offset + 2]) + ", " + String(payload[offset + 3]) + "] = " + String(startFrame));
|
||||||
|
|
||||||
animState.animation.clear();
|
animState.animation.clear();
|
||||||
String fullPath = "/" + String(filename);
|
String fullPath = "/" + String(filename);
|
||||||
|
|
||||||
if (animState.animation.loadFromFile(fullPath.c_str())) {
|
if (animState.animation.loadFromFile(fullPath.c_str())) {
|
||||||
animState.play(mode, repeats);
|
animState.play(mode, repeats, startFrame);
|
||||||
sendAck(Tag::FPLAY);
|
sendAck(Tag::FPLAY);
|
||||||
sendMessage("Playing: " + String(filename));
|
sendMessage("Playing: " + String(filename) + " from frame " + String(startFrame));
|
||||||
|
sendMessage("animState.startFrame stored as: " + String(animState.startFrame));
|
||||||
} else {
|
} else {
|
||||||
sendNack(Tag::FPLAY, "Load failed");
|
sendNack(Tag::FPLAY, "Load failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleFileStop(const uint8_t* payload, uint16_t len) {
|
||||||
|
// FSTP has no payload (len should be 0, but we don't strictly require it)
|
||||||
|
animState.stop();
|
||||||
|
sendAck(Tag::FSTP);
|
||||||
|
sendMessage("Animation stopped");
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Motor Control Handlers
|
// Motor Control Handlers
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,9 @@ struct AnimationState {
|
||||||
Animation* current = nullptr;
|
Animation* current = nullptr;
|
||||||
PlayMode playMode = PLAY_IDLE;
|
PlayMode playMode = PLAY_IDLE;
|
||||||
uint8_t repeatsRemaining = 0;
|
uint8_t repeatsRemaining = 0;
|
||||||
|
uint16_t startFrame = 0; // Frame to start playback from
|
||||||
void play(PlayMode mode, uint8_t repeats = 0);
|
|
||||||
|
void play(PlayMode mode, uint8_t repeats = 0, uint16_t startFrame = 0);
|
||||||
void stop();
|
void stop();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -59,6 +60,7 @@ void handleFileLoad(const uint8_t* payload, uint16_t len);
|
||||||
void handleFileSave(const uint8_t* payload, uint16_t len);
|
void handleFileSave(const uint8_t* payload, uint16_t len);
|
||||||
void handleFileDelete(const uint8_t* payload, uint16_t len);
|
void handleFileDelete(const uint8_t* payload, uint16_t len);
|
||||||
void handleFilePlay(const uint8_t* payload, uint16_t len);
|
void handleFilePlay(const uint8_t* payload, uint16_t len);
|
||||||
|
void handleFileStop(const uint8_t* payload, uint16_t len);
|
||||||
|
|
||||||
// Motor Control
|
// Motor Control
|
||||||
void handleMotorSet(const uint8_t* payload, uint16_t len);
|
void handleMotorSet(const uint8_t* payload, uint16_t len);
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ namespace Tag {
|
||||||
constexpr char FSAVE[4] = {'F','S','A','V'}; // File save
|
constexpr char FSAVE[4] = {'F','S','A','V'}; // File save
|
||||||
constexpr char FDELE[4] = {'F','D','E','L'}; // File delete
|
constexpr char FDELE[4] = {'F','D','E','L'}; // File delete
|
||||||
constexpr char FPLAY[4] = {'F','P','L','Y'}; // Play animation file
|
constexpr char FPLAY[4] = {'F','P','L','Y'}; // Play animation file
|
||||||
|
constexpr char FSTP[4] = {'F','S','T','P'}; // Stop animation
|
||||||
|
|
||||||
// Motor Control
|
// Motor Control
|
||||||
constexpr char MSET[4] = {'M','S','E','T'}; // Set motor positions
|
constexpr char MSET[4] = {'M','S','E','T'}; // Set motor positions
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool adxlStreamEnabled = true;
|
bool adxlStreamEnabled = true;
|
||||||
bool radarStreamEnabled = false;
|
bool radarStreamEnabled = true;
|
||||||
uint16_t adxlInterval = 10;
|
uint16_t adxlInterval = 10;
|
||||||
uint16_t radarInterval = 10;
|
uint16_t radarInterval = 10;
|
||||||
unsigned long lastADXLSend = 0;
|
unsigned long lastADXLSend = 0;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue