Read the previous post before you read this one.
In the previous post, I showed how to use hand-verification – and perhaps a debugger – to get your code working. That works well in many cases, but sometimes you have code that you think is going to evolve over time or code where it is tedious to do the hand verification.
The alternate is to automate that verification, using what is commonly known as “Unit Tests”.
Blending colors
The current implementation only uses red, green, and blue. It would be much nicer if it could smoothly change between colors. I’m going to be building a way to blend from the current color to a new color in a specified number of steps.
I’m going to do this implementation in small steps, using a technique where I write the test before I write the code. To start, we need to switch to a new color immediately when the user chooses zero steps.
Here’s my test code:
static void TestZeroSteps()
{
ColorBlender colorBlender;
colorBlender.blendToColor(LedColor(255, 0, 255), 0);
LedColor color = colorBlender.getCurrentColor();
Assert::AreEqual(255, color.Red);
Assert::AreEqual( 0, color.Green);
Assert::AreEqual(255, color.Blue);
}
The test blends to (255, 0, 255) – purple – in zero steps, so the next time getCurrentColor() is called, it should return that color.
The Assert::AreEqual() statements are verifying that the values we get back are the ones we expect; if they are not, a message will be written out to the console.
This test code lives in the ColorBlenderTest.h file.
The code for ColorBlender lives in the arduino project, and looks like this:
class ColorBlender
{
LedColor _targetColor;
public:
LedColor getCurrentColor()
{
return LedColor(_targetColor.Red, _targetColor.Green, _targetColor.Blue);
}
void blendToColor(LedColor targetColor, int steps)
{
_targetColor = targetColor;
}
};
When run, that produces no errors. In the next test, we’ll do the blend in one step. Here’s a new test:
static void TestOneStep()
{
ColorBlender colorBlender;
colorBlender.blendToColor(LedColor(255, 0, 255), 1);
LedColor color = colorBlender.getCurrentColor();
Assert::AreEqual(0, color.Red);
Assert::AreEqual(0, color.Green);
Assert::AreEqual(0, color.Blue);
colorBlender.step();
color = colorBlender.getCurrentColor();
Assert::AreEqual(255, color.Red);
Assert::AreEqual(0, color.Green);
Assert::AreEqual(255, color.Blue);
}
The initial color should be black, and then after calling step(), it should move to the new color. When this is run, we get the following:
Assert: expected 0 got 255
Assert: expected 0 got 255
We get those errors because there is no implementation to make the test work. This code will make it work:
class ColorBlender
{
LedColor _currentColor;
LedColor _targetColor;
public:
LedColor getCurrentColor()
{
return LedColor(_currentColor.Red, _currentColor.Green, _currentColor.Blue);
}
void blendToColor(LedColor targetColor, int steps)
{
_targetColor = targetColor;
if (steps == 0)
{
_currentColor = _targetColor;
}
}
void step()
{
_currentColor = _targetColor;
}
};
and now, onto two steps. Here’s the test:
static void TestTwoSteps()
{
ColorBlender colorBlender;
colorBlender.blendToColor(LedColor(255, 0, 255), 2);
LedColor color = colorBlender.getCurrentColor();
Assert::AreEqual(0, color.Red);
Assert::AreEqual(0, color.Green);
Assert::AreEqual(0, color.Blue);
colorBlender.step();
color = colorBlender.getCurrentColor();
Assert::AreEqual(127, color.Red);
Assert::AreEqual(0, color.Green);
Assert::AreEqual(127, color.Blue);
colorBlender.step();
color = colorBlender.getCurrentColor();
Assert::AreEqual(255, color.Red);
Assert::AreEqual(0, color.Green);
Assert::AreEqual(255, color.Blue);
}
and the updated code:
class ColorBlender
{
float _red = 0.0F;
float _green = 0.0F;
float _blue = 0.0F;
float _redDelta = 0.0F;
float _greenDelta = 0.0F;
float _blueDelta = 0.0F;
LedColor _targetColor;
public:
LedColor getCurrentColor()
{
return LedColor((int) _red, (int)_green, (int)_blue);
}
void blendToColor(LedColor targetColor, int steps)
{
_targetColor = targetColor;
if (steps == 0)
{
_red = _targetColor.Red;
_green = _targetColor.Green;
_blue = _targetColor.Blue;
}
else
{
_redDelta = (_targetColor.Red – _red) / steps;
_greenDelta = (_targetColor.Green – _green) / steps;
_blueDelta = (_targetColor.Blue – _blue) / steps;
}
}
void step()
{
_red = _red + _redDelta;
_green = _green + _greenDelta;
_blue = _blue + _blueDelta;
}
};
That works. One more test to add; we should stop blending even if we go beyond the specified number of steps. Here’s a test for it:
static void TestThreeStepsAndHold()
{
ColorBlender colorBlender;
colorBlender.blendToColor(LedColor(10, 0, 0), 3);
Assert::AreEqual(0, colorBlender.getCurrentColor().Red);
colorBlender.step();
Assert::AreEqual(3, colorBlender.getCurrentColor().Red);
colorBlender.step();
Assert::AreEqual(6, colorBlender.getCurrentColor().Red);
colorBlender.step();
Assert::AreEqual(10, colorBlender.getCurrentColor().Red);
colorBlender.step();
Assert::AreEqual(10, colorBlender.getCurrentColor().Red);
}
That fails on the last assert, as it keeps adding and gives us 13. I added some code and ended up with this:
class ColorBlender
{
float _red = 0.0F;
float _green = 0.0F;
float _blue = 0.0F;
float _redDelta = 0.0F;
float _greenDelta = 0.0F;
float _blueDelta = 0.0F;
LedColor _targetColor;
int _steps;
public:
LedColor getCurrentColor()
{
return LedColor((int) _red, (int)_green, (int)_blue);
}
void blendToColor(LedColor targetColor, int steps)
{
_steps = steps;
_targetColor = targetColor;
if (steps == 0)
{
_red = _targetColor.Red;
_green = _targetColor.Green;
_blue = _targetColor.Blue;
}
else
{
_redDelta = (_targetColor.Red – _red) / steps;
_greenDelta = (_targetColor.Green – _green) / steps;
_blueDelta = (_targetColor.Blue – _blue) / steps;
}
}
void step()
{
if (_steps != 0)
{
_red = _red + _redDelta;
_green = _green + _greenDelta;
_blue = _blue + _blueDelta;
_steps–;
}
}
};
All of that was written without and tested without any interaction with my microcontroller.
Doing something nice with the blender…
The blender by itself isn’t that useful; we need something to drive it through different colors. Here’s ColorWheel.h:
class ColorWheel
{
int _stepCount;
ColorBlender _colorBlender;
LedColor _colors[6] = {
LedColor(255, 0, 0),
LedColor(255, 255, 0),
LedColor(0, 255, 0),
LedColor(0, 255, 255),
LedColor(0, 0, 255),
LedColor(255, 0, 255) };
int _colorIndex = 0;
public:
ColorWheel(int stepCount)
{
_stepCount = stepCount;
_colorBlender.blendToColor(_colors[_colorIndex], 1);
}
LedColor getNextColor()
{
_colorBlender.step();
LedColor ledColor = _colorBlender.getCurrentColor();
if (_colorBlender.isDone())
{
_colorIndex = (_colorIndex + 1) % 6;
_colorBlender.blendToColor(_colors[_colorIndex], _stepCount);
}
return ledColor;
}
};
It uses a ColorBlender, and whenever a color blender is done – which is checked through a new “isDone()” method – it will add a blend to the next color in the sequence. So it continuously cycles through the 6 main colors (Red, yellow, green, cyan, blue, purple).
It has tests:
#pragma once
#include “..\Arduino\Larson\src\ColorWheel.h”
class ColorWheelTest
{
static void TestSingleStepWheel()
{
ColorWheel colorWheel(1);
LedColor ledColor = colorWheel.getNextColor();
Assert::AreEqual(255, ledColor.Red);
Assert::AreEqual( 0, ledColor.Green);
Assert::AreEqual( 0, ledColor.Blue);
ledColor = colorWheel.getNextColor();
Assert::AreEqual(255, ledColor.Red);
Assert::AreEqual(255, ledColor.Green);
Assert::AreEqual( 0, ledColor.Blue);
ledColor = colorWheel.getNextColor();
Assert::AreEqual( 0, ledColor.Red);
Assert::AreEqual(255, ledColor.Green);
Assert::AreEqual( 0, ledColor.Blue);
ledColor = colorWheel.getNextColor();
Assert::AreEqual( 0, ledColor.Red);
Assert::AreEqual(255, ledColor.Green);
Assert::AreEqual(255, ledColor.Blue);
ledColor = colorWheel.getNextColor();
Assert::AreEqual( 0, ledColor.Red);
Assert::AreEqual( 0, ledColor.Green);
Assert::AreEqual(255, ledColor.Blue);
ledColor = colorWheel.getNextColor();
Assert::AreEqual(255, ledColor.Red);
Assert::AreEqual( 0, ledColor.Green);
Assert::AreEqual(255, ledColor.Blue);
ledColor = colorWheel.getNextColor();
Assert::AreEqual(255, ledColor.Red);
Assert::AreEqual( 0, ledColor.Green);
Assert::AreEqual( 0, ledColor.Blue);
}
static void TestFourStepWheel()
{
ColorWheel colorWheel(4);
LedColor ledColor = colorWheel.getNextColor();
Assert::AreEqual(255, ledColor.Red);
Assert::AreEqual(0, ledColor.Green);
Assert::AreEqual(0, ledColor.Blue);
ledColor = colorWheel.getNextColor();
Assert::AreEqual(255, ledColor.Red);
Assert::AreEqual(63, ledColor.Green);
Assert::AreEqual(0, ledColor.Blue);
ledColor = colorWheel.getNextColor();
Assert::AreEqual(255, ledColor.Red);
Assert::AreEqual(127, ledColor.Green);
Assert::AreEqual(0, ledColor.Blue);
ledColor = colorWheel.getNextColor();
Assert::AreEqual(255, ledColor.Red);
Assert::AreEqual(191, ledColor.Green);
Assert::AreEqual(0, ledColor.Blue);
ledColor = colorWheel.getNextColor();
Assert::AreEqual(255, ledColor.Red);
Assert::AreEqual(255, ledColor.Green);
Assert::AreEqual(0, ledColor.Blue);
ledColor = colorWheel.getNextColor();
Assert::AreEqual(191, ledColor.Red);
Assert::AreEqual(255, ledColor.Green);
Assert::AreEqual(0, ledColor.Blue);
}
public:
static void RunTests()
{
TestSingleStepWheel();
TestFourStepWheel();
}
};
and that makes the Animater simpler. Here’s the running code:
#define NUM_LEDS 15
class Animater
{
int _last = 0;
int _current = 0;
int _increment = 1;
int _color = 0;
ColorWheel _colorWheel;
public:
Animater() : _colorWheel(20) {}
void doAnimationStep(LedStrip &ledStrip)
{
ledStrip.setColor(_last, 0, 0, 0);
LedColor ledColor = _colorWheel.getNextColor();
ledStrip.setColor(_current, ledColor.Red, ledColor.Green, ledColor.Blue);
ledStrip.show();
_last = _current;
_current = _current + _increment;
if (_current == 0 || _current == NUM_LEDS – 1)
{
_increment = -_increment;
}
}
};