Merge pull request #10 from Block-Party-VR/5-create-more-eye-catching-idle-animations

Refactoring the board tracking code and adding new functionality to make it easy to make animations for the board
This commit is contained in:
Quinn
2024-08-26 18:50:51 -04:00
committed by GitHub
19 changed files with 1567 additions and 441 deletions

View File

@@ -4,32 +4,67 @@
*/ */
#pragma once #pragma once
#include <cstdint>
#include <array>
#include "PINOUT.h" #include "PINOUT.h"
#include "CubeStack.h" #include "BoardTypes.h"
#include "Vector3D.h"
// define some important buffer sizes
static constexpr uint32_t SERIAL_ARG_LENGTH{15};
static constexpr uint32_t SERIAL_CHAR_LENGTH{SERIAL_ARG_LENGTH*10};
// define the physical dimensions of the board // define the physical dimensions of the board
#define BOARD_WIDTH 3 static constexpr uint32_t BOARD_WIDTH{3};
#define BOARD_LENGTH 3 static constexpr uint32_t BOARD_LENGTH{3};
#define BOARD_HEIGHT 3 static constexpr uint32_t BOARD_HEIGHT{3};
static constexpr V3D<uint32_t> BOARD_DIMENSIONS{BOARD_WIDTH, BOARD_LENGTH, BOARD_HEIGHT};
// define the number of stacks // define the number of stacks
#define NUMBER_STACKS BOARD_WIDTH * BOARD_LENGTH static constexpr uint32_t NUMBER_STACKS{BOARD_WIDTH * BOARD_LENGTH};
// define the CubeStacks // define the CubeStacks
CubeStack stack1(STACK1_ADC_PIN, STACK1_LED_PIN, BOARD_HEIGHT); static BOARD_TYPES::CubeStack stack1{
CubeStack stack2(STACK2_ADC_PIN, STACK2_LED_PIN, BOARD_HEIGHT); .adcPin=STACK1_ADC_PIN,
CubeStack stack3(STACK3_ADC_PIN, STACK3_LED_PIN, BOARD_HEIGHT); .ledPin=STACK1_LED_PIN
CubeStack stack4(STACK4_ADC_PIN, STACK4_LED_PIN, BOARD_HEIGHT); };
CubeStack stack5(STACK5_ADC_PIN, STACK5_LED_PIN, BOARD_HEIGHT); static BOARD_TYPES::CubeStack stack2{
CubeStack stack6(STACK6_ADC_PIN, STACK6_LED_PIN, BOARD_HEIGHT); .adcPin=STACK2_ADC_PIN,
CubeStack stack7(STACK7_ADC_PIN, STACK7_LED_PIN, BOARD_HEIGHT); .ledPin=STACK2_LED_PIN
CubeStack stack8(STACK8_ADC_PIN, STACK8_LED_PIN, BOARD_HEIGHT); };
CubeStack stack9(STACK9_ADC_PIN, STACK9_LED_PIN, BOARD_HEIGHT); static BOARD_TYPES::CubeStack stack3{
.adcPin=STACK3_ADC_PIN,
// define the array of stacks .ledPin=STACK3_LED_PIN
CubeStack * stacks[] = { };
&stack1, &stack2, &stack3, static BOARD_TYPES::CubeStack stack4{
&stack4, &stack5, &stack6, .adcPin=STACK4_ADC_PIN,
&stack7, &stack8, &stack9 .ledPin=STACK4_LED_PIN
};
static BOARD_TYPES::CubeStack stack5{
.adcPin=STACK5_ADC_PIN,
.ledPin=STACK5_LED_PIN
};
static BOARD_TYPES::CubeStack stack6{
.adcPin=STACK6_ADC_PIN,
.ledPin=STACK6_LED_PIN
};
static BOARD_TYPES::CubeStack stack7{
.adcPin=STACK7_ADC_PIN,
.ledPin=STACK7_LED_PIN
};
static BOARD_TYPES::CubeStack stack8{
.adcPin=STACK8_ADC_PIN,
.ledPin=STACK8_LED_PIN
};
static BOARD_TYPES::CubeStack stack9{
.adcPin=STACK9_ADC_PIN,
.ledPin=STACK9_LED_PIN
};
static std::array<BOARD_TYPES::CubeStack, NUMBER_STACKS> stacks{
stack1, stack2, stack3,
stack4, stack5, stack6,
stack7, stack8, stack9
}; };

View File

@@ -5,45 +5,47 @@
#pragma once #pragma once
#include <cstdint>
// Stack pins // Stack pins
// Stack 1 pins // Stack 1 pins
#define STACK1_ADC_PIN 6 static constexpr uint8_t STACK1_ADC_PIN{6};
#define STACK1_LED_PIN 11 static constexpr uint8_t STACK1_LED_PIN{11};
// Stack 2 pins // Stack 2 pins
#define STACK2_ADC_PIN 16 static constexpr uint8_t STACK2_ADC_PIN{16};
#define STACK2_LED_PIN 14 static constexpr uint8_t STACK2_LED_PIN{14};
// Stack 3 pins // Stack 3 pins
#define STACK3_ADC_PIN 8 static constexpr uint8_t STACK3_ADC_PIN{8};
#define STACK3_LED_PIN 45 static constexpr uint8_t STACK3_LED_PIN{45};
// Stack 4 pins // Stack 4 pins
#define STACK4_ADC_PIN 5 static constexpr uint8_t STACK4_ADC_PIN{5};
#define STACK4_LED_PIN 10 static constexpr uint8_t STACK4_LED_PIN{10};
// Stack 5 pins // Stack 5 pins
#define STACK5_ADC_PIN 15 static constexpr uint8_t STACK5_ADC_PIN{15};
#define STACK5_LED_PIN 13 static constexpr uint8_t STACK5_LED_PIN{13};
// Stack 6 pins // Stack 6 pins
#define STACK6_ADC_PIN 18 static constexpr uint8_t STACK6_ADC_PIN{18};
#define STACK6_LED_PIN 38 static constexpr uint8_t STACK6_LED_PIN{38};
// Stack 7 pins // Stack 7 pins
#define STACK7_ADC_PIN 4 static constexpr uint8_t STACK7_ADC_PIN{4};
#define STACK7_LED_PIN 9 static constexpr uint8_t STACK7_LED_PIN{9};
// Stack 8 pins // Stack 8 pins
#define STACK8_ADC_PIN 7 static constexpr uint8_t STACK8_ADC_PIN{7};
#define STACK8_LED_PIN 12 static constexpr uint8_t STACK8_LED_PIN{12};
// Stack 9 pins // Stack 9 pins
#define STACK9_ADC_PIN 17 static constexpr uint8_t STACK9_ADC_PIN{17};
#define STACK9_LED_PIN 37 static constexpr uint8_t STACK9_LED_PIN{37};
// Bluetooth module configuration pins // Bluetooth module configuration pins
#define BT_STATE_PIN 2 static constexpr uint8_t BT_STATE_PIN{2};
#define BT_EN_PIN 3 static constexpr uint8_t BT_EN_PIN{3};

80
include/Vector3D.h Normal file
View File

@@ -0,0 +1,80 @@
#pragma once
#include <cstdint>
#include <cmath>
template <typename Type>
class V3D{
public:
constexpr V3D(const V3D& other):
x(other.x),
y(other.y),
z(other.z){
static_assert(std::is_arithmetic<Type>::value, "Type must be a number");
}
constexpr V3D(Type x=0, Type y=0, Type z=0):
x(x),
y(y),
z(z){
static_assert(std::is_arithmetic<Type>::value, "Type must be a number");
}
template <typename OtherType>
constexpr V3D(const V3D<OtherType> other):
x(static_cast<Type>(other.x)),
y(static_cast<Type>(other.y)),
z(static_cast<Type>(other.z)){
static_assert(std::is_arithmetic<Type>::value, "Type must be a number");
static_assert(std::is_arithmetic<OtherType>::value, "OtherType must be a number");
}
V3D& operator=(const V3D &other){
this->x = other.x;
this->y = other.y;
this->z = other.z;
return *this;
}
V3D& operator+=(const V3D &other){
this->x += other.x;
this->y += other.y;
this->z += other.z;
return *this;
}
V3D& operator-=(const V3D &other){
this->x -= other.x;
this->y -= other.y;
this->z -= other.z;
return *this;
}
V3D& operator/=(const Type scalar){
if(scalar == 0){
return *this;
}
this->x /= scalar;
this->y /= scalar;
this->z /= scalar;
return *this;
}
V3D& operator*=(const Type scalar){
this->x *= scalar;
this->y *= scalar;
this->z *= scalar;
return *this;
}
bool operator==(const V3D &other){
return this->x == other.x && this->y == other.y && this->z == other.z;
}
float magnitude(){
return std::sqrt(static_cast<float>(this->x * this->x + this->y * this->y + this->z * this->z));
}
Type x;
Type y;
Type z;
};

221
lib/Animator/Animation.h Normal file
View File

@@ -0,0 +1,221 @@
#pragma once
#include "AnimationTypes.h"
#include "Vector3D.h"
#include "Animator.h"
using namespace ANIMATION_TYPES;
namespace AnimationHelpers{
V3D<uint8_t> red{255,0,0};
V3D<uint8_t> green{0,255,0};
V3D<uint8_t> blue{0,0,255};
V3D<uint8_t> cyan{0,255,255};
V3D<uint8_t> magenta{255,0,255};
Cell CreateCell(float x_percent, float y_percent, float z_percent, V3D<uint8_t> &color){
float continuousMaxValue{static_cast<float>(std::numeric_limits<uint32_t>::max())};
Cell cell{
.position = V3D<uint32_t>{
static_cast<uint32_t>(continuousMaxValue*x_percent),
static_cast<uint32_t>(continuousMaxValue*y_percent),
static_cast<uint32_t>(continuousMaxValue*z_percent)
},
.color = color
};
return cell;
}
}
namespace RotatingCubes{
using namespace AnimationHelpers;
AnimationFrame frame1{
.frame = {
CreateCell(0,0,0,red),
CreateCell(1,0.5,0,green),
CreateCell(0,1,0,blue)
},
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(1000)
};
AnimationFrame frame2{
.frame = {
CreateCell(0,0.5,0,red),
CreateCell(1,0,0,green),
CreateCell(0.5,1,0,blue)
},
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(500)
};
AnimationFrame frame3{
.frame = {
CreateCell(0,1,0,red),
CreateCell(0.5,0,0,green),
CreateCell(1,1,0,blue)
},
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(1000)
};
AnimationFrame frame4{
.frame = {
CreateCell(0.5,1,0,red),
CreateCell(0,0,0,green),
CreateCell(1,0.5,0,blue)
},
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(500)
};
AnimationFrame frame5{
.frame = {
CreateCell(1,1,0,red),
CreateCell(0,0.5,0,green),
CreateCell(1,0,0,blue)
},
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(1000)
};
AnimationFrame frame6{
.frame = {
CreateCell(1,0.5,0,red),
CreateCell(0,1,0,green),
CreateCell(0.5,0,0,blue)
},
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(500)
};
AnimationFrame frame7{
.frame = {
CreateCell(1,0,0,red),
CreateCell(0.5,1,0,green),
CreateCell(0,0,0,blue)
},
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(1000)
};
AnimationFrame frame8{
.frame = {
CreateCell(0.5,0,0,red),
CreateCell(1,1,0,green),
CreateCell(0,0.5,0,blue)
},
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(500)
};
AnimationFrame frame9{
.frame = {
CreateCell(0,0,0,red),
CreateCell(1,0.5,0,green),
CreateCell(0,1,0,blue)
},
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(1)
};
std::vector<AnimationFrame> rotating{
frame1, // 0
frame2, // 1
frame3, // 2
frame4, // 3
frame5, // 4
frame6, // 5
frame7, // 6
frame8, // 7
frame9, // 8
};
}
namespace RisingCubes{
using namespace AnimationHelpers;
AnimationFrame frame1{
.frame = {
CreateCell(0,0,0,cyan),
CreateCell(0,1,0.5,green),
CreateCell(1,0,1,blue),
CreateCell(0.5,0.5,0.5,red),
CreateCell(1,1,0,magenta)
},
.fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(800)
};
AnimationFrame frame2{
.frame = {
CreateCell(0,0,0.5,cyan),
CreateCell(0,1,1,green),
CreateCell(1,0,0.5,blue),
CreateCell(0.5,0.5,0,red),
CreateCell(1,1,0.5,magenta)
},
.fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(800)
};
AnimationFrame frame3{
.frame = {
CreateCell(0,0,1,cyan),
CreateCell(0,1,0.5,green),
CreateCell(1,0,0,blue),
CreateCell(0.5,0.5,0.5,red),
CreateCell(1,1,1,magenta)
},
.fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(800)
};
AnimationFrame frame4{
.frame = {
CreateCell(0,0,0.5,cyan),
CreateCell(0,1,0,green),
CreateCell(1,0,0.5,blue),
CreateCell(0.5,0.5,1,red),
CreateCell(1,1,0.5,magenta)
},
.fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(800)
};
AnimationFrame frame5{
.frame = {
CreateCell(0,0,0,cyan),
CreateCell(0,1,0.5,green),
CreateCell(1,0,1,blue),
CreateCell(0.5,0.5,0.5,red),
CreateCell(1,1,0,magenta)
},
.fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(1)
};
std::vector<AnimationFrame> rising{
frame1, // 0
frame2, // 1
frame3, // 2
frame4, // 3
frame5
};
}

View File

@@ -0,0 +1,33 @@
#pragma once
#include "Vector3D.h"
#include <vector>
#include <chrono>
namespace ANIMATION_TYPES{
// for cube spots which aren't defined in a key frame,
// you can have the controller automatically interpolate a color
enum FillInterpolation{
NO_FILL, // if not specified, the cube color will be black
CLOSEST_COLOR, // The cube color will be the same color as the cube closest to it
LINEAR_WEIGHTED_DISTANCE, // the cube color will be an average of all specified cube colors weighted by the linear distance to this cube
SQUARE_WEIGHTED_DISTANCE // same as linear, but further colors have exponentially less impact on the color
};
enum FrameInterpolation{
SNAP, // After the delay, snap to the next key frame
FADE // over the course of the delay, fade to the next frame
};
struct Cell{
V3D<uint32_t> position;
V3D<uint32_t> color;
};
// this contains all of the information to specify exactly how a single frame should look and fade to the next frame
struct AnimationFrame{
std::vector<Cell> frame;
FillInterpolation fillInterpolation;
FrameInterpolation frameInterpolation;
std::chrono::milliseconds delay;
};
};

293
lib/Animator/Animator.h Normal file
View File

@@ -0,0 +1,293 @@
#pragma once
#include <cstdint>
#include <array>
#include <vector>
#include <chrono>
#include <cstring>
#include <cmath>
#include <climits>
#include "Vector3D.h"
#include "AnimationTypes.h"
using namespace ANIMATION_TYPES;
template <const V3D<uint32_t> &BOARD_DIMS>
class Animator{
public:
typedef std::array<std::array<std::array<V3D<uint32_t>, BOARD_DIMS.z>, BOARD_DIMS.y>, BOARD_DIMS.x> Frame;
void StartAnimation(const std::vector<AnimationFrame> *animationSequence);
void RunAnimation(const std::chrono::milliseconds& timePassed);
void SetLoop(bool isLooping);
Frame &GetInterpolatedFrame(){return this->interpolatedFrame;}
bool isEnabled{true};
bool interpolatedFrameHasChanged{false};
private:
bool isLooping{true};
// these are the uncompressed frames you get by following the key colors and interpolation instructions of an animation frame
Frame startFrame;
Frame interpolatedFrame;
Frame endFrame;
std::chrono::milliseconds timeElapsed;
const std::vector<AnimationFrame> *animationSequence;
uint32_t animationIndex{0};
void incrimentAnimationIndex();
void uncompressFrame(const AnimationFrame &keyFrame, Frame &frameBuffer);
void copyFrame(Frame &copyFrom, Frame &copyTo){
std::memcpy(&copyTo, &copyFrom, sizeof(Frame));
}
V3D<uint32_t> getInterpolatedColor(const AnimationFrame &keyFrame, V3D<uint32_t> position);
V3D<uint32_t> keyFrame2BoardCoords(const V3D<uint32_t> &keyFramePosition);
V3D<uint32_t> noFillInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position);
V3D<uint32_t> closestColorInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position);
V3D<uint32_t> linearInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position);
V3D<uint32_t> squareInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position);
void PrintUncompressedFrame(){
for(uint32_t x = 0; x < BOARD_DIMS.x; x++){
for(uint32_t y = 0; y < BOARD_DIMS.y; y++){
for(uint32_t z = 0; z < BOARD_DIMS.z; z++){
auto color = this->startFrame[x][y][z];
Serial.print("Cube X:" + String(x) + ",Y:" + String(y) + ",Z:" + String(z));
Serial.println("\tColor R:" + String(color.x) + ",G:" + String(color.y) + ",B:" + String(color.z));
}
}
}
}
};
template <const V3D<uint32_t> &BOARD_DIMS>
void Animator<BOARD_DIMS>::StartAnimation(const std::vector<AnimationFrame> *animationSequence){
this->animationSequence = animationSequence;
this->animationIndex = 0;
this->timeElapsed = std::chrono::milliseconds(0);
if(animationSequence->size() == 0){
return;
}
else if(animationSequence->size() == 1){
AnimationFrame frame{((*this->animationSequence)[0])};
this->uncompressFrame(frame, this->startFrame);
this->copyFrame(this->startFrame, this->interpolatedFrame);
this->copyFrame(this->startFrame, this->endFrame);
}
else{
this->uncompressFrame((*this->animationSequence)[0], this->startFrame);
this->copyFrame(this->startFrame, this->interpolatedFrame);
this->uncompressFrame((*this->animationSequence)[1], this->endFrame);
}
this->interpolatedFrameHasChanged = true;
}
template <const V3D<uint32_t> &BOARD_DIMS>
void Animator<BOARD_DIMS>::RunAnimation(const std::chrono::milliseconds& timePassed){
if(!(this->isEnabled)){
return;
}
auto delayTime = (*this->animationSequence)[this->animationIndex].delay;
this->timeElapsed += timePassed;
// load in the next frame if we're done with this transition
if(this->timeElapsed >= delayTime){
this->incrimentAnimationIndex();
}
// don't do frame interpolations if we're doing snap fades
if((*this->animationSequence)[this->animationIndex].frameInterpolation == FrameInterpolation::SNAP){
return;
}
// linearly interpolate between the two uncompressed frames
for(uint32_t x = 0; x < BOARD_DIMS.x; x++){
for(uint32_t y = 0; y < BOARD_DIMS.y; y++){
for(uint32_t z = 0; z < BOARD_DIMS.z; z++){
V3D<float> startColor{this->startFrame[x][y][z]};
V3D<float> endColor{this->endFrame[x][y][z]};
V3D<float> difference{endColor};
difference -= startColor;
V3D<float> interpolatedColor{difference};
interpolatedColor *= this->timeElapsed.count();
interpolatedColor /= delayTime.count();
interpolatedColor += startColor;
this->interpolatedFrame[x][y][z] = interpolatedColor;
}
}
}
this->interpolatedFrameHasChanged = true;
}
template <const V3D<uint32_t> &BOARD_DIMS>
void Animator<BOARD_DIMS>::SetLoop(bool isLooping){
this->isLooping = isLooping;
}
template <const V3D<uint32_t> &BOARD_DIMS>
void Animator<BOARD_DIMS>::incrimentAnimationIndex(){
if(this->animationIndex < this->animationSequence->size() - 2){
this->animationIndex++;
this->timeElapsed = std::chrono::milliseconds(0);
this->uncompressFrame((*this->animationSequence)[this->animationIndex], this->startFrame);
this->copyFrame(this->startFrame, this->interpolatedFrame);
this->uncompressFrame((*this->animationSequence)[this->animationIndex + 1], this->endFrame);
}
else{
this->StartAnimation(this->animationSequence);
}
this->interpolatedFrameHasChanged = true;
}
template <const V3D<uint32_t> &BOARD_DIMS>
void Animator<BOARD_DIMS>::uncompressFrame(const AnimationFrame &keyFrame, Frame &frameBuffer){
for(uint32_t x = 0; x < BOARD_DIMS.x; x++){
for(uint32_t y = 0; y < BOARD_DIMS.y; y++){
for(uint32_t z = 0; z < BOARD_DIMS.z; z++){
frameBuffer[x][y][z] = getInterpolatedColor(keyFrame, V3D<uint32_t>(x, y, z));
}
}
}
}
template <const V3D<uint32_t> &BOARD_DIMS>
V3D<uint32_t> Animator<BOARD_DIMS>::getInterpolatedColor(const AnimationFrame &keyFrame, V3D<uint32_t> position){
switch(keyFrame.fillInterpolation){
case FillInterpolation::NO_FILL:
return noFillInterpolate(keyFrame, position);
case FillInterpolation::CLOSEST_COLOR:
return closestColorInterpolate(keyFrame, position);
case FillInterpolation::LINEAR_WEIGHTED_DISTANCE:
return linearInterpolate(keyFrame, position);
case FillInterpolation::SQUARE_WEIGHTED_DISTANCE:
return squareInterpolate(keyFrame, position);
default:
V3D<uint32_t> black{};
return black;
}
}
template <const V3D<uint32_t> &BOARD_DIMS>
V3D<uint32_t> Animator<BOARD_DIMS>::keyFrame2BoardCoords(const V3D<uint32_t> &keyFramePosition){
V3D<uint32_t> returnValue{};
float maxValue{static_cast<float>(std::numeric_limits<uint32_t>::max())};
// scale the key frame values down to be within board coordinates
float keyFrame_X = static_cast<float>(BOARD_DIMS.x - 1) * static_cast<float>(keyFramePosition.x) / maxValue;
float keyFrame_Y = static_cast<float>(BOARD_DIMS.y - 1) * static_cast<float>(keyFramePosition.y) / maxValue;
float keyFrame_Z = static_cast<float>(BOARD_DIMS.z - 1) * static_cast<float>(keyFramePosition.z) / maxValue;
// carefully quantize the float values back into ints with a precise rounding operation
if(keyFrame_X - std::floor(keyFrame_X) < 0.5f){
returnValue.x = static_cast<uint32_t>(keyFrame_X);
}
else{
returnValue.x = static_cast<uint32_t>(keyFrame_X) + 1;
}
if(keyFrame_Y - std::floor(keyFrame_Y) < 0.5f){
returnValue.y = static_cast<uint32_t>(keyFrame_Y);
}
else{
returnValue.y = static_cast<uint32_t>(keyFrame_Y) + 1;
}
if(keyFrame_Z - std::floor(keyFrame_Z) < 0.5f){
returnValue.z = static_cast<uint32_t>(keyFrame_Z);
}
else{
returnValue.z = static_cast<uint32_t>(keyFrame_Z) + 1;
}
return returnValue;
}
template <const V3D<uint32_t> &BOARD_DIMS>
V3D<uint32_t> Animator<BOARD_DIMS>::noFillInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position){
V3D<uint32_t> returnColor{};
for(Cell cell : keyFrame.frame){
if(keyFrame2BoardCoords(cell.position) == position){
returnColor = cell.color;
}
}
return returnColor;
}
template <const V3D<uint32_t> &BOARD_DIMS>
V3D<uint32_t> Animator<BOARD_DIMS>::closestColorInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> cubePosition){
V3D<uint32_t> returnColor{keyFrame.frame[0].color};
V3D<uint32_t> distance{keyFrame.frame[0].position};
distance -= cubePosition;
float closestDistance = distance.magnitude();
for(Cell cell : keyFrame.frame){
distance = keyFrame2BoardCoords(cell.position);
distance -= cubePosition;
float euclidDistance = distance.magnitude();
if(euclidDistance < closestDistance){
returnColor = cell.color;
closestDistance = euclidDistance;
}
}
return returnColor;
}
template <const V3D<uint32_t> &BOARD_DIMS>
V3D<uint32_t> Animator<BOARD_DIMS>::linearInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position){
V3D<uint32_t> returnColor{};
for(Cell cell : keyFrame.frame){
V3D<uint32_t> vectorDistance{keyFrame2BoardCoords(cell.position)};
vectorDistance -= position;
float distance = vectorDistance.magnitude();
if(distance == 0) return cell.color;
returnColor += cell.color;
returnColor /= distance;
}
returnColor /= keyFrame.frame.size();
return returnColor;
}
template <const V3D<uint32_t> &BOARD_DIMS>
V3D<uint32_t> Animator<BOARD_DIMS>::squareInterpolate(const AnimationFrame &keyFrame, V3D<uint32_t> position){
V3D<uint32_t> returnColor{};
for(Cell cell : keyFrame.frame){
V3D<uint32_t> vectorDistance{keyFrame2BoardCoords(cell.position)};
vectorDistance -= position;
uint32_t distance = static_cast<uint32_t>(vectorDistance.magnitude());
distance *= distance;
if(distance == 0) return cell.color;
returnColor += cell.color;
returnColor /= distance;
}
returnColor /= keyFrame.frame.size();
return returnColor;
}

169
lib/Animator/TestFrames.h Normal file
View File

@@ -0,0 +1,169 @@
#pragma once
#include "AnimationTypes.h"
#include "Vector3D.h"
#include "Animator.h"
using namespace ANIMATION_TYPES;
namespace TestFrames{
V3D<uint32_t> red{255,0,0};
V3D<uint32_t> green{0,255,0};
V3D<uint32_t> blue{0,0,255};
uint32_t maxValue{std::numeric_limits<uint32_t>::max()};
Cell CreateCell(float x_percent, float y_percent, float z_percent, V3D<uint32_t> &color){
float continuousMaxValue{static_cast<float>(std::numeric_limits<uint32_t>::max())};
Cell cell{
.position = V3D<uint32_t>{
static_cast<uint32_t>(continuousMaxValue*x_percent),
static_cast<uint32_t>(continuousMaxValue*y_percent),
static_cast<uint32_t>(continuousMaxValue*z_percent)
},
.color = color
};
return cell;
}
AnimationFrame noFillFrame{
.frame = {
CreateCell(0,0,0,red),
CreateCell(0.5,0.5,0.5,green),
CreateCell(1,1,1,blue)
},
.fillInterpolation = FillInterpolation::NO_FILL,
.frameInterpolation = FrameInterpolation::SNAP,
.delay = std::chrono::milliseconds(10000)
};
AnimationFrame closestColorFrame{
.frame = {
CreateCell(0,0,0,red),
CreateCell(0.5,0.5,0.5,green),
CreateCell(1,1,1,blue)
},
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
.frameInterpolation = FrameInterpolation::SNAP,
.delay = std::chrono::milliseconds(10000)
};
AnimationFrame linearFillFrame{
.frame = {
CreateCell(0,0,0,red),
CreateCell(0.5,0.5,0.5,green),
CreateCell(1,1,1,blue)
},
.fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE,
.frameInterpolation = FrameInterpolation::SNAP,
.delay = std::chrono::milliseconds(10000)
};
AnimationFrame squareFillFrame{
.frame = {
CreateCell(0,0,0,red),
CreateCell(0.5,0.5,0.5,green),
CreateCell(1,1,1,blue)
},
.fillInterpolation = FillInterpolation::SQUARE_WEIGHTED_DISTANCE,
.frameInterpolation = FrameInterpolation::SNAP,
.delay = std::chrono::milliseconds(10000)
};
AnimationFrame noFillFadeFrame{
.frame = {
CreateCell(0,0,0,red),
CreateCell(0.5,0.5,0.5,green),
CreateCell(1,1,1,blue)
},
.fillInterpolation = FillInterpolation::NO_FILL,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(10000)
};
AnimationFrame closestColorFadeFrame{
.frame = {
CreateCell(0,0,0,red),
CreateCell(0.5,0.5,0.5,green),
CreateCell(1,1,1,blue)
},
.fillInterpolation = FillInterpolation::CLOSEST_COLOR,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(10000)
};
AnimationFrame linearFillFadeFrame{
.frame = {
CreateCell(0,0,0,red),
CreateCell(0.5,0.5,0.5,green),
CreateCell(1,1,1,blue)
},
.fillInterpolation = FillInterpolation::LINEAR_WEIGHTED_DISTANCE,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(10000)
};
AnimationFrame squareFillFadeFrame{
.frame = {
CreateCell(0,0,0,red),
CreateCell(0.5,0.5,0.5,green),
CreateCell(1,1,1,blue)
},
.fillInterpolation = FillInterpolation::SQUARE_WEIGHTED_DISTANCE,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(10000)
};
std::vector<AnimationFrame> testAnimationSequence2{
noFillFrame, // 0
closestColorFrame, // 1
linearFillFrame, // 2
squareFillFrame, // 3
noFillFadeFrame, // 4
closestColorFadeFrame, // 5
linearFillFadeFrame, // 6
squareFillFadeFrame, // 7
noFillFrame // 8
};
AnimationFrame testFrame1{
.frame = {
CreateCell(0,0,0,red),
// CreateCell(0.5,0.5,0,green),
CreateCell(1,1,0,blue)
},
.fillInterpolation = FillInterpolation::NO_FILL,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(10000)
};
AnimationFrame testFrame2{
.frame = {
CreateCell(0,1,0,red),
// CreateCell(0.5,0.5,0,green),
CreateCell(1,0,0,green)
},
.fillInterpolation = FillInterpolation::NO_FILL,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(10000)
};
AnimationFrame testFrame3{
.frame = {
CreateCell(0.5,0.5,0,red),
// CreateCell(0.5,0.5,0,green),
CreateCell(0,1,0,blue)
},
.fillInterpolation = FillInterpolation::NO_FILL,
.frameInterpolation = FrameInterpolation::FADE,
.delay = std::chrono::milliseconds(10000)
};
std::vector<AnimationFrame> testAnimationSequence1{
testFrame1,
testFrame2,
testFrame3,
testFrame1
};
}

205
lib/Board/Board.h Normal file
View File

@@ -0,0 +1,205 @@
/**
* @file Board.h
* @brief The Board class is meant to store data about each cube on the board and
* provide an easy way to organize and access that data in a 3-dimensional context.
*/
#pragma once
#include <cstdint>
#include <array>
#include <WString.h>
#include "BoardTypes.h"
#include "Vector3D.h"
template <const V3D<uint32_t> &BOARD_DIMS>
class Board{
public:
Board() = default;
~Board() = default;
constexpr const V3D<uint32_t> &GetSize() const{return BOARD_DIMS;}
constexpr uint32_t GetNumberCubes() const{return BOARD_DIMS.x * BOARD_DIMS.y * BOARD_DIMS.z;}
constexpr uint32_t GetMaxDimension(){return std::max(std::max(BOARD_DIMS.x, BOARD_DIMS.y), BOARD_DIMS.z);}
/**
* @brief Returns a string in the format:
* !a,b,c,d,e,f,g,h,i;
* Where each letter is the number of cubes in a given position's stack
* @param stringBuffer the buffer to write the string into
*/
void ToStackString(String& stringBuffer) const;
/**
* @brief fill the entire board with the given color
* @param color the color to fill the board with
*/
void FillColor(const V3D<uint32_t> &color);
/**
* @brief Set the color of the cube at the given position.
* Even if the cube is not occupied, it will remember this color
* and turn that color if it becomes occupied.
* @param position the position of the cube.
* @param color the color you want the cube to be
*/
void SetCubeColor(const V3D<uint32_t> &position, const V3D<uint32_t> &color);
/**
* @brief Set the occupation status of the cube at a given position
* @param position the position of the cube
* @param the occupation status of the cube
* @post if the new occupation status of the cube is different than
* the old occupation status, this will enable boardStateHasChanged.
*/
void SetCubeOccupation(const V3D<uint32_t> &position, bool occupation);
/**
* @returns true if the board state has changed since this flag was last set to false
*/
bool BoardStateChanged(){return this->boardStateHasChanged;}
/**
* @brief set the board state status.
* @note you should probably only use this to clear the baordState flag
*/
void SetStateChanged(bool boardState){this->boardStateHasChanged = boardState;}
/**
* @brief Get a column along any axis read into the sliceBuffer
* @param column .z specifies the normal direction of the plane (see PLANE_NORMAL), and
* the x,y values specify the location of the column in that plane
* to fill. IE To fill one stack at 0,2 I would say give V3D<uint32_t>(0,2,PLANE_NORMAL::Z)
* @param sliceBuffer an array of pointers to the cubes along that column
* @returns the number of elements written into the slice buffer
* @note That array is stored locally and will be overwritten everytime this function is called.
* Also, any unused spots at the end of the array will be nullptrs
* @warning allocate the size of the slice buffer using GetMaxDimension if you don't know what you're doing!
*/
uint32_t SliceBoard(const V3D<uint32_t> &column, BOARD_TYPES::Cube ** sliceBuffer);
void PrintEntireBoard() const;
void UpdateAllColors(const std::array<std::array<std::array<V3D<uint32_t>, BOARD_DIMS.z>, BOARD_DIMS.y>, BOARD_DIMS.x>& colorFrame){
for(uint32_t x = 0; x < BOARD_DIMS.x; x++){
for(uint32_t y = 0; y < BOARD_DIMS.y; y++){
for(uint32_t z = 0; z < BOARD_DIMS.z; z++){
this->cubes[x][y][z].color = colorFrame[x][y][z];
}
}
}
}
private:
// this is a 3d array of cubes to represent the board. Good luck visualizing it
/* _____________
/ /|
/ / |
/ / |
/____________/ |
| | |
| | /
| | /
|____________|/
*/
std::array<std::array<std::array<BOARD_TYPES::Cube, BOARD_DIMS.z>, BOARD_DIMS.y>, BOARD_DIMS.x> cubes;
bool boardStateHasChanged;
};
template <const V3D<uint32_t> &BOARD_DIMS>
void Board<BOARD_DIMS>::ToStackString(String &stringBuffer) const{
std::array<uint32_t, BOARD_DIMS.x * BOARD_DIMS.y> linearizedBoard;
for(uint32_t x{0}; x < BOARD_DIMS.x; x++){
for(uint32_t y{0}; y < BOARD_DIMS.y; y++){
uint32_t boardIndex{x + y*3};
linearizedBoard[boardIndex] = 0;
for(uint32_t z{0}; z < BOARD_DIMS.z; z++){
linearizedBoard[boardIndex] += this->cubes[x][y][z].isOccupied;
}
}
}
stringBuffer += String(linearizedBoard[0]);
for(uint32_t i = 1; i < BOARD_DIMS.x * BOARD_DIMS.y; i++){
stringBuffer += "," + String(linearizedBoard[i]);
}
}
template <const V3D<uint32_t> &BOARD_DIMS>
void Board<BOARD_DIMS>::FillColor(const V3D<uint32_t> &color){
for(uint32_t x{0}; x < BOARD_DIMS.x; x++){
for(uint32_t y{0}; y < BOARD_DIMS.y; y++){
for(uint32_t z{0}; z < BOARD_DIMS.z; z++){
this->cubes[x][y][z].color = color;
}
}
}
}
template <const V3D<uint32_t> &BOARD_DIMS>
void Board<BOARD_DIMS>::SetCubeColor(const V3D<uint32_t> &position, const V3D<uint32_t> &color){
this->cubes[position.x][position.y][position.z].color = color;
}
template <const V3D<uint32_t> &BOARD_DIMS>
void Board<BOARD_DIMS>::SetCubeOccupation(const V3D<uint32_t> &position, bool occupation){
bool oldOccupation{this->cubes[position.x][position.y][position.z].isOccupied};
this->cubes[position.x][position.y][position.z].isOccupied = occupation;
if(occupation != oldOccupation) this->boardStateHasChanged = true;
}
template <const V3D<uint32_t> &BOARD_DIMS>
uint32_t Board<BOARD_DIMS>::SliceBoard(const V3D<uint32_t> &column, BOARD_TYPES::Cube ** sliceBuffer){
uint32_t columnLength{0};
V3D<uint32_t> indexIncrimentVector{};
V3D<uint32_t> indexVector{};
switch(column.z){
case BOARD_TYPES::PLANE_NORMAL::X:
columnLength = BOARD_DIMS.x;
indexIncrimentVector.x = 1;
indexVector.z = column.x;
indexVector.y = column.y;
break;
case BOARD_TYPES::PLANE_NORMAL::Y:
columnLength = BOARD_DIMS.y;
indexIncrimentVector.y = 1;
indexVector.x = column.x;
indexVector.z = column.y;
break;
default:
case BOARD_TYPES::PLANE_NORMAL::Z:
columnLength = BOARD_DIMS.z;
indexIncrimentVector.z = 1;
indexVector.x = column.x;
indexVector.y = column.y;
break;
}
for(uint32_t i = 0; i < columnLength; i++){
if(indexVector.x >= BOARD_DIMS.x || indexVector.y >= BOARD_DIMS.y || indexVector.z >= BOARD_DIMS.z){
Serial.println("Board::SliceBoard: Index Out of Bounds:" + String(indexVector.x) + "," + String(indexVector.y) + "," + String(indexVector.z));
return 0;
}
sliceBuffer[i] = &(this->cubes[indexVector.x][indexVector.y][indexVector.z]);
indexVector += indexIncrimentVector;
}
return columnLength;
}
template <const V3D<uint32_t> &BOARD_DIMS>
void Board<BOARD_DIMS>::PrintEntireBoard() const{
for(uint32_t x = 0; x < BOARD_DIMS.x; x++){
for(uint32_t y = 0; y < BOARD_DIMS.y; y++){
for(uint32_t z = 0; z < BOARD_DIMS.z; z++){
const BOARD_TYPES::Cube &cube = this->cubes[x][y][z];
Serial.print("Cube X:" + String(x) + ",Y:" + String(y) + ",Z:" + String(z));
Serial.print("\tColor R:" + String(cube.color.x) + ",G:" + String(cube.color.y) + ",B:" + String(cube.color.z));
Serial.println("\tOccupied? " + String(cube.isOccupied));
}
}
}
}

187
lib/Board/BoardDriver.h Normal file
View File

@@ -0,0 +1,187 @@
/**
* @file BoardDriver.h
* @brief the board driver provides helpful methods to read the physical board's state,
* filter those results, and send color data to the board
*/
#pragma once
#include <cstdint>
#include <array>
#include <Adafruit_NeoPixel.h>
#include <Arduino.h>
#include "BoardTypes.h"
#include "BoardTypes.h"
template <uint32_t NUM_STACKS>
class BoardDriver{
public:
/**
* @param stacks a reference to an array which contains information about the pins which control each cube stack.
* @param pixelController a reference to a pre-constructed neopixel controller which will allow this driver to control neopixels
*/
BoardDriver(
std::array<BOARD_TYPES::CubeStack, NUM_STACKS> &stacks,
Adafruit_NeoPixel &pixelController
);
~BoardDriver() = default;
/**
* @brief Initializes all of the pins necessary to read/write to the board
*/
void Init();
/**
* @param numXStacks the width of the board along the x dimension.
* @param X_COORD the x coordinate of the stack you want information about
* @param Y_COORD the y coordinate of the stack you want information about
* @returns Get the number of cubes present in a stack
*/
uint32_t GetNumberCubes(uint32_t numXStacks, uint32_t X_COORD, uint32_t Y_COORD);
/**
* @param stackIndex the index of the stack that you want to know the height of
* @returns Get the number of cubes present in a stack
*/
uint32_t GetNumberCubes(uint32_t stackIndex);
/**
* @brief stream data to a single cube stack on the board
* @param numXStacks the width of the board along the x dimension.
* @param X_COORD the x coordinate of the stack you want information about
* @param Y_COORD the y coordinate of the stack you want information about
* @param cubes an array of cube pointers which contain the color data you want to stream
* @param numCubes the number of cubes in the cubes array
*/
void UpdateStackLEDs(
uint32_t numXStacks,
uint32_t X_COORD,
uint32_t Y_COORD,
BOARD_TYPES::Cube* cubes[],
uint32_t numCubes
);
/**
* @brief stream data to a single cube stack on the board
* @param stackIndex the index of the stack you want to update the color of
* @param cubes an array of cube pointers which contain the color data you want to stream
* @param numCubes the number of cubes in the cubes array
*/
void UpdateStackLEDs(
uint32_t stackIndex,
BOARD_TYPES::Cube* cubes[],
uint32_t numCubes
);
private:
std::array<BOARD_TYPES::CubeStack, NUM_STACKS> &stacks;
Adafruit_NeoPixel &pixelController;
std::array<uint16_t, NUM_STACKS> filteredReadings;
uint32_t xy2StackIndex(uint32_t x_coord, uint32_t y_coord, uint32_t numXStacks){
return x_coord + y_coord*numXStacks;
}
};
template<uint32_t NUM_STACKS>
void BoardDriver<NUM_STACKS>::Init(){
for(uint32_t i = 0; i < NUM_STACKS; i++){
pinMode(this->stacks[i].ledPin, OUTPUT);
filteredReadings[i] = 0;
}
// begin doesn't really do anything besides setting the pinmode
this->pixelController.begin();
}
template<uint32_t NUM_STACKS>
BoardDriver<NUM_STACKS>::BoardDriver(
std::array<BOARD_TYPES::CubeStack, NUM_STACKS> &stacks,
Adafruit_NeoPixel &pixelController
):
stacks(stacks),
pixelController(pixelController)
{
for(uint32_t i = 0; i < NUM_STACKS; i++){
this->filteredReadings[i] = 0;
}
}
template<uint32_t NUM_STACKS>
void BoardDriver<NUM_STACKS>::UpdateStackLEDs(
uint32_t numXStacks,
uint32_t X_COORD,
uint32_t Y_COORD,
BOARD_TYPES::Cube* cubes[],
uint32_t numCubes
){
this->UpdateStackLEDs(this->xy2StackIndex(X_COORD, Y_COORD, numXStacks), cubes, numCubes);
}
template<uint32_t NUM_STACKS>
void BoardDriver<NUM_STACKS>::UpdateStackLEDs(
uint32_t stackIndex,
BOARD_TYPES::Cube* cubes[],
uint32_t numCubes
){
this->pixelController.setPin(this->stacks[stackIndex].ledPin);
for(int i = 0; i < numCubes; i++){
V3D<uint32_t> color{cubes[i]->color};
this->pixelController.setPixelColor(i*2, this->pixelController.Color(color.x, color.y, color.z));
this->pixelController.setPixelColor((i*2 + 1), this->pixelController.Color(color.x, color.y, color.z));
}
this->pixelController.show();
}
template<uint32_t NUM_STACKS>
uint32_t BoardDriver<NUM_STACKS>::GetNumberCubes(
uint32_t numXStacks,
uint32_t X_COORD,
uint32_t Y_COORD
){
return this->GetNumberCubes(this->xy2StackIndex(X_COORD, Y_COORD, numXStacks));
}
template<uint32_t NUM_STACKS>
uint32_t BoardDriver<NUM_STACKS>::GetNumberCubes(uint32_t stackIndex){
// read the ADC and return the number of cubes
/*
0 cubes: 1 : 4095-3400
1 cube: 1/2 3400-2500
2 cubes: 1/3 2500-1850
3 cubes: 1/4 1850-0
*/
uint16_t value = analogRead(this->stacks[stackIndex].adcPin);
uint16_t lowPassADCRead =
static_cast<uint16_t>(
(static_cast<float>(this->filteredReadings[stackIndex]) * 0.9)
+ (static_cast<float>(value) * 0.1)
);
// temporary definitions to define value ranges:
uint16_t zeroCubesHigh = 4095;
uint16_t zeroCubesLow = 3400;
uint16_t oneCubeLow = 2500;
uint16_t twoCubesLow = 1850;
uint16_t threeCubesLow = 0;
uint8_t stackHeight = 0;
if(lowPassADCRead >= zeroCubesLow && lowPassADCRead <= zeroCubesHigh){
stackHeight = 0;
}
else if(lowPassADCRead >= oneCubeLow){
stackHeight = 1;
}
else if(lowPassADCRead >= twoCubesLow){
stackHeight = 2;
}
else if(lowPassADCRead >= threeCubesLow){
stackHeight = 3;
}
this->filteredReadings[stackIndex] = lowPassADCRead;
return stackHeight;
}

View File

@@ -1,38 +0,0 @@
#include "BoardLayout.h"
uint8_t BoardLayout::GetNumberStacks(){
return this->boardWidth * this->boardHeight;
}
void BoardLayout::SetStackColors(uint8_t stackNum, Color * colors){
CubeStack * stack = this->stacks[stackNum];
stack->SetLEDColors(colors, this->boardHeight);
}
bool BoardLayout::BoardStateHasChanged(){
uint16_t boardState[this->boardWidth * this->boardLength];
this->GetBoardState(boardState);
// compare the board state to the last board state
for(int i = 0; i < (this->boardWidth * this->boardLength); i++){
uint16_t stackState = boardState[i];
uint16_t lastStackState = (this->lastBoardState)[i];
if(stackState != lastStackState){
// copy the board state into the last board state
for(int k = 0; k < (this->boardWidth * this->boardLength); k++){
this->lastBoardState[k] = boardState[k];
}
return true;
}
}
return false;
}
void BoardLayout::GetBoardState(uint16_t * boardStateBuffer){
for(int i = 0; i < (this->boardLength * this->boardWidth); i++){
CubeStack * stack = this->stacks[i];
stack->SendLEDData(); // Enable this if you want to constantly stream LED data
boardStateBuffer[i] = stack->GetNumberCubes();
}
}

View File

@@ -1,69 +0,0 @@
/**
* @brief This is the full board manager which handles the state of every stack on the board
*/
#pragma once
#include "CubeStack.h"
#include "Color.h"
class BoardLayout{
public:
/**
* @brief BoardLayout COnstructor
*/
BoardLayout(uint8_t boardWidth, uint8_t boardLength, uint8_t boardHeight, CubeStack ** stacks) :
boardWidth(boardWidth),
boardLength(boardLength),
boardHeight(boardHeight),
stacks(stacks)
{
this->lastBoardState = new uint16_t[boardWidth * boardLength];
}
/**
* @brief Check if our board state has changed
* @return true if the board state has changed, false otherwise
*/
bool BoardStateHasChanged();
/**
* @brief Get the Number of Stacks
* @return the number of stacks
*/
uint8_t GetNumberStacks();
/**
* @brief Set the LED Colors
* @param stackNum the stack index you would like to address.
* From top left to bottom right, the stack numbers are as follows:
* | 0 1 2 |
* | 3 4 5 |
* | 6 7 8 |
* @param Colors the array of colors to set the LEDs in a stack to
*/
void SetStackColors(uint8_t stackNum, Color * colors);
/**
* @brief Get the board population state
* @param boardStateBuffer the buffer to write the board state to. It must be at least boardWidth * boardLength in length
*/
void GetBoardState(uint16_t * boardStateBuffer);
private:
uint8_t boardWidth;
uint8_t boardLength;
uint8_t boardHeight;
/*
An array of arrays of stacks
[ [stack1, stack2, stack3],
[stack4, stack5, stack6],
[stack7, stack8, stack9] ]
etc
*/
CubeStack ** stacks;
// records the last known board state
uint16_t * lastBoardState;
};

197
lib/Board/BoardManager.h Normal file
View File

@@ -0,0 +1,197 @@
/**
* @file BoardManager.h
* @brief Provides an easy way to manage a board
*/
#pragma once
#include "Board.h"
#include "BoardDriver.h"
#include "Vector3D.h"
#include "Animator.h"
template <const V3D<uint32_t> &BOARD_DIMS>
class BoardManager{
public:
BoardManager(BoardDriver<BOARD_WIDTH*BOARD_LENGTH> &boardDriver, Animator<BOARD_DIMS> &animator);
~BoardManager() = default;
/**
* @brief Initialize the board control. This hsould be called during setup
*/
void Init();
/**
* @brief Read the board state and update the LED colors
*/
void Update();
/**
* @brief Set the color of one cube
* @param position the position of the cube
* @param color the oclor you want the cube to be
*/
void SetCubeColor(const V3D<uint32_t> &position, const V3D<uint32_t> &color);
/**
* @brief Set the color of one column of cubes.
* A column is defined as x,y,z where z is the normal vector direction to a plane.
* x,y is the column coordinate on the plane.
* @param column the column vector
* @param color the color you want the column to be
*/
void SetColumnColors(const V3D<uint32_t> &column, const V3D<uint32_t> *color, uint32_t numColors);
/**
* @brief Fill a column along any axis with a color
* @param column .z specifies the normal direction of the plane (see PLANE_NORMAL), and
* the x,y values specify the location of the column in that plane
* to fill. IE To fill one stack at 0,2 I would say give V3D<uint32_t>(0,2,PLANE_NORMAL::Z)
* @param color the color you want to fill the column with
*/
void FillColumnColor(const V3D<uint32_t> &column, const V3D<uint32_t> &color);
/**
* @returns true if the board has changed state
*/
bool HasBoardChanged();
/**
* @brief Clear the board state changed flag
*/
void ClearBoardChanged();
/**
* @brief Get the board occupation state returned in the format a,b,c,d....
*/
void Board2StackString(String& messageBuffer);
void FillColor(const V3D<uint32_t> &color){this->board.FillColor(color);}
void PrintColorState(){this->board.PrintEntireBoard();}
private:
BoardDriver<BOARD_WIDTH*BOARD_LENGTH> &driver;
Board<BOARD_DIMS> board{};
Animator<BOARD_DIMS> &animator;
void updateStackColors(const V3D<uint32_t> &column);
uint32_t getColumnHeight(BOARD_TYPES::PLANE_NORMAL normal){
switch(normal){
case BOARD_TYPES::PLANE_NORMAL::X:
return BOARD_DIMS.x;
break;
case BOARD_TYPES::PLANE_NORMAL::Y:
return BOARD_DIMS.y;
break;
case BOARD_TYPES::PLANE_NORMAL::Z:
return BOARD_DIMS.z;
break;
default:
return 0;
}
}
void updateColorsFromAnimator();
};
template <const V3D<uint32_t> &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::updateColorsFromAnimator(){
if(this->animator.interpolatedFrameHasChanged){
this->board.UpdateAllColors(this->animator.GetInterpolatedFrame());
this->animator.interpolatedFrameHasChanged = false;
}
}
template <const V3D<uint32_t> &BOARD_DIMS>
BoardManager<BOARD_DIMS>::BoardManager(BoardDriver<BOARD_WIDTH*BOARD_LENGTH> &boardDriver, Animator<BOARD_DIMS> &animator):
driver(boardDriver),
animator(animator){}
template <const V3D<uint32_t> &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::Init(){
this->driver.Init();
}
template <const V3D<uint32_t> &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::Update(){
this->updateColorsFromAnimator();
// update the occupied cubes on the board and the cube colors
for(uint32_t x = 0; x < BOARD_DIMS.x; x++){
for(uint32_t y = 0; y < BOARD_DIMS.y; y++){
uint32_t stackIndex{y * BOARD_DIMS.x + x};
uint32_t numCubes{this->driver.GetNumberCubes(stackIndex)};
for(uint32_t z = 0; z < BOARD_DIMS.z; z++){
V3D<uint32_t> cubePosition{x, y, z};
// update the cube's occupation
this->board.SetCubeOccupation(cubePosition, z < numCubes);
}
// create the column vector for the slice direction
V3D<uint32_t> sliceVector{x,y,BOARD_TYPES::PLANE_NORMAL::Z};
// create a cube slice array buffer
BOARD_TYPES::Cube* sliceBuffer[BOARD_DIMS.z];
// have the board slice get read into our buffer
this->board.SliceBoard(sliceVector, sliceBuffer);
// send the board slice to the driver to update its LED colors
this->driver.UpdateStackLEDs(stackIndex, sliceBuffer, BOARD_DIMS.z);
}
}
}
template <const V3D<uint32_t> &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::updateStackColors(const V3D<uint32_t> &column){
// the only column type allowed here is z.
V3D<uint32_t> sliceVector{column.x, column.y, BOARD_TYPES::Z};
// create a buffer for slice board to write the cube slice into
BOARD_TYPES::Cube * cubeSlice[BOARD_DIMS.z];
this->board.SliceBoard(column, cubeSlice);
uint32_t numCubes{this->getColumnHeight(static_cast<BOARD_TYPES::PLANE_NORMAL>(column.z))};
this->driver.UpdateStackLEDs(BOARD_DIMS.x, cubeSlice, numCubes);
}
template <const V3D<uint32_t> &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::SetCubeColor(const V3D<uint32_t> &position, const V3D<uint32_t> &color){
this->board.SetCubeColor(position, color);
V3D<uint32_t> slice{position.x, position.y, BOARD_TYPES::PLANE_NORMAL::Z};
this->updateStackColors(slice);
}
template <const V3D<uint32_t> &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::SetColumnColors(const V3D<uint32_t> &column, const V3D<uint32_t> *color, uint32_t numColors){
uint32_t columnHeight{this->getColumnHeight(static_cast<BOARD_TYPES::PLANE_NORMAL>(column.z))};
// create a cube pointer buffer and store a board slice into it
BOARD_TYPES::Cube * slicedBoard[columnHeight];
uint32_t sliceLength{this->board.SliceBoard(column, slicedBoard)};
uint32_t maxIndex{std::min(numColors, columnHeight)};
for(uint32_t i = 0; i < maxIndex; i++){
slicedBoard[i]->color = color[i];
}
}
template <const V3D<uint32_t> &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::FillColumnColor(const V3D<uint32_t> &column, const V3D<uint32_t> &color){
uint32_t columnHeight{this->getColumnHeight(column.z)};
V3D<uint32_t> colors[columnHeight];
for(uint32_t i = 0; i < columnHeight; i++){
colors[i] = color;
}
this->SetColumnColors(column, colors);
}
template <const V3D<uint32_t> &BOARD_DIMS>
bool BoardManager<BOARD_DIMS>::HasBoardChanged(){return this->board.BoardStateChanged();}
template <const V3D<uint32_t> &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::ClearBoardChanged(){this->board.SetStateChanged(false);}
template <const V3D<uint32_t> &BOARD_DIMS>
void BoardManager<BOARD_DIMS>::Board2StackString(String& messageBuffer){
this->board.ToStackString(messageBuffer);
}

26
lib/Board/BoardTypes.h Normal file
View File

@@ -0,0 +1,26 @@
/**
* @file BoardTypes.h
* @brief Some types to that you'll need to define and control the board
*/
#pragma once
#include <cstdint>
#include "Vector3D.h"
namespace BOARD_TYPES{
struct CubeStack{
uint8_t adcPin;
uint8_t ledPin;
};
enum PLANE_NORMAL : uint32_t{
X = 0,
Y,
Z
};
struct Cube{
V3D<uint32_t> color;
bool isOccupied{false};
};
};

View File

@@ -1,21 +0,0 @@
/**
* @file Color.h
* @brief This file contains the color struct
*/
#pragma once
#include <Arduino.h>
// store a color
struct Color{
// create a constructor for this struct
Color(uint8_t red, uint8_t green, uint8_t blue) :
red(red),
green(green),
blue(blue)
{}
Color() = default;
uint8_t red{0};
uint8_t green{0};
uint8_t blue{0};
};

View File

@@ -1,79 +0,0 @@
#include "CubeStack.h"
CubeStack::CubeStack(uint16_t ADCPin, uint16_t ledPin, uint8_t numLEDs){
this->ADCPin = ADCPin;
this->blockLights = *(new Adafruit_NeoPixel(numLEDs*2, ledPin, NEO_GRB + NEO_KHZ800));
this->ledColors = new Color[numLEDs];
this->numLEDs = numLEDs;
// initialize the LED colors to off
for(int i = 0; i < numLEDs; i++){
this->ledColors[i] = *(new Color(0, 0, 0));
}
};
uint8_t CubeStack::GetNumberCubes(){
// read the ADC and return the number of cubes
/*
0 cubes: 1 : 4095-3400
1 cube: 1/2 3400-2500
2 cubes: 1/3 2500-1850
3 cubes: 1/4 1850-0
*/
uint16_t value = analogRead(this->ADCPin);
this->lowPassADCRead = static_cast<uint16_t>((static_cast<float>(this->lowPassADCRead) * 0.9) + (static_cast<float>(value) * 0.1));
if(this->lowPassADCRead < 2500 && false){
Serial.println("ADC Pin:" + String(this->ADCPin) + " Value: " + String(value) + " Low Pass: " + String(this->lowPassADCRead));
}
// temporary definitions to define value ranges:
uint16_t zeroCubesHigh = 4095;
uint16_t zeroCubesLow = 3400;
uint16_t oneCubeLow = 2500;
uint16_t twoCubesLow = 1850;
uint16_t threeCubesLow = 0;
uint8_t stackHeight = 0;
if(this->lowPassADCRead >= zeroCubesLow && this->lowPassADCRead <= zeroCubesHigh){
stackHeight = 0;
}
else if(this->lowPassADCRead >= oneCubeLow){
stackHeight = 1;
}
else if(this->lowPassADCRead >= twoCubesLow){
stackHeight = 2;
}
else if(this->lowPassADCRead >= threeCubesLow){
stackHeight = 3;
}
if(this->lastStackHeight != stackHeight){
this->lastStackHeight = stackHeight;
this->SendLEDData();
}
return stackHeight;
}
void CubeStack::SetLEDColors(Color * colors, uint8_t numColors){
// copy the colors into the ledColors array
for(int i = 0; i < numColors; i++){
this->ledColors[i].red = colors[i].red;
this->ledColors[i].green = colors[i].green;
this->ledColors[i].blue = colors[i].blue;
}
this->SendLEDData();
}
void CubeStack::SendLEDData(){
// we always initialize before we do anything because other CubeStacks could be hogging the hardware
// between our writes
this->blockLights.begin();
// set the LED colors
for(int i = 0; i < this->numLEDs; i++){
this->blockLights.setPixelColor(i*2, this->blockLights.Color(this->ledColors[i].red, this->ledColors[i].green, this->ledColors[i].blue));
this->blockLights.setPixelColor((i*2 + 1), this->blockLights.Color(this->ledColors[i].red, this->ledColors[i].green, this->ledColors[i].blue));
}
this->blockLights.show();
}

View File

@@ -1,52 +0,0 @@
/**
* @brief this manages a single cube stack and the lighting / detecting of how many cubes
*/
#pragma once
#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include "Color.h"
class CubeStack{
public:
/**
* @brief Construct a new Cube Stack object
* @param ADCPin the pin that the ADC is connected to
* @param ledPin the pin that the LED is connected to
*/
CubeStack(uint16_t ADCPin, uint16_t ledPin, uint8_t numLEDs);
/**
* @brief Returns the number of cubes in the stack
* @return the number of cubes in the stack
*/
uint8_t GetNumberCubes();
/**
* @brief Set the led color array and then send the data to the LED strip
* @param colors the array of colors to set the LEDs to
* @param numColors the number of colors in the array
*/
void SetLEDColors(Color * colors, uint8_t numColors);
/**
* @brief sends the LED data to the LED strip
*/
void SendLEDData();
private:
uint8_t ADCPin;
// we will probably need a pointer to a fastled object here
Adafruit_NeoPixel blockLights;
uint16_t lowPassADCRead{0};
// store the Color of each LED
Color * ledColors;
uint8_t numLEDs;
uint8_t lastStackHeight{0};
};

View File

@@ -1,48 +0,0 @@
#include "ColorManager.h"
void ColorManager::Update(){
if(!(this->enabled)){
return;
}
// go through our colors and have them fade from r->g->b->r
for(uint8_t i = 0; i < 9; i++){
for(uint8_t j = 0; j < 3; j++){
Color * color = this->colors[i][j];
// fade from red to green
if(color->red > 0 && color->green >= 0 && color->blue == 0){
color->red--;
color->green++;
}
// fade from green to blue
else if(color->green > 0 && color->blue >= 0 && color->red == 0){
color->green--;
color->blue++;
}
// fade from blue to red
else if(color->blue > 0 && color->red >= 0 && color->green == 0){
color->blue--;
color->red++;
}
}
}
// set the colors
for(uint8_t i = 0; i < 9; i++){
Color temp_colors[3] = {*(this->colors[i][0]), *(this->colors[i][1]), *(this->colors[i][2])};
this->board->SetStackColors(i, temp_colors);
}
}
void ColorManager::Enable(bool enable){
this->enabled = enable;
if(this->enabled == false){
// set all the colors to black
Color black(0, 0, 0);
Color temp_colors[3] = {black, black, black};
// set the colors
for(uint8_t i = 0; i < 9; i++){
this->board->SetStackColors(i, temp_colors);
}
}
}

View File

@@ -1,44 +0,0 @@
/**
* @file ColorManager.h
* @brief Generate pretty colors for the board and make it do something when unity isn't controlling it
*/
#pragma once
#include "BoardLayout.h"
#include "Color.h"
class ColorManager{
public:
ColorManager(BoardLayout * board) :
board(board)
{}
/**
* @brief Allows the color manager to update the board colors
*/
void Update();
/**
* @brief Enables or disables the color manager
* @param enable true to enable, false to disable
*/
void Enable(bool enable);
private:
BoardLayout * board;
bool enabled{true};
Color * colors[9][3] = {
{new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)},
{new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)},
{new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)},
{new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)},
{new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)},
{new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)},
{new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)},
{new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)},
{new Color(255, 0, 0), new Color(0, 255, 0), new Color(0, 0, 255)}
};
};

View File

@@ -2,6 +2,9 @@
#include <Arduino.h> #include <Arduino.h>
#include <BluetoothSerial.h> #include <BluetoothSerial.h>
#include <FreeRTOS.h> #include <FreeRTOS.h>
#include <Adafruit_NeoPixel.h>
#include <chrono>
#include <array>
// Static Defines // Static Defines
#include "PINOUT.h" #include "PINOUT.h"
@@ -11,29 +14,36 @@
// project specific libraries // project specific libraries
#include "BluetoothSerial.h" #include "BluetoothSerial.h"
#include "SerialMessage.h" #include "SerialMessage.h"
#include "BoardLayout.h"
#include "Color.h"
#include "ColorManager.h"
#include "GlobalPrint.h" #include "GlobalPrint.h"
#include "BoardManager.h"
#include "BoardDriver.h"
#include "BoardTypes.h"
#include "Animator.h"
#include "TestFrames.h"
#include "Animation.h"
// -------------------------------------------------- // --------------------------------------------------
// ----------------- VARIABLES ---------------------- // ----------------- VARIABLES ----------------------
// -------------------------------------------------- // --------------------------------------------------
TaskHandle_t updateCommunicaitonTask; TaskHandle_t updateCommunicaitonTask;
TaskHandle_t updateBoardTask; TaskHandle_t updateBoardTask;
uint32_t boardStateTimer{0}; std::array<std::vector<AnimationFrame>*, 2> animations = {
bool boardStateHasChanged{false}; &RisingCubes::rising,
uint32_t boardStateMaxUpdatePeriod{34}; // this is a little slower than 30fps &RotatingCubes::rotating,
};
// BluetoothSerial SerialBT; // BluetoothSerial SerialBT;
// BluetoothSerialMessage serialMessageBT(&SerialBT); // BluetoothSerialMessage serialMessageBT(&SerialBT);
SerialMessage<500, 10> serialMessage(&Serial); SerialMessage<SERIAL_CHAR_LENGTH, SERIAL_ARG_LENGTH> serialMessage(&Serial);
BoardLayout board(BOARD_WIDTH, BOARD_LENGTH, BOARD_HEIGHT, stacks);
// Temporary thing until we can get bluetooth color management working on the quest Adafruit_NeoPixel pixelController{BOARD_HEIGHT*2, STACK1_LED_PIN, NEO_GRB + NEO_KHZ800};
ColorManager colorManager(&board);
Animator<BOARD_DIMENSIONS> animator{};
BoardDriver<BOARD_WIDTH*BOARD_LENGTH> boardDriver{stacks, pixelController};
BoardManager<BOARD_DIMENSIONS> boardManager{boardDriver, animator};
// -------------------------------------------------- // --------------------------------------------------
// ----------------- FUNCTIONS ---------------------- // ----------------- FUNCTIONS ----------------------
// -------------------------------------------------- // --------------------------------------------------
@@ -64,63 +74,64 @@ void SetupBluetoothModule(){
void printBoardState(){ void printBoardState(){
// create a buffer to hold the board state
uint16_t boardState[BOARD_WIDTH * BOARD_LENGTH];
// read in the board state
board.GetBoardState(boardState);
GlobalPrint::Print("!0,"); GlobalPrint::Print("!0,");
String boardString;
for(int i = 0; i < (BOARD_WIDTH * BOARD_LENGTH); i++){ boardManager.Board2StackString(boardString);
GlobalPrint::Print(String(boardState[i])); GlobalPrint::Print(boardString);
if(i == (BOARD_WIDTH * BOARD_LENGTH) - 1){
break;
}
GlobalPrint::Print(",");
}
GlobalPrint::Println(";"); GlobalPrint::Println(";");
} }
void SetStackColor(uint32_t * args, int argsLength){ void SetStackColor(uint32_t * args, int argsLength){
uint32_t stackNum = args[1]; uint32_t stackNum = args[1];
uint32_t X_COORD{stackNum};
while(X_COORD > BOARD_DIMENSIONS.x - 1){
X_COORD -= BOARD_DIMENSIONS.x;
}
uint32_t Y_COORD{(stackNum - X_COORD) / BOARD_DIMENSIONS.y};
uint32_t numColors = (argsLength - 2) / 3; uint32_t numColors = (argsLength - 2) / 3;
Color colors[numColors]; V3D<uint32_t> colors[numColors];
for(int i = 0; i < numColors; i++){ for(int i = 0; i < numColors; i++){
int red = args[2 + (i * 3)]; uint32_t red = args[2 + (i * 3)];
int green = args[3 + (i * 3)]; uint32_t green = args[3 + (i * 3)];
int blue = args[4 + (i * 3)]; uint32_t blue = args[4 + (i * 3)];
colors[i] = Color(red, green, blue); colors[i] = V3D<uint32_t>{red, green, blue};
} }
boardManager.SetColumnColors(V3D<uint32_t>{X_COORD, Y_COORD, BOARD_TYPES::PLANE_NORMAL::Z}, colors, numColors);
board.SetStackColors(stackNum, colors);
} }
void parseData(Message<500, 10> &message){ void parseData(Message<SERIAL_CHAR_LENGTH, SERIAL_ARG_LENGTH> &message){
int32_t * args{message.GetArgs()}; int32_t * args{message.GetArgs()};
uint32_t argsLength{message.GetArgsLength()}; uint32_t argsLength{message.GetPopulatedArgs()};
uint32_t command = args[0]; uint32_t command = args[0];
switch(command){ switch(command){
case Commands::BoardState: case Commands::BoardState:{
printBoardState(); printBoardState();
break; break;
case Commands::PING: }
case Commands::PING:{
GlobalPrint::Println("!" + String(Commands::PING) + ";"); GlobalPrint::Println("!" + String(Commands::PING) + ";");
break; break;
case Commands::SetStackColors: }
case Commands::SetStackColors:{
GlobalPrint::Println("!2;"); GlobalPrint::Println("!2;");
colorManager.Enable(false); animator.isEnabled = false;
V3D<uint32_t> black{};
boardManager.FillColor(black);
SetStackColor(reinterpret_cast<uint32_t *>(args), argsLength); SetStackColor(reinterpret_cast<uint32_t *>(args), argsLength);
break; break;
case Commands::GoToIdle: }
case Commands::GoToIdle:{
GlobalPrint::Println("!3;"); GlobalPrint::Println("!3;");
colorManager.Enable(true); animator.isEnabled = true;
break; break;
default: }
default:{
GlobalPrint::Println("INVALID COMMAND"); GlobalPrint::Println("INVALID COMMAND");
break; break;
} }
}
// now that we have run the command we can clear the data for the next command. // now that we have run the command we can clear the data for the next command.
serialMessage.ClearNewData(); serialMessage.ClearNewData();
@@ -150,20 +161,38 @@ void UpdateCommunication(void * params){
void UpdateBoard(void * params){ void UpdateBoard(void * params){
Serial.println("Spawning UpdateBoard task"); Serial.println("Spawning UpdateBoard task");
auto updateTickRate{std::chrono::milliseconds(8)};
auto boardStateTimer{std::chrono::milliseconds(0)};
auto boardStateMaxUpdatePeriod{std::chrono::milliseconds(34)}; // this is a little slower than 30fps
unsigned long accurateTimer{millis()};
auto changeAnimationTimer{std::chrono::milliseconds(0)};
uint8_t currentAnimation{0};
for(;;){ for(;;){
if(board.BoardStateHasChanged()){ auto actualTimePassed{std::chrono::milliseconds(millis() - accurateTimer)};
boardStateHasChanged = true; accurateTimer = millis();
}
if(millis() - boardStateTimer > boardStateMaxUpdatePeriod && boardStateHasChanged){ if(boardStateTimer >= boardStateMaxUpdatePeriod && boardManager.HasBoardChanged()){
boardStateTimer = millis();
printBoardState(); printBoardState();
boardStateHasChanged = false; boardManager.ClearBoardChanged();
boardStateTimer = std::chrono::milliseconds(0);
} }
colorManager.Update(); if(changeAnimationTimer >= std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::minutes(1))){
changeAnimationTimer = std::chrono::milliseconds(0);
currentAnimation++;
if(currentAnimation >= animations.size()){
currentAnimation = 0;
}
animator.StartAnimation(animations[currentAnimation]);
}
vTaskDelay(5); animator.RunAnimation(actualTimePassed);
boardManager.Update();
boardStateTimer += actualTimePassed;
changeAnimationTimer += actualTimePassed;
vTaskDelay(updateTickRate.count());
} }
Serial.println("UpdateBoard task has ended unexpectedly!"); Serial.println("UpdateBoard task has ended unexpectedly!");
} }
@@ -189,10 +218,10 @@ void setup() {
xTaskCreate(UpdateCommunication, "UpdateCommunication", 10000, NULL, 0, &updateCommunicaitonTask); xTaskCreate(UpdateCommunication, "UpdateCommunication", 10000, NULL, 0, &updateCommunicaitonTask);
Serial.println("Beginning Board Initializaiton"); Serial.println("Beginning Board Initializaiton");
boardManager.Init();
animator.SetLoop(true);
animator.StartAnimation(animations[0]);
xTaskCreate(UpdateBoard, "UpdateBoard", 10000, NULL, 0, &updateBoardTask); xTaskCreate(UpdateBoard, "UpdateBoard", 10000, NULL, 0, &updateBoardTask);
Color colors[] = {Color(255, 0, 0), Color(0, 0, 0), Color(0, 0, 0)};
board.SetStackColors(2, colors);
boardStateTimer = millis();
Serial.println("Setup Complete"); Serial.println("Setup Complete");
} }