Added animation test framework

This framework provides the same animation interface as the ESP
firmware, but sends commands to the ESP via UDP. Therefore animations
can be tested without re-flashing the firmware for every tiny tuning
change.
This commit is contained in:
Thomas Kolb 2019-12-24 17:32:15 +01:00
parent ae2eb2a25f
commit f28de024d8
5 changed files with 316 additions and 0 deletions

View file

@ -0,0 +1,15 @@
project(animation_test)
cmake_minimum_required(VERSION 3.5)
set(SOURCES
src/Fader.cpp
src/Animation/ImageScrollerAnimation.cpp
src/main.cpp
src/Animation/Animation.h
src/Fader.h
src/Animation/ImageScrollerAnimation.h
)
add_executable(animation_test ${SOURCES})
target_include_directories(animation_test PUBLIC src)

View file

@ -0,0 +1,53 @@
#pragma once
#include <cstdint>
#include "Fader.h"
class Animation
{
public:
Animation(Fader *fader)
: m_fader(fader), m_running(true)
{}
/*!
* The animation's main function. Will be called once per frame.
*
* \param frame The current frame since the animation's start.
*/
virtual void loop(uint64_t frame) = 0;
/*!
* Stop the animation. After this function is called, the animation code
* might start some transition to black (all LEDs off).
*/
virtual void stop(void)
{
m_running = false;
}
/*!
* A function for checking wether the animation has stopped. This must
* return true after the stop transition has finished.
*
* \returns True if all activity of the animation code has stopped.
*/
virtual bool finished(void)
{
return !m_running;
}
/*!
* Reset function of the animation code. This should clear any
* internal state.
*/
virtual void reset(void)
{
m_running = true;
};
protected:
Fader *m_fader;
bool m_running;
};

View file

@ -0,0 +1,115 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <errno.h>
#include <string.h>
#include "Fader.h"
Fader::Fader(std::size_t nstrips, std::size_t nmodules_per_strip)
: m_strips(nstrips),
m_modulesPerStrip(nmodules_per_strip)
{
m_fd = socket(AF_INET, SOCK_DGRAM, 0);
}
void Fader::set_color(uint32_t strip, uint32_t module, const Fader::Color &color)
{
m_commands.emplace_back(Command{
SET_COLOUR,
static_cast<uint8_t>(strip),
static_cast<uint8_t>(module),
color
});
}
void Fader::add_color(uint32_t strip, uint32_t module, const Fader::Color &color)
{
m_commands.emplace_back(Command{
ADD_COLOUR,
static_cast<uint8_t>(strip),
static_cast<uint8_t>(module),
color
});
}
void Fader::fade_color(uint32_t strip, uint32_t module, const Fader::Color &color)
{
m_commands.emplace_back(Command{
FADE_COLOUR,
static_cast<uint8_t>(strip),
static_cast<uint8_t>(module),
color
});
}
void Fader::set_color(const Fader::Color &color)
{
for(std::size_t strip = 0; strip < m_strips; strip++) {
for(std::size_t module = 0; module < m_modulesPerStrip; module++) {
set_color(strip, module, color);
}
}
}
void Fader::add_color(const Fader::Color &color)
{
for(std::size_t strip = 0; strip < m_strips; strip++) {
for(std::size_t module = 0; module < m_modulesPerStrip; module++) {
add_color(strip, module, color);
}
}
}
void Fader::fade_color(const Fader::Color &color)
{
for(std::size_t strip = 0; strip < m_strips; strip++) {
for(std::size_t module = 0; module < m_modulesPerStrip; module++) {
fade_color(strip, module, color);
}
}
}
void Fader::set_fadestep(uint8_t newFadestep)
{
m_commands.emplace_back(Command{
SET_FADESTEP,
0,
0,
Color{newFadestep, 0, 0, 0}
});
}
void Fader::update(void)
{
// TODO: convert the m_commands vector to an UDP packet and send it
std::vector<uint8_t> packet;
for(auto &command : m_commands) {
packet.push_back(command.action);
packet.push_back(command.strip);
packet.push_back(command.module);
packet.push_back(command.color.r);
packet.push_back(command.color.g);
packet.push_back(command.color.b);
packet.push_back(command.color.w);
}
sockaddr_in target_addr;
target_addr.sin_family = AF_INET;
target_addr.sin_port = htons(2703);
target_addr.sin_addr.s_addr = inet_addr("10.42.7.145");
int ret = sendto(m_fd, packet.data(), packet.size(), 0, reinterpret_cast<const sockaddr*>(&target_addr), sizeof(target_addr));
if(ret == -1) {
std::cerr << "sendto failed: " << strerror(errno) << std::endl;
}
m_commands.clear();
}

View file

@ -0,0 +1,82 @@
#pragma once
#include <cstdint>
#include <vector>
class Fader
{
public:
struct Color {
int16_t r, g, b, w;
Color(int16_t ir = 0, int16_t ig = 0, int16_t ib = 0, int16_t iw = 0) : r(ir), g(ig), b(ib), w(iw) {}
void operator += (const Color &color)
{
this->r += color.r;
this->g += color.g;
this->b += color.b;
this->w += color.w;
}
void normalize(void)
{
if(r > 255) { r = 255; } else if (r < 0) { r = 0; };
if(g > 255) { g = 255; } else if (g < 0) { g = 0; };
if(b > 255) { b = 255; } else if (b < 0) { b = 0; };
if(w > 255) { w = 255; } else if (w < 0) { w = 0; };
}
void gamma_correct(void)
{
r = r * r / 255;
g = g * g / 255;
b = b * b / 255;
w = w * w / 255;
}
};
Fader(std::size_t nstrips, std::size_t nmodules_per_strip);
void set_color(uint32_t strip, uint32_t module, const Color &color);
void fade_color(uint32_t strip, uint32_t module, const Color &color);
void add_color(uint32_t strip, uint32_t module, const Color &color);
void set_color(const Color &color); // for all LEDs
void fade_color(const Color &color); // for all LEDs
void add_color(const Color &color); // for all LEDs
void set_fadestep(uint8_t newFadestep);
void update(void);
/*!
* \returns Whether any color value has changed since the last call to getColorValues().
*/
bool something_changed(void) const {return !m_commands.empty(); };
std::size_t modules_per_strip(void) { return m_modulesPerStrip; }
std::size_t strips(void) { return m_strips; }
private:
enum Action {
SET_COLOUR = 0,
FADE_COLOUR = 1,
ADD_COLOUR = 2,
SET_FADESTEP = 3
};
struct Command {
enum Action action;
uint8_t strip;
uint8_t module;
Color color;
};
int m_fd;
std::size_t m_strips;
std::size_t m_modulesPerStrip;
std::vector<Command> m_commands;
};

View file

@ -0,0 +1,51 @@
#include <iostream>
#include <unistd.h>
#include "Fader.h"
#include "Animation/ImageScrollerAnimation.h"
int main(void)
{
Fader fader(8, 16);
ImageScrollerAnimation::Image img(12, 3);
img.pixel( 0 + 1, 0).r = 48;
img.pixel( 0 + 2, 1).r = 48;
img.pixel( 0 + 0, 2).r = 48;
img.pixel( 0 + 1, 2).r = 48;
img.pixel( 0 + 2, 2).r = 48;
img.pixel( 3 + 1, 0).g = 48;
img.pixel( 3 + 2, 1).g = 48;
img.pixel( 3 + 0, 2).g = 48;
img.pixel( 3 + 1, 2).g = 48;
img.pixel( 3 + 2, 2).g = 48;
img.pixel( 6 + 1, 0).b = 48;
img.pixel( 6 + 2, 1).b = 48;
img.pixel( 6 + 0, 2).b = 48;
img.pixel( 6 + 1, 2).b = 48;
img.pixel( 6 + 2, 2).b = 48;
img.pixel( 9 + 1, 0).w = 48;
img.pixel( 9 + 2, 1).w = 48;
img.pixel( 9 + 0, 2).w = 48;
img.pixel( 9 + 1, 2).w = 48;
img.pixel( 9 + 2, 2).w = 48;
Animation *animation = new ImageScrollerAnimation(&fader, &img);
for(uint64_t frame = 0; !animation->finished(); frame++) {
animation->loop(frame);
if(fader.something_changed()) {
fader.update();
}
if(frame == 3000) {
animation->stop();
}
usleep(100000);
};
delete animation;
}