Skip to content

Commit a993fb7

Browse files
committed
Merge branch 'release/1.1.0'
2 parents 2cfb2eb + efc4d2c commit a993fb7

File tree

15 files changed

+313
-113
lines changed

15 files changed

+313
-113
lines changed

.github/workflows/ci.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ jobs:
3232
run: |
3333
find src test -regex '.*\.\(cpp\|h\)' -exec clang-format-13 --dry-run --Werror {} \;
3434
find examples -regex '.*\.ino' -exec clang-format-13 --dry-run --Werror {} \;
35-
- uses: arduino/arduino-lint-action@v1
35+
- name: Lint with arduino-lint
36+
uses: arduino/arduino-lint-action@v1
37+
with:
38+
library-manager: update
3639
test:
3740
name: "Test"
3841
runs-on: ubuntu-latest

README.md

+12-10
Original file line numberDiff line numberDiff line change
@@ -153,24 +153,26 @@ void loop() {
153153

154154
At first, an animation will be in the default mode. In this mode, the animation is simply not doing anything and waits until the mode has changed.
155155

156-
| Mode | Constant | Description |
157-
|------|----------|-------------|
158-
| default | MODE_DEFAULT | Not playing / waiting |
159-
| play | MODE_PLAY | Playing back the animation |
160-
| pause | MODE_PAUSE | Pausing the animation at the current frame |
161-
| stop | MODE_STOP | Slowly moving the servos to their neutral position |
162-
| live | MODE_LIVE | Reading serial commands to move the servos in real-time |
156+
| Constant | Method | Description |
157+
|----------|--------|-------------|
158+
| MODE_DEFAULT | | Not playing / waiting |
159+
| MODE_PLAY | play() | Start or resume playing the animation once |
160+
| MODE_PAUSE | pause() | Pausing the animation at the current frame |
161+
| MODE_STOP | stop() | Slowly moving the servos to their neutral position |
162+
| MODE_LOOP | loop() | Start or resume playing the animation in a loop |
163+
| MODE_LIVE | live(stream) | Reading serial commands to move the servos in real-time |
163164

164-
The modes can be changed or triggered via the following methods:
165+
The modes can be changed or triggered by calling the above methods on the animation object:
165166

166167
```ino
167168
myBlenderAnimation.play();
168169
myBlenderAnimation.pause();
169-
myBlenderAnimation.stop(stepDelay);
170+
myBlenderAnimation.loop();
171+
myBlenderAnimation.stop();
170172
myBlenderAnimation.live(stream);
171173
```
172174

173-
> Note: the default mode is only handled internally.
175+
> Note: the default mode can not be triggered as it is only handled internally.
174176
175177
When calling the `stop` method, it is possible to pass a delay in milliseconds. This delay will be used when the servos are moved to their neutral position during the stop mode. To get to the neutral position, the current position will either be increased or decreased by 1. The delay therefore controls how fast or smooth this movement will take place. The default value for this parameter is `20`.
176178

examples/AdafruitPCA9685/AdafruitPCA9685.ino

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ void setup() {
4040
animation.addServo(neckLeftServo);
4141
animation.addServo(neckRightServo);
4242

43-
// Trigger the animation play mode
44-
animation.play();
43+
// Trigger the animation loop mode
44+
animation.loop();
4545

4646
// Initialize servo driver
4747
pwm.begin();

examples/MultiplePCA9685/MultiplePCA9685.ino

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ void setup() {
7373
animation.addServo(servoMaps[i].servo);
7474
}
7575

76-
// Trigger the animation play mode
77-
animation.play();
76+
// Trigger the animation loop mode
77+
animation.loop();
7878

7979
// Initialize servo drivers
8080
pwmA.begin();

examples/StandardServoLib/StandardServoLib.ino

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ void setup() {
3838
// Add the Blender servo object to the animation
3939
animation.addServo(myBlenderServo);
4040

41-
// Trigger the animation play mode
42-
animation.play();
41+
// Trigger the animation loop mode
42+
animation.loop();
4343
}
4444

4545
void loop() {

examples/SwitchModeButton/SwitchModeButton.ino

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
changes. For example, this can be used to control the animation audio. See the
1111
comments below to understand what happens on a short or long button press
1212
based on the current mode.
13+
14+
Starting the animation will only play it once, so another button press is
15+
required to play it again. Alternatively, you can also use MODE_LOOP instead
16+
of MODE_PLAY and loop() instead of play(). This will keep the animation
17+
running until the button is short or long pressed again, thus pausing or
18+
stopping the animation.
1319
*/
1420

1521
#include "simple.h"

library.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name=BlenderServoAnimation
2-
version=1.0.0
2+
version=1.1.0
33
author=Tim Hendriks
44
maintainer=Tim Hendriks <[email protected]>
55
sentence=Library to control servos based on an exported Blender animation.

src/BlenderServoAnimation.cpp

+15-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ void Animation::changeMode(byte mode) {
4141
void Animation::run(unsigned long currentMicros) {
4242
switch (mode) {
4343
case MODE_PLAY:
44+
case MODE_LOOP:
4445
this->handlePlayMode(currentMicros);
4546
break;
4647
case MODE_STOP:
@@ -74,7 +75,15 @@ void Animation::handlePlayMode(unsigned long currentMicros) {
7475
Servo *servo = this->servos[i];
7576

7677
if (servo && servo->hasPositions()) {
77-
servo->moveByStep(this->frame);
78+
servo->moveByFrame(this->frame);
79+
}
80+
}
81+
82+
if (this->frame == 0) {
83+
if (this->mode == Animation::MODE_LOOP) {
84+
this->changeMode(Animation::MODE_LOOP);
85+
} else {
86+
this->changeMode(Animation::MODE_DEFAULT);
7887
}
7988
}
8089
}
@@ -132,6 +141,11 @@ void Animation::pause() {
132141
this->changeMode(Animation::MODE_PAUSE);
133142
}
134143

144+
void Animation::loop() {
145+
this->lastMicros = micros();
146+
this->changeMode(Animation::MODE_LOOP);
147+
}
148+
135149
void Animation::stop(byte stepDelay) {
136150
this->stopStepDelay = stepDelay;
137151
this->frame = 0;

src/BlenderServoAnimation.h

+6-4
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ class Animation {
3737

3838
public:
3939
static const byte MODE_DEFAULT = 0;
40-
static const byte MODE_PAUSE = 1;
41-
static const byte MODE_PLAY = 2;
42-
static const byte MODE_STOP = 3;
43-
static const byte MODE_LIVE = 4;
40+
static const byte MODE_PLAY = 1;
41+
static const byte MODE_PAUSE = 2;
42+
static const byte MODE_LOOP = 3;
43+
static const byte MODE_STOP = 4;
44+
static const byte MODE_LIVE = 5;
4445

4546
Animation();
4647
Animation(byte fps, int frames);
@@ -50,6 +51,7 @@ class Animation {
5051
void run(unsigned long currentMicros = micros());
5152
void play();
5253
void pause();
54+
void loop();
5355
void stop(byte stepDelay = 20);
5456
void live(Stream &serial);
5557
byte getMode();

src/servo/Servo.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ void Servo::move(int position, bool force) {
3838
this->currentPosition = position;
3939
}
4040

41-
void Servo::moveByStep(int step) {
42-
int newPosition = pgm_read_word_near(this->positions + step);
41+
void Servo::moveByFrame(int frame) {
42+
int newPosition = pgm_read_word_near(this->positions + frame);
4343
this->move(newPosition);
4444
}
4545

src/servo/Servo.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Servo {
2222
Servo(byte id, const int positions[], cb moveCallback, byte threshold = 20);
2323
Servo(byte id, cb moveCallback, byte threshold = 20);
2424
void move(int position, bool force = false);
25-
void moveByStep(int step);
25+
void moveByFrame(int frame);
2626
void moveTowardsNeutral(bool inSteps = true);
2727
bool isNeutral();
2828
bool hasPositions();
+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#include "BlenderServoAnimation.h"
2+
#include "../../SerialMock.h"
3+
#include <unity.h>
4+
5+
using namespace BlenderServoAnimation;
6+
7+
#define FPS 60
8+
#define FRAMES 5
9+
10+
struct positionLog {
11+
int index;
12+
int positions[20];
13+
};
14+
15+
positionLog lastPositions[16];
16+
17+
void setUp(void) {
18+
for (int id = 0; id < 16; id++) {
19+
lastPositions[id].index = 0;
20+
21+
for (int i = 0; i < 20; i++) {
22+
lastPositions[id].positions[i] = 0;
23+
}
24+
}
25+
}
26+
27+
void move(byte servoID, int position) {
28+
int index = lastPositions[servoID].index;
29+
lastPositions[servoID].positions[index] = position;
30+
lastPositions[servoID].index++;
31+
}
32+
33+
const int positions[5] PROGMEM = {350, 340, 330, 340, 330};
34+
35+
void test_multiple_servos(void) {
36+
Animation animation;
37+
SerialMock mock;
38+
Servo servos[] = {
39+
Servo(0, positions, move),
40+
Servo(1, move),
41+
};
42+
animation.addServos(servos, 2);
43+
animation.live(mock);
44+
TEST_ASSERT_EQUAL(Animation::MODE_LIVE, animation.getMode());
45+
46+
byte values[20] = {60, 0, 1, 94, 62, 60, 1, 1, 94, 62,
47+
60, 0, 1, 99, 62, 60, 1, 1, 99, 62};
48+
49+
for (int i = 0; i < 10; i++) {
50+
mock.write(values[i]);
51+
}
52+
53+
animation.run();
54+
55+
TEST_ASSERT_EQUAL(0, lastPositions[0].positions[0]);
56+
TEST_ASSERT_EQUAL(350, lastPositions[1].positions[0]);
57+
58+
for (int i = 10; i < 20; i++) {
59+
mock.write(values[i]);
60+
}
61+
62+
animation.run();
63+
64+
TEST_ASSERT_EQUAL(355, lastPositions[0].positions[0]);
65+
TEST_ASSERT_EQUAL(355, lastPositions[1].positions[1]);
66+
}
67+
68+
void test_skip(void) {
69+
Animation animation;
70+
SerialMock mock;
71+
Servo servo(0, move);
72+
animation.addServo(servo);
73+
animation.live(mock);
74+
TEST_ASSERT_EQUAL(Animation::MODE_LIVE, animation.getMode());
75+
76+
byte values[15] = {60, 0, 1, 94, 62, 60, 0, 1, 99, 0, 60, 0, 1, 104, 62};
77+
78+
for (int i = 0; i < 15; i++) {
79+
mock.write(values[i]);
80+
}
81+
82+
animation.run();
83+
84+
TEST_ASSERT_EQUAL(350, lastPositions[0].positions[0]);
85+
TEST_ASSERT_EQUAL(360, lastPositions[0].positions[1]);
86+
}
87+
88+
int main(int argc, char **argv) {
89+
UNITY_BEGIN();
90+
RUN_TEST(test_multiple_servos);
91+
RUN_TEST(test_skip);
92+
UNITY_END();
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#include "BlenderServoAnimation.h"
2+
#include "../../SerialMock.h"
3+
#include <unity.h>
4+
5+
using namespace BlenderServoAnimation;
6+
7+
#define FPS 60
8+
#define FRAMES 5
9+
10+
struct modeLog {
11+
byte prevMode;
12+
byte newMode;
13+
};
14+
15+
byte modeIndex = 0;
16+
modeLog lastModes[10];
17+
18+
void setUp(void) {
19+
for (int i = 0; i < 10; i++) {
20+
lastModes[i].prevMode = 0;
21+
lastModes[i].newMode = 0;
22+
}
23+
24+
modeIndex = 0;
25+
}
26+
27+
void onModeChange(byte prevMode, byte newMode) {
28+
lastModes[modeIndex].prevMode = prevMode;
29+
lastModes[modeIndex].newMode = newMode;
30+
modeIndex++;
31+
}
32+
33+
void test_different_mode(void) {
34+
Animation animation(FPS, FRAMES);
35+
36+
animation.onModeChange(onModeChange);
37+
38+
animation.play();
39+
TEST_ASSERT_EQUAL(Animation::MODE_DEFAULT, lastModes[0].prevMode);
40+
TEST_ASSERT_EQUAL(Animation::MODE_PLAY, lastModes[0].newMode);
41+
animation.pause();
42+
TEST_ASSERT_EQUAL(Animation::MODE_PLAY, lastModes[1].prevMode);
43+
TEST_ASSERT_EQUAL(Animation::MODE_PAUSE, lastModes[1].newMode);
44+
}
45+
46+
void test_same_mode(void) {
47+
Animation animation(FPS, FRAMES);
48+
49+
animation.onModeChange(onModeChange);
50+
51+
animation.loop();
52+
TEST_ASSERT_EQUAL(Animation::MODE_DEFAULT, lastModes[0].prevMode);
53+
TEST_ASSERT_EQUAL(Animation::MODE_LOOP, lastModes[0].newMode);
54+
animation.loop();
55+
TEST_ASSERT_EQUAL(Animation::MODE_LOOP, lastModes[1].prevMode);
56+
TEST_ASSERT_EQUAL(Animation::MODE_LOOP, lastModes[1].newMode);
57+
}
58+
59+
void test_all_modes(void) {
60+
SerialMock mock;
61+
Animation animation(FPS, FRAMES);
62+
63+
animation.onModeChange(onModeChange);
64+
65+
animation.play();
66+
TEST_ASSERT_EQUAL(Animation::MODE_DEFAULT, lastModes[0].prevMode);
67+
TEST_ASSERT_EQUAL(Animation::MODE_PLAY, lastModes[0].newMode);
68+
animation.pause();
69+
TEST_ASSERT_EQUAL(Animation::MODE_PLAY, lastModes[1].prevMode);
70+
TEST_ASSERT_EQUAL(Animation::MODE_PAUSE, lastModes[1].newMode);
71+
animation.loop();
72+
TEST_ASSERT_EQUAL(Animation::MODE_PAUSE, lastModes[2].prevMode);
73+
TEST_ASSERT_EQUAL(Animation::MODE_LOOP, lastModes[2].newMode);
74+
animation.stop();
75+
TEST_ASSERT_EQUAL(Animation::MODE_LOOP, lastModes[3].prevMode);
76+
TEST_ASSERT_EQUAL(Animation::MODE_STOP, lastModes[3].newMode);
77+
animation.run();
78+
TEST_ASSERT_EQUAL(Animation::MODE_STOP, lastModes[4].prevMode);
79+
TEST_ASSERT_EQUAL(Animation::MODE_DEFAULT, lastModes[4].newMode);
80+
animation.live(mock);
81+
TEST_ASSERT_EQUAL(Animation::MODE_DEFAULT, lastModes[5].prevMode);
82+
TEST_ASSERT_EQUAL(Animation::MODE_LIVE, lastModes[5].newMode);
83+
}
84+
85+
int main(int argc, char **argv) {
86+
UNITY_BEGIN();
87+
RUN_TEST(test_different_mode);
88+
RUN_TEST(test_same_mode);
89+
RUN_TEST(test_all_modes);
90+
UNITY_END();
91+
}

0 commit comments

Comments
 (0)