Skip to content

Commit

Permalink
v1.2.31
Browse files Browse the repository at this point in the history
- New `color-light` firmware version with many fixes and improvements
  • Loading branch information
genemars committed Jul 5, 2024
1 parent 695c6b5 commit 5ffedd9
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 132 deletions.
144 changes: 71 additions & 73 deletions examples/color-light/color-fx.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,105 +29,108 @@
using namespace Service;
using namespace Service::API::devices;

#define FX_DEG_NORM(X) (((int) round((X) * 360.0f) % 360) / 360.0f)

typedef LightColor* AnimatedColor;
AnimatedColor* animatedColors = nullptr;

float currentSaturation;
float hueOffset = 0;
float hueRange = 1;
float cursorDirection = 1;
float hueZoom = 1;

void fx_init(int count, LightColor& currentColor) {
// Allocate "animated" colors
animatedColors = new AnimatedColor[count];
for (int i = 0; i < count; i++) {
animatedColors[i] = new LightColor();
animatedColors[i]->setColor(currentColor.getHue(), currentColor.getSaturation(), currentColor.getValue(), 0);
}
}

void fx_reset(Adafruit_NeoPixel* pixels, LightColor& color) {
// hueOffset = 0;
hueRange = 1;
hueZoom = 1;
}

void fx_solid(Adafruit_NeoPixel* pixels, LightColor& color) {
float r = color.getRed();
float g = color.getGreen();
float b = color.getBlue();
void fx_solid(Adafruit_NeoPixel* pixels, LightColor& color, int transitionMs = 200) {
for (int i = 0; i < pixels->numPixels(); i++) {
pixels->setPixelColor(i, r, g, b);
// animatedColors[i]->setColor(color.getHue(), color.getSaturation(), color.getValue(), 0);
animatedColors[i]->setColor(color.getHue(), color.getSaturation(), color.getValue(), transitionMs);
}
}

float currentSaturation;
float cursorDirection = 1;
unsigned long rainbow_refresh_ts = 0;
void fx_rainbow(Adafruit_NeoPixel* pixels, LightColor& color) {
if (color.getSaturation() > 1) {
return;
// TODO: ...
currentSaturation = 1;
} else {
currentSaturation = color.getSaturation();
}
currentSaturation = color.getSaturation();
float length = pixels->numPixels() * hueZoom;

float hueStep = 1.0f / (float) length;
//float offsetIncrement = (0.128f / length);

// animate
if (millis() - rainbow_refresh_ts > 100) {
rainbow_refresh_ts = millis();

if (pixels != nullptr) {
float h = color.getHue() + hueOffset;
if (h > 1) h = ((int)round(h * 10000) % 10000) / 10000.0f;
float hueStep = 1.0f / (float) pixels->numPixels();
hueStep /= hueRange;
float v = color.getValue();
for (int i = 0; i < pixels->numPixels(); i++) {
h += hueStep;
auto rgb = Utility::hsv2rgb(h, currentSaturation, v);
pixels->setPixelColor(i, rgb.r, rgb.g, rgb.b);
animatedColors[i]->setColor(h, currentSaturation, v, 0);
float h = FX_DEG_NORM((color.getHue() + hueOffset) + ((float)i * hueStep));
animatedColors[i]->setColor(h, currentSaturation, v, currentSaturation * 100);
}
}

// animate
hueOffset += (0.128f / pixels->numPixels());
if (hueOffset > 1) hueOffset = 0;
hueRange += (1.5f / pixels->numPixels()) * cursorDirection;
if (hueRange > 5) {
cursorDirection *= -1;
hueRange = 5;
} else if (hueRange < 1) {
cursorDirection *= -1;
hueRange = 1;
// animate
hueOffset += hueStep;
if (hueOffset > 1) FX_DEG_NORM(hueOffset);
hueZoom += (0.05f * (float)cursorDirection);
if (hueZoom > 3) {
cursorDirection = -1;
} else if (hueZoom < 1) {
cursorDirection = 1;
hueZoom = 1;
}
}

}


unsigned long stripe_refresh_ts = 0;
int stripe_delay = 200; // ms
unsigned long stripe_transition = 200; // ms
int stripe_delay = 10; // ms
unsigned int stripe_transition = 150; // ms
int stripe_step = 3;
int stripe_length = 9;
int shift = 0;
float stripe_cycle = 0;
float stripe_previous_hue;

void fx_white_stripes(Adafruit_NeoPixel* pixels, LightColor& color) {
int stripe_length = (int)round((float)pixels->numPixels() / 7.0f);
float shift;

// animate
if (millis() - stripe_refresh_ts > stripe_delay) {

shift = shift % stripe_length;

if (pixels != nullptr) {
for (int i = 0; i < pixels->numPixels(); i++) {
if ((i + shift) % stripe_length < stripe_step) {
// draw stripe
animatedColors[i]->setColor(0, 0, color.getValue(), stripe_transition);
} else {
// draw solid color
animatedColors[i]->setColor(color.getHue(), color.getSaturation(),
color.getValue(), stripe_transition);
}
}
}
//shift = shift % stripe_length;

stripe_cycle += /*color.getValue() * */ ((float)stripe_length / (float)50);
if (stripe_cycle >= stripe_length) stripe_cycle = 0;

shift++;
if (shift >= stripe_length) shift = 0;
stripe_refresh_ts = millis();
}
shift = ((float)stripe_length) + (stripe_cycle * cursorDirection);

cursorDirection = (color.getHue() - stripe_previous_hue > 0) ? 1 : -1;
stripe_previous_hue = color.getHue();

// render
if (pixels != nullptr) {
for (int i = 0; i < pixels->numPixels(); i++) {
pixels->setPixelColor(i, animatedColors[i]->getRed(), animatedColors[i]->getGreen(),
animatedColors[i]->getBlue());
float v = color.getValue();
float s = color.getSaturation();
if ((int)round(i + shift) % stripe_length < stripe_step) {
// draw stripe
animatedColors[i]->setColor(0, 0, 1,
(float)stripe_transition / v);
} else {
// draw solid color
animatedColors[i]->setColor(color.getHue(), s, v,
(float)stripe_transition * v);
}
}

stripe_refresh_ts = millis();
}

}
Expand All @@ -143,19 +146,14 @@ void fx_kaleidoscope(Adafruit_NeoPixel* pixels, LightColor& color) {
for (int i = 0; i < pixels->numPixels(); i++) {
float rnd1 = random(1000);
float rnd2 = random(1000);
animatedColors[i]->setColor(rnd1 / 1000, rnd2 / 1000, color.getValue(), 500);
if ((int)rnd1 % 2 == 0) {
animatedColors[i]->setColor(color.getHue(), color.getSaturation(), color.getValue(), 300);
} else {
animatedColors[i]->setColor(rnd1 / 1000, rnd2 / 1000, color.getValue(), 500);
}
}

kaleidoscope_refresh_ts = millis();
}

// render
if (pixels != nullptr) {
for (int i = 0; i < pixels->numPixels(); i++) {
animatedColors[i]->setValue(color.getValue());
pixels->setPixelColor(i, animatedColors[i]->getRed(), animatedColors[i]->getGreen(),
animatedColors[i]->getBlue());
}
}

}
100 changes: 48 additions & 52 deletions examples/color-light/color-light.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ ColorLight* mainModule;
ModuleParameter* fxStyle;
ModuleParameter* fxStrobe;

bool playFxStrobe;
unsigned long strobeFxTickMs = 0;
unsigned int strobeFxDurationMs = 25;
unsigned int strobeFxIntervalMs = 75; // limit strobe to 10Hz (25+75 -> 100ms interval)

String currentStyle = "solid";

void refresh() {
Expand All @@ -85,9 +88,9 @@ void setup() {
new ModuleParameter("Widget.OptionField.FX.Strobe",
"select:FX.Strobe:strobe_effect:off|slow|medium|fast"));

fxStyle = new ModuleParameter("FX.Style", "solid");
fxStyle = new ModuleParameter("FX.Style", currentStyle);
miniModule->properties.add(fxStyle);
fxStrobe = new ModuleParameter("FX.Strobe", "false");
fxStrobe = new ModuleParameter("FX.Strobe", "off");
miniModule->properties.add(fxStrobe);

// Get status LED config
Expand Down Expand Up @@ -135,17 +138,11 @@ void setup() {
mainModule->onSetColor([](LightColor color) {
currentColor = color;
fx_reset(pixels, color);
fx_solid(pixels, color);
});
homeGenie->addAPIHandler(mainModule);

// Allocate "animated" colors
animatedColors = new AnimatedColor[count];
for (int i = 0; i < count; i++) {
animatedColors[i] = new LightColor();
animatedColors[i]->setColor(currentColor.getHue(), currentColor.getSaturation(), currentColor.getValue(), 0);
}

// Initialize FX buffer
fx_init(count, currentColor);
}

homeGenie->begin();
Expand All @@ -167,70 +164,69 @@ void loop()
homeGenie->loop();

if (isConfigured) {
// refresh background/strobe layer
if (currentColor.isAnimating() && currentStyle == "solid") {
refresh();
}

// 40 fps animation FX layer
if (millis() - lastRefreshTs > refreshMs) {

// Check if current rendering style changed and update fx switches
if (millis() - lastRefreshTs > refreshMs)
{
// Update current rendering style if changed
if (currentStyle != fxStyle->value) {
currentStyle = fxStyle->value;
if (currentStyle == "solid") {
fx_solid(pixels, currentColor);
refresh();
}
}

if (playFxStrobe && fxStrobe->value == "off") {
playFxStrobe = false;
fx_solid(pixels, currentColor);
refresh();
} else if (!playFxStrobe && (fxStrobe->value == "slow" || fxStrobe->value == "medium" || fxStrobe->value == "fast")) {
playFxStrobe = true;
// enable / disable strobe light
if (strobeFxTickMs > 0 && fxStrobe->value == "off") {
strobeFxTickMs = 0;
} else if (strobeFxTickMs == 0 && fxStrobe->value != "off") {
strobeFxTickMs = millis();
}

if (currentStyle != "solid") {
// overlay selected effect
if (currentStyle == "rainbow") {

if (strobeFxTickMs > 0 && millis() - strobeFxTickMs > ((strobeFxDurationMs + strobeFxIntervalMs) * (fxStrobe->value == "slow" ? 3 : fxStrobe->value == "medium" ? 2 : 1))) {

// strobe effect timing tick
strobeFxTickMs = millis();

} else if (strobeFxTickMs > 0 && millis() - strobeFxTickMs <= strobeFxDurationMs) {

// show strobe light for `strobeFxDurationMs` milliseconds (25)
auto c = LightColor();
c.setColor(0, 0, 1, 0);
fx_solid(pixels, c, 0);

} else {

// apply selected light style
if (currentStyle == "solid") {
fx_solid(pixels, currentColor, strobeFxTickMs > 0 ? 0 : 200);
} else if (currentStyle == "rainbow") {
fx_rainbow(pixels, currentColor);
} else if (currentStyle == "kaleidoscope") {
fx_kaleidoscope(pixels, currentColor);
} else if (currentStyle == "white_stripes") {
fx_white_stripes(pixels, currentColor);
}
refresh();

}

if (playFxStrobe) {
float speed = 2;
if (fxStrobe->value == "medium") {
speed = 4;
}
if (fxStrobe->value == "slow") {
speed = 6;

// render pixels
if (pixels != nullptr) {
for (int i = 0; i < pixels->numPixels(); i++) {
pixels->setPixelColor(i,
animatedColors[i]->getRed(),
animatedColors[i]->getGreen(),
animatedColors[i]->getBlue());
}
// TODO: rewrite without using delays
delay(refreshMs * speed);
// invert solid color
auto c = LightColor();
c.setColor(0, 0, 1, 0);
fx_solid(pixels, c);
refresh();
delay(refreshMs);
// restore solid color
fx_solid(pixels, currentColor);
refresh();
delay(refreshMs);
}
// show pixels
refresh();


if (statusLED != nullptr) {
statusLED->setPixelColor(0, currentColor.getRed(), currentColor.getGreen(), currentColor.getBlue());
}

lastRefreshTs = millis();
}

}
}
4 changes: 2 additions & 2 deletions src/service/api/devices/ColorLight.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ namespace Service { namespace API { namespace devices {
return b;
}
private:
float h, s, v;
float oh, os, ov;
float h = 0, s = 0, v = 0;
float oh = 0, os = 0, ov = 0;
unsigned long startTime = -1;
unsigned long duration = 0;
static float hueFix(float h) {
Expand Down
8 changes: 4 additions & 4 deletions src/service/api/devices/Dimmer.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace Service { namespace API { namespace devices {

class DimmerLevel {
public:
unsigned long duration;
unsigned long duration = 0;
bool isAnimating = false;
void setLevel(float l, unsigned long transitionMs) {
duration = transitionMs;
Expand All @@ -55,9 +55,9 @@ namespace Service { namespace API { namespace devices {
return ol + ((level - ol) * getProgress());
}
private:
float level;
float ol;
unsigned long startTime;
float level = 0;
float ol = 0;
unsigned long startTime = -1;

};

Expand Down
2 changes: 1 addition & 1 deletion src/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 30
#define VERSION_PATCH 31

#define STRING_VALUE(...) STRING_VALUE__(__VA_ARGS__)
#define STRING_VALUE__(...) #__VA_ARGS__
Expand Down

0 comments on commit 5ffedd9

Please sign in to comment.