Add SNTP sync for worldwide synchronized animations
This commit is contained in:
parent
0a6e1dc761
commit
5a743d066c
2 changed files with 576 additions and 372 deletions
|
@ -15,6 +15,7 @@ framework = arduino
|
|||
monitor_speed = 115200
|
||||
build_flags =
|
||||
-DCORE_DEBUG_LEVEL=3
|
||||
-DNO_GFX=1
|
||||
lib_deps =
|
||||
mrfaptastic/ESP32 HUB75 LED MATRIX PANEL DMA Display@^3.0.12
|
||||
adafruit/Adafruit GFX Library@^1.12.0
|
||||
|
@ -24,6 +25,6 @@ lib_deps =
|
|||
johboh/MQTTRemote@^5.0.2
|
||||
bblanchon/ArduinoJson@^7.4.1
|
||||
juerd/ESP-WiFiSettings@^3.9.2
|
||||
|
||||
ropg/ezTime@^0.8.3
|
||||
upload_protocol = espota
|
||||
upload_port = transigione-902e41.local
|
|
@ -12,6 +12,7 @@
|
|||
#include <entities/HaEntityLight.h>
|
||||
#include <SPIFFS.h>
|
||||
#include <WiFiSettings.h>
|
||||
#include <ezTime.h>
|
||||
|
||||
// Information about this device.
|
||||
// All these keys will be added to a "device" key in the Home Assistant configuration for each entity.
|
||||
|
@ -19,7 +20,8 @@
|
|||
// We call the setupJsonForThisDevice() from the ardunio setup() function to populate the Json document.
|
||||
// IJsonDocument can be replaced with nlohmann-json::json or ArduinoJson::JsonDocument
|
||||
IJsonDocument _json_this_device_doc;
|
||||
void setupJsonForThisDevice(String name) {
|
||||
void setupJsonForThisDevice(String name)
|
||||
{
|
||||
_json_this_device_doc["identifiers"] = name;
|
||||
_json_this_device_doc["name"] = "Transigione";
|
||||
_json_this_device_doc["sw_version"] = "1.0.0";
|
||||
|
@ -52,13 +54,13 @@ uint16_t myRED = dma_display->color565(255, 0, 0);
|
|||
uint16_t myGREEN = dma_display->color565(0, 255, 0);
|
||||
uint16_t myBLUE = dma_display->color565(0, 0, 255);
|
||||
|
||||
|
||||
#define XMAX 64
|
||||
#define YMAX 32
|
||||
|
||||
#define BRIGHTNESS 127
|
||||
|
||||
void displaySetup() {
|
||||
void displaySetup()
|
||||
{
|
||||
HUB75_I2S_CFG mxconfig(
|
||||
64, // module width
|
||||
32, // module height
|
||||
|
@ -70,7 +72,6 @@ void displaySetup() {
|
|||
// This can be commented out for any smaller displays (but should work fine with it)
|
||||
// mxconfig.gpio.e = 18;
|
||||
|
||||
|
||||
// May or may not be needed depending on your matrix
|
||||
// Example of what needing it looks like:
|
||||
// https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/134#issuecomment-866367216
|
||||
|
@ -84,7 +85,8 @@ void displaySetup() {
|
|||
dma_display->begin();
|
||||
}
|
||||
|
||||
void setup_ota() {
|
||||
void setup_ota()
|
||||
{
|
||||
ArduinoOTA.setHostname(WiFiSettings.hostname.c_str());
|
||||
|
||||
ArduinoOTA.begin();
|
||||
|
@ -95,7 +97,12 @@ uint8_t brightness = BRIGHTNESS;
|
|||
|
||||
std::set<std::string> effect_set{"Transition 1", "Transition 2", "Transition 3", "Transition 4", "Transition 5", "Transition 6"};
|
||||
|
||||
void setup() {
|
||||
uint8_t program = 0;
|
||||
|
||||
uint32_t now_time = 0;
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
Serial.setDebugOutput(true);
|
||||
Serial.println("Moini");
|
||||
|
@ -112,6 +119,8 @@ void setup() {
|
|||
encoder2.attachSingleEdge(32, 33);
|
||||
encoder1.setFilter(1023);
|
||||
encoder2.setFilter(1023);
|
||||
encoder1.setCount(BRIGHTNESS << 1);
|
||||
encoder2.setCount(program);
|
||||
|
||||
SPIFFS.begin(true); // On first run, will format after failing to mount
|
||||
WiFiSettings.hostname = "transigione-";
|
||||
|
@ -121,13 +130,13 @@ void setup() {
|
|||
String mqtt_username = WiFiSettings.string("mqtt_username", "username");
|
||||
String mqtt_password = WiFiSettings.string("mqtt_password", "password");
|
||||
|
||||
|
||||
|
||||
// Set callbacks to start OTA when the portal is active
|
||||
WiFiSettings.onPortal = []() {
|
||||
WiFiSettings.onPortal = []()
|
||||
{
|
||||
setup_ota();
|
||||
};
|
||||
WiFiSettings.onPortalWaitLoop = []() {
|
||||
WiFiSettings.onPortalWaitLoop = []()
|
||||
{
|
||||
ArduinoOTA.handle();
|
||||
};
|
||||
|
||||
|
@ -138,7 +147,8 @@ void setup() {
|
|||
_ha_entity_light = new HaEntityLight(*ha_bridge, "", "light", {.with_brightness = true, .effects = effect_set});
|
||||
|
||||
ArduinoOTA
|
||||
.onStart([]() {
|
||||
.onStart([]()
|
||||
{
|
||||
String type;
|
||||
if (ArduinoOTA.getCommand() == U_FLASH) {
|
||||
type = "sketch";
|
||||
|
@ -147,15 +157,13 @@ void setup() {
|
|||
}
|
||||
|
||||
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
|
||||
Serial.println("Start updating " + type);
|
||||
})
|
||||
.onEnd([]() {
|
||||
Serial.println("\nEnd");
|
||||
})
|
||||
.onProgress([](unsigned int progress, unsigned int total) {
|
||||
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
|
||||
})
|
||||
.onError([](ota_error_t error) {
|
||||
Serial.println("Start updating " + type); })
|
||||
.onEnd([]()
|
||||
{ Serial.println("\nEnd"); })
|
||||
.onProgress([](unsigned int progress, unsigned int total)
|
||||
{ Serial.printf("Progress: %u%%\r", (progress / (total / 100))); })
|
||||
.onError([](ota_error_t error)
|
||||
{
|
||||
Serial.printf("Error[%u]: ", error);
|
||||
if (error == OTA_AUTH_ERROR) {
|
||||
Serial.println("Auth Failed");
|
||||
|
@ -167,15 +175,12 @@ void setup() {
|
|||
Serial.println("Receive Failed");
|
||||
} else if (error == OTA_END_ERROR) {
|
||||
Serial.println("End Failed");
|
||||
}
|
||||
});
|
||||
|
||||
} });
|
||||
|
||||
// Can be set between 0 and 255
|
||||
// WARNING: The birghter it is, the more power it uses
|
||||
// Could take up to 3A on full brightness
|
||||
dma_display->setBrightness8(BRIGHTNESS); //0-255
|
||||
|
||||
dma_display->setBrightness8(brightness); // 0-255
|
||||
|
||||
dma_display->clearScreen();
|
||||
|
||||
|
@ -183,7 +188,12 @@ void setup() {
|
|||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
_mqtt_remote->setOnConnectionChange([](bool connected) {
|
||||
// Time:
|
||||
setInterval(600);
|
||||
waitForSync(5);
|
||||
|
||||
_mqtt_remote->setOnConnectionChange([](bool connected)
|
||||
{
|
||||
if (connected) {
|
||||
Serial.println("We are in");
|
||||
// Publish Home Assistant Configuration for both lights once connected to MQTT.
|
||||
|
@ -197,19 +207,17 @@ void setup() {
|
|||
_ha_entity_light->publishIsOn(power);
|
||||
});
|
||||
_ha_entity_light->setOnBrightness(
|
||||
[&](uint8_t b) { Serial.println("Got brightness " + String(b) + " for light"); brightness = b >> 1;
|
||||
_ha_entity_light->publishBrightness(b); });
|
||||
[&](uint8_t b) {
|
||||
encoder1.setCount((int64_t) b);
|
||||
});
|
||||
|
||||
_ha_entity_light->setOnEffect([&](std::string effect) {
|
||||
if(auto search = effect_set.find(effect); search != effect_set.end()) {
|
||||
size_t index = std::distance(effect_set.begin(), search);
|
||||
encoder2.setCount((int64_t) index);
|
||||
std::string item = *search;
|
||||
_ha_entity_light->publishEffect(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} });
|
||||
|
||||
setup_ota();
|
||||
}
|
||||
|
@ -221,33 +229,62 @@ void transition4();
|
|||
void transition5();
|
||||
void transition6();
|
||||
|
||||
uint8_t parameter = 0;
|
||||
uint8_t program = 0;
|
||||
void loop() {
|
||||
void loop()
|
||||
{
|
||||
ArduinoOTA.handle();
|
||||
_mqtt_remote->handle();
|
||||
|
||||
encoder1Button.loop();
|
||||
encoder2Button.loop();
|
||||
|
||||
if (encoder1Button.isPressed() || encoder2Button.isPressed()) {
|
||||
// eztime
|
||||
events();
|
||||
now_time = UTC.now() * 1000 + UTC.ms();
|
||||
|
||||
if (encoder1Button.isPressed() || encoder2Button.isPressed())
|
||||
{
|
||||
power = !power;
|
||||
_ha_entity_light->publishIsOn(power);
|
||||
}
|
||||
|
||||
if (encoder1.getCount() > 255)
|
||||
{
|
||||
encoder1.setCount(255);
|
||||
}
|
||||
if (encoder1.getCount() < 0)
|
||||
{
|
||||
encoder1.setCount(0);
|
||||
}
|
||||
|
||||
if (brightness != encoder1.getCount())
|
||||
{
|
||||
brightness = encoder1.getCount() >> 1;
|
||||
_ha_entity_light->publishBrightness(brightness << 1);
|
||||
}
|
||||
|
||||
dma_display->setBrightness8(power ? brightness : 0);
|
||||
|
||||
if (program != encoder2.getCount() % effect_set.size()) {
|
||||
program = encoder2.getCount() % effect_set.size();
|
||||
if (encoder2.getCount() < 0)
|
||||
{
|
||||
encoder2.setCount(effect_set.size() - 1);
|
||||
}
|
||||
|
||||
if (encoder2.getCount() > effect_set.size() - 1)
|
||||
{
|
||||
encoder2.setCount(0);
|
||||
}
|
||||
|
||||
if (program != encoder2.getCount())
|
||||
{
|
||||
program = encoder2.getCount();
|
||||
auto it = std::next(effect_set.begin(), program);
|
||||
_ha_entity_light->publishEffect(*it);
|
||||
}
|
||||
|
||||
parameter = encoder1.getCount();
|
||||
|
||||
switch (program) {
|
||||
switch (program)
|
||||
{
|
||||
case 0:
|
||||
transition6();
|
||||
transition1();
|
||||
break;
|
||||
case 1:
|
||||
transition2();
|
||||
|
@ -262,12 +299,13 @@ void loop() {
|
|||
transition5();
|
||||
break;
|
||||
case 5:
|
||||
transition1();
|
||||
transition6();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t interpolateRGB565(uint16_t color1, uint16_t color2, uint8_t t) {
|
||||
uint16_t interpolateRGB565(uint16_t color1, uint16_t color2, uint8_t t)
|
||||
{
|
||||
// Extract the RGB components from color1
|
||||
uint8_t r1 = (color1 >> 11) & 0x1F;
|
||||
uint8_t g1 = (color1 >> 5) & 0x3F;
|
||||
|
@ -288,13 +326,10 @@ uint16_t interpolateRGB565(uint16_t color1, uint16_t color2, uint8_t t) {
|
|||
uint8_t g = g1 + ((gdiff * t + 128) >> 8);
|
||||
uint8_t b = b1 + ((bdiff * t + 128) >> 8);
|
||||
|
||||
|
||||
// Combine the components back into a 16-bit RGB565 color
|
||||
return ((uint16_t)r << 11) | ((uint16_t)g << 5) | b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Interpolates between two RGB565 colors using HSV color space with fixed-point arithmetic.
|
||||
* No floating point operations are used. Ensures smooth color transitions.
|
||||
|
@ -304,7 +339,8 @@ uint16_t interpolateRGB565(uint16_t color1, uint16_t color2, uint8_t t) {
|
|||
* @param t Interpolation factor from 0 to 255 (0 = color1, 255 = color2)
|
||||
* @return Interpolated color in RGB565 format
|
||||
*/
|
||||
uint16_t interpolateHSV565(uint16_t color1, uint16_t color2, uint8_t t) {
|
||||
uint16_t interpolateHSV565(uint16_t color1, uint16_t color2, uint8_t t)
|
||||
{
|
||||
// Extract RGB components from RGB565 colors
|
||||
uint8_t r1 = (color1 >> 11) & 0x1F;
|
||||
uint8_t g1 = (color1 >> 5) & 0x3F;
|
||||
|
@ -339,16 +375,24 @@ uint16_t interpolateHSV565(uint16_t color1, uint16_t color2, uint8_t t) {
|
|||
s1 = max1 == 0 ? 0 : ((uint32_t)delta1 * 255) / max1;
|
||||
|
||||
// Hue (scaled to 0-1535 for fixed-point representation, 0-360 degrees * 4.26)
|
||||
if (delta1 == 0) {
|
||||
if (delta1 == 0)
|
||||
{
|
||||
h1 = 0; // undefined, default to red
|
||||
} else if (max1 == r1) {
|
||||
}
|
||||
else if (max1 == r1)
|
||||
{
|
||||
int16_t diff = g1 - b1;
|
||||
h1 = 0 + (((int32_t)diff * 256) / delta1);
|
||||
if (h1 < 0) h1 += 1536;
|
||||
} else if (max1 == g1) {
|
||||
if (h1 < 0)
|
||||
h1 += 1536;
|
||||
}
|
||||
else if (max1 == g1)
|
||||
{
|
||||
int16_t diff = b1 - r1;
|
||||
h1 = 512 + (((int32_t)diff * 256) / delta1);
|
||||
} else { // max1 == b1
|
||||
}
|
||||
else
|
||||
{ // max1 == b1
|
||||
int16_t diff = r1 - g1;
|
||||
h1 = 1024 + (((int32_t)diff * 256) / delta1);
|
||||
}
|
||||
|
@ -361,36 +405,50 @@ uint16_t interpolateHSV565(uint16_t color1, uint16_t color2, uint8_t t) {
|
|||
v2 = max2;
|
||||
s2 = max2 == 0 ? 0 : ((uint32_t)delta2 * 255) / max2;
|
||||
|
||||
if (delta2 == 0) {
|
||||
if (delta2 == 0)
|
||||
{
|
||||
h2 = 0;
|
||||
} else if (max2 == r2) {
|
||||
}
|
||||
else if (max2 == r2)
|
||||
{
|
||||
int16_t diff = g2 - b2;
|
||||
h2 = 0 + (((int32_t)diff * 256) / delta2);
|
||||
if (h2 < 0) h2 += 1536;
|
||||
} else if (max2 == g2) {
|
||||
if (h2 < 0)
|
||||
h2 += 1536;
|
||||
}
|
||||
else if (max2 == g2)
|
||||
{
|
||||
int16_t diff = b2 - r2;
|
||||
h2 = 512 + (((int32_t)diff * 256) / delta2);
|
||||
} else { // max2 == b2
|
||||
}
|
||||
else
|
||||
{ // max2 == b2
|
||||
int16_t diff = r2 - g2;
|
||||
h2 = 1024 + (((int32_t)diff * 256) / delta2);
|
||||
}
|
||||
|
||||
// Handle special case for grayscale colors (undefined hue)
|
||||
if (s1 < 8) h1 = h2;
|
||||
if (s2 < 8) h2 = h1;
|
||||
if (s1 < 8)
|
||||
h1 = h2;
|
||||
if (s2 < 8)
|
||||
h2 = h1;
|
||||
|
||||
// Interpolate HSV components
|
||||
uint16_t h_interp, s_interp, v_interp;
|
||||
|
||||
// Hue needs special handling for the shortest path around the circle
|
||||
int16_t h_diff = h2 - h1;
|
||||
if (h_diff > 768) h_diff -= 1536;
|
||||
else if (h_diff < -768) h_diff += 1536;
|
||||
if (h_diff > 768)
|
||||
h_diff -= 1536;
|
||||
else if (h_diff < -768)
|
||||
h_diff += 1536;
|
||||
|
||||
// Use 32-bit arithmetic for the interpolation to avoid overflow issues
|
||||
h_interp = h1 + (((int32_t)h_diff * t) >> 8);
|
||||
if (h_interp >= 1536) h_interp -= 1536;
|
||||
else if (h_interp < 0) h_interp += 1536;
|
||||
if (h_interp >= 1536)
|
||||
h_interp -= 1536;
|
||||
else if (h_interp < 0)
|
||||
h_interp += 1536;
|
||||
|
||||
s_interp = s1 + (((int32_t)(s2 - s1) * t) >> 8);
|
||||
v_interp = v1 + (((int32_t)(v2 - v1) * t) >> 8);
|
||||
|
@ -406,7 +464,8 @@ uint16_t interpolateHSV565(uint16_t color1, uint16_t color2, uint8_t t) {
|
|||
uint32_t q = ((uint32_t)v_interp * (255 - ((s_interp * remainder) / 256))) / 255;
|
||||
uint32_t t_val = ((uint32_t)v_interp * (255 - ((s_interp * (255 - remainder)) / 256))) / 255;
|
||||
|
||||
switch (region) {
|
||||
switch (region)
|
||||
{
|
||||
case 0:
|
||||
r = v_interp;
|
||||
g = t_val;
|
||||
|
@ -447,7 +506,8 @@ uint16_t interpolateHSV565(uint16_t color1, uint16_t color2, uint8_t t) {
|
|||
return (r << 11) | (g << 5) | b;
|
||||
}
|
||||
|
||||
uint16_t RGB888ToRGB565(uint32_t rgb888) {
|
||||
uint16_t RGB888ToRGB565(uint32_t rgb888)
|
||||
{
|
||||
// Extract 8-bit color components
|
||||
uint8_t red = (rgb888 >> 16) & 0xFF;
|
||||
uint8_t green = (rgb888 >> 8) & 0xFF;
|
||||
|
@ -462,96 +522,243 @@ uint16_t RGB888ToRGB565(uint32_t rgb888) {
|
|||
return (r << 11) | (g << 5) | b;
|
||||
}
|
||||
|
||||
void transition1() {
|
||||
// Fast sine approximation function for 16-bit angle (0-65535)
|
||||
// Returns a signed 16-bit value (-32768 to 32767)
|
||||
int16_t sine_approx(uint16_t angle)
|
||||
{
|
||||
// Convert angle to 0-255 for table lookup
|
||||
uint8_t index = (angle >> 8);
|
||||
|
||||
const int color1 = dma_display->color565(0xdf, 0xf5, 0x4f);
|
||||
const int color2 = dma_display->color565(0x8d, 0x1f, 0xf2);
|
||||
// Simple lookup table (quarter sine wave)
|
||||
static const uint8_t sine_table[64] = {
|
||||
0, 6, 13, 19, 25, 31, 37, 44, 50, 56, 62, 68, 74, 80, 86, 92,
|
||||
98, 103, 109, 115, 120, 126, 131, 136, 142, 147, 152, 157, 162, 167, 171, 176,
|
||||
181, 185, 189, 193, 197, 201, 205, 209, 212, 216, 219, 222, 225, 228, 231, 234,
|
||||
236, 238, 241, 243, 244, 246, 248, 249, 251, 252, 253, 254, 254, 255, 255, 255};
|
||||
|
||||
static uint8_t state[YMAX];
|
||||
static unsigned long last_move_time = 0;
|
||||
if (last_move_time == 0) {
|
||||
for (int y = 0; y < YMAX; y++) {
|
||||
state[y] = 127;
|
||||
}
|
||||
}
|
||||
if (last_move_time + ((parameter + 50) % 255) < millis()) {
|
||||
for (int y = 0; y < YMAX; y++) {
|
||||
int move_length = rand() - RAND_MAX / 2;
|
||||
int dir = state[y] < 64 || state[y] > 191 ? 4 : 1;
|
||||
if (move_length > 0) {
|
||||
dir = -dir;
|
||||
}
|
||||
state[y] += dir;
|
||||
if (state[y] < 0) state[y] = 0;
|
||||
if (state[y] > 255) state[y] = 255;
|
||||
}
|
||||
last_move_time = millis();
|
||||
// Get table value based on quadrant
|
||||
uint8_t quadrant = index >> 6;
|
||||
uint8_t table_index = index & 0x3F;
|
||||
if (quadrant & 1)
|
||||
table_index = 63 - table_index; // Flip index for Q1 and Q3
|
||||
uint8_t value = sine_table[table_index];
|
||||
|
||||
// Apply sign based on quadrant
|
||||
int16_t result = (quadrant < 2) ? value : -value;
|
||||
|
||||
// Scale to full 16-bit range
|
||||
return result << 8;
|
||||
}
|
||||
|
||||
for (int x = 0; x < XMAX; x++) {
|
||||
for(int y = 0; y < YMAX; y++) {
|
||||
int t = x << 2;
|
||||
void transition1()
|
||||
{
|
||||
const int color1 = dma_display->color565(0xdf, 0xf5, 0x4f); // Yellow
|
||||
const int color2 = dma_display->color565(0x8d, 0x1f, 0xf2); // Purple
|
||||
const int color3 = dma_display->color565(0x6f, 0x55, 0xff); // Blue
|
||||
const int color4 = dma_display->color565(0x1d, 0x9f, 0x23); // Green
|
||||
static unsigned long last_step = 0;
|
||||
extern unsigned long now_time;
|
||||
|
||||
t += state[y] - 127;
|
||||
// Fixed-point constants (8.8 format)
|
||||
#define FP_SHIFT 8
|
||||
#define FP_SCALE (1 << FP_SHIFT)
|
||||
#define FP_HALF (FP_SCALE / 2)
|
||||
#define FP_ONE FP_SCALE
|
||||
|
||||
if (t <= 0) t = 1;
|
||||
if (t >= 255) t = 254;
|
||||
// Animation speed constant
|
||||
const unsigned long ANIMATION_SPEED = 30;
|
||||
|
||||
int color = interpolateHSV565(color1, color2, t);
|
||||
// Transition zone width
|
||||
const int TRANSITION_WIDTH = XMAX / 3;
|
||||
|
||||
dma_display->drawPixel(x, y, color);
|
||||
// Pixel block size (for pixelation effect)
|
||||
const int MIN_BLOCK_SIZE = 2;
|
||||
const int MAX_BLOCK_SIZE = 5;
|
||||
|
||||
// Continuous phase values for smooth color cycling (avoids jumps)
|
||||
static uint16_t phase1 = 0;
|
||||
static uint16_t phase2 = 0x8000; // Start at opposite phase (32768)
|
||||
|
||||
if (last_step + ANIMATION_SPEED < now_time)
|
||||
{
|
||||
// Update phase values - use continuous movement
|
||||
phase1 = (phase1 + 64) % 65536; // Move through full 16-bit value space
|
||||
phase2 = (phase2 + 48) % 65536; // Different speed for more variation
|
||||
|
||||
last_step = now_time;
|
||||
|
||||
// Convert phases to smooth oscillating values (0-255)
|
||||
// Using sine approximation to avoid jumps
|
||||
uint8_t t1 = (uint8_t)(128 + ((sine_approx(phase1) * 127) >> 16));
|
||||
uint8_t t2 = (uint8_t)(128 + ((sine_approx(phase2) * 127) >> 16));
|
||||
|
||||
// Center line with small time-based variation
|
||||
int centerX = XMAX / 2 + (((now_time / 307) % 7) - 3);
|
||||
|
||||
// Pre-calculate base colors for both visualizations
|
||||
// First visualization - horizontal gradient with smooth time changes
|
||||
int viz1_colorL = interpolateHSV565(color1, color3, t1);
|
||||
int viz1_colorR = interpolateHSV565(color2, color4, t1);
|
||||
|
||||
// Second visualization - vertical gradient with different timing
|
||||
int viz2_colorT = interpolateHSV565(color3, color1, t2);
|
||||
int viz2_colorB = interpolateHSV565(color4, color2, t2);
|
||||
|
||||
// Time value for pixelation pattern - change slowly to avoid flicker
|
||||
uint32_t pattern_time = now_time / 20000;
|
||||
|
||||
// Create the visualization
|
||||
for (int x = 0; x < XMAX; x++)
|
||||
{
|
||||
// Calculate horizontal interpolation factor (0-255)
|
||||
uint8_t x_factor = (x * 255) / (XMAX - 1);
|
||||
|
||||
// Calculate distance from center
|
||||
int dist_from_center = abs(x - centerX);
|
||||
|
||||
// Determine if we're in the transition zone
|
||||
bool in_transition = (dist_from_center <= TRANSITION_WIDTH);
|
||||
|
||||
// Calculate base transition factor (0-255)
|
||||
uint8_t trans_factor = in_transition ? (255 * dist_from_center) / TRANSITION_WIDTH : 255;
|
||||
if (x < centerX)
|
||||
trans_factor = 255 - trans_factor;
|
||||
|
||||
for (int y = 0; y < YMAX; y++)
|
||||
{
|
||||
// Calculate vertical interpolation factor (0-255)
|
||||
uint8_t y_factor = (y * 255) / (YMAX - 1);
|
||||
|
||||
// Generate colors for both visualizations
|
||||
// Visualization 1 - horizontal gradient
|
||||
int viz1_color = interpolateRGB565(viz1_colorL, viz1_colorR, x_factor);
|
||||
|
||||
// Visualization 2 - vertical gradient
|
||||
int viz2_color = interpolateRGB565(viz2_colorT, viz2_colorB, y_factor);
|
||||
|
||||
// Default to smooth transition outside the transition zone
|
||||
int final_color;
|
||||
|
||||
if (in_transition)
|
||||
{
|
||||
// In transition zone - create pixelated effect
|
||||
|
||||
// Determine block size based on position and time
|
||||
// Hash coordinates with time to get stable blocks
|
||||
uint32_t pos_hash = (x * 127 + y * 73 + pattern_time) % 997;
|
||||
|
||||
// Calculate block coordinates - different sized blocks
|
||||
int block_size = MIN_BLOCK_SIZE + (pos_hash % (MAX_BLOCK_SIZE - MIN_BLOCK_SIZE + 1));
|
||||
int block_x = (x / block_size) * block_size;
|
||||
int block_y = (y / block_size) * block_size;
|
||||
|
||||
// Create a unique hash for each block
|
||||
uint32_t block_hash = (block_x * 31 + block_y * 17 + pattern_time) % 1024;
|
||||
|
||||
// Determine which visualization to use for this block
|
||||
// Make decision based on position and a pseudo-random factor
|
||||
bool use_viz2;
|
||||
|
||||
// Use different strategies in different areas of the transition zone
|
||||
if (dist_from_center < TRANSITION_WIDTH / 3)
|
||||
{
|
||||
// Inner third - mostly random distribution
|
||||
use_viz2 = (block_hash % 256) < trans_factor;
|
||||
}
|
||||
else if (dist_from_center < 2 * TRANSITION_WIDTH / 3)
|
||||
{
|
||||
// Middle third - more structured but still random
|
||||
int base_factor = trans_factor + ((block_hash % 64) - 32);
|
||||
if (base_factor < 0)
|
||||
base_factor = 0;
|
||||
if (base_factor > 255)
|
||||
base_factor = 255;
|
||||
use_viz2 = (block_hash % 256) < base_factor;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Outer third - mostly follow transition factor with small variation
|
||||
int base_factor = trans_factor + ((block_hash % 32) - 16);
|
||||
if (base_factor < 0)
|
||||
base_factor = 0;
|
||||
if (base_factor > 255)
|
||||
base_factor = 255;
|
||||
use_viz2 = (block_hash % 256) < base_factor;
|
||||
}
|
||||
|
||||
// Apply the selected visualization for this block
|
||||
final_color = use_viz2 ? viz2_color : viz1_color;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Outside transition zone - smooth gradient
|
||||
final_color = (x < centerX) ? viz1_color : viz2_color;
|
||||
}
|
||||
|
||||
dma_display->drawPixel(x, y, final_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void transition2() {
|
||||
void transition2()
|
||||
{
|
||||
const int color1 = dma_display->color565(0xdf, 0xf5, 0x4f);
|
||||
const int color2 = dma_display->color565(0x8d, 0x1f, 0xf2);
|
||||
const int color3 = dma_display->color565(0x6f, 0x55, 0xff);
|
||||
const int color4 = dma_display->color565(0x1d, 0x9f, 0x23);
|
||||
static unsigned long last_step = 0;
|
||||
extern unsigned long now_time; // Reference to global variable
|
||||
|
||||
// Animation speed constant (replaces parameter-based calculation)
|
||||
const unsigned long ANIMATION_SPEED = 30; // Adjust this value as needed
|
||||
|
||||
static uint8_t t1 = 0;
|
||||
static uint8_t t2 = 254;
|
||||
static int8_t dir1 = 1;
|
||||
static int8_t dir2 = 1;
|
||||
|
||||
if (last_step + (255-parameter) < millis()) {
|
||||
if (last_step + ANIMATION_SPEED < now_time)
|
||||
{
|
||||
t1 += dir1;
|
||||
t2 += dir2;
|
||||
if (t1 == 255) {
|
||||
if (t1 == 255)
|
||||
{
|
||||
t1 = 254;
|
||||
dir1 = -1;
|
||||
}
|
||||
if (t1 == 0) {
|
||||
if (t1 == 0)
|
||||
{
|
||||
t1 = 1;
|
||||
dir1 = 1;
|
||||
}
|
||||
if (t2 == 255) {
|
||||
if (t2 == 255)
|
||||
{
|
||||
t2 = 254;
|
||||
dir2 = -1;
|
||||
}
|
||||
if (t2 == 0) {
|
||||
if (t2 == 0)
|
||||
{
|
||||
t2 = 1;
|
||||
dir2 = 1;
|
||||
}
|
||||
last_step = millis();
|
||||
|
||||
for (int x = 0; x < XMAX; x++) {
|
||||
for(int y = 0; y < YMAX; y++) {
|
||||
last_step = now_time;
|
||||
|
||||
for (int x = 0; x < XMAX; x++)
|
||||
{
|
||||
for (int y = 0; y < YMAX; y++)
|
||||
{
|
||||
int colorA = interpolateHSV565(color1, color2, t1);
|
||||
int colorB = interpolateHSV565(color3, color4, t2);
|
||||
|
||||
int color = interpolateRGB565(colorA, colorB, x << 2);
|
||||
|
||||
dma_display->drawPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void transition3() {
|
||||
void transition3()
|
||||
{
|
||||
// 0x274001, 0x828a00, 0xf29f05, 0xf25c05, 0xd6568c, 0x4d8584, 0xa62f03, 0x400d01
|
||||
const int color1 = RGB888ToRGB565(0x274001);
|
||||
const int color2 = RGB888ToRGB565(0x828a00);
|
||||
|
@ -561,37 +768,29 @@ void transition3() {
|
|||
const int color6 = RGB888ToRGB565(0x4d8584);
|
||||
const int color7 = RGB888ToRGB565(0xa62f03);
|
||||
const int color8 = RGB888ToRGB565(0x400d01);
|
||||
static unsigned long last_step = 0;
|
||||
|
||||
static uint8_t t1 = 0;
|
||||
static uint8_t t2 = 0;
|
||||
static int8_t dir1 = 1;
|
||||
static int8_t dir2 = 1;
|
||||
// Use different cycle times for t1 and t2 to avoid synchronization
|
||||
const uint32_t cycle_time1 = 25500;
|
||||
const uint32_t cycle_time2 = 33100; // Use a different period to create more variety
|
||||
|
||||
if (last_step + 100 < millis()) {
|
||||
t1 += dir1;
|
||||
t2 += dir2;
|
||||
if (t1 == 255) {
|
||||
t1 = 254;
|
||||
dir1 = -1;
|
||||
}
|
||||
if (t1 == 0) {
|
||||
t1 = 1;
|
||||
dir1 = 1;
|
||||
}
|
||||
if (t2 == 255) {
|
||||
t2 = 254;
|
||||
dir2 = -1;
|
||||
}
|
||||
if (t2 == 0) {
|
||||
t2 = 1;
|
||||
dir2 = 1;
|
||||
}
|
||||
last_step = millis();
|
||||
// Use a sine wave function for smooth transitions
|
||||
float angle1 = (float)(now_time % cycle_time1) / cycle_time1 * 2 * PI;
|
||||
float angle2 = (float)(now_time % cycle_time2) / cycle_time2 * 2 * PI;
|
||||
|
||||
for (int x = 0; x < XMAX; x++) {
|
||||
for(int y = 0; y < YMAX; y++) {
|
||||
// Using sine to get a value between -1 and 1, then scale to 1-254
|
||||
// The sine function ensures a smooth transition with no abrupt changes
|
||||
uint8_t t1 = (uint8_t)(127.5f * (sinf(angle1) + 1.0f)) + 1;
|
||||
uint8_t t2 = (uint8_t)(127.5f * (sinf(angle2) + 1.0f)) + 1;
|
||||
|
||||
// Ensure t1 and t2 stay within 1-254 range
|
||||
t1 = constrain(t1, 1, 254);
|
||||
t2 = constrain(t2, 1, 254);
|
||||
|
||||
// Render the display
|
||||
for (int x = 0; x < XMAX; x++)
|
||||
{
|
||||
for (int y = 0; y < YMAX; y++)
|
||||
{
|
||||
int colorA = interpolateHSV565(color1, color5, t1);
|
||||
int colorB = interpolateHSV565(color2, color6, t2);
|
||||
int colorC = interpolateHSV565(color3, color7, t1);
|
||||
|
@ -606,9 +805,9 @@ void transition3() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void transition4() {
|
||||
void transition4()
|
||||
{
|
||||
const uint8_t NUM_COLORS = 4;
|
||||
const uint8_t NUM_PALETTES = 8;
|
||||
// Color palettes (up to 4 colors per palette)
|
||||
|
@ -628,13 +827,12 @@ const uint16_t palettes[NUM_PALETTES][NUM_COLORS] = {
|
|||
// 6: Neon
|
||||
{0xF81F, 0x07FF, 0xFFE0, 0x07E0},
|
||||
// 7: Grayscale
|
||||
{ 0x0000, 0x528A, 0xAD55, 0xFFFF }
|
||||
};
|
||||
{0x0000, 0x528A, 0xAD55, 0xFFFF}};
|
||||
|
||||
// Select the active palette here (index 0-7)
|
||||
uint8_t currentPaletteIndex = parameter % 8;
|
||||
uint8_t currentPaletteIndex = (now_time / (1000 * 60 * 20)) % 8;
|
||||
const uint32_t TRANSITION_DURATION = 200000UL;
|
||||
uint32_t t = millis();
|
||||
uint32_t t = now_time;
|
||||
const uint16_t *currentPalette = palettes[currentPaletteIndex];
|
||||
|
||||
uint32_t totalCycle = NUM_COLORS * TRANSITION_DURATION;
|
||||
|
@ -646,8 +844,10 @@ uint8_t currentPaletteIndex = parameter % 8;
|
|||
uint16_t timeInStep = localTime % TRANSITION_DURATION;
|
||||
uint8_t blendT = (timeInStep * 255UL) / TRANSITION_DURATION;
|
||||
|
||||
for (int x = 0; x < XMAX; x++) {
|
||||
for (int y = 0; y < YMAX; y++) {
|
||||
for (int x = 0; x < XMAX; x++)
|
||||
{
|
||||
for (int y = 0; y < YMAX; y++)
|
||||
{
|
||||
uint8_t offset = ((x + y) * 2 + (t >> 4)) & 0x3F;
|
||||
uint8_t finalBlend = (blendT + offset) & 0xFF;
|
||||
|
||||
|
@ -657,8 +857,8 @@ uint8_t currentPaletteIndex = parameter % 8;
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void transition5() {
|
||||
void transition5()
|
||||
{
|
||||
static const uint8_t NUM_COLORS = 4;
|
||||
static const uint8_t NUM_PALETTES = 8;
|
||||
static const uint32_t COLOR_DURATION = 100000UL; // 100s per color blend
|
||||
|
@ -676,7 +876,7 @@ void transition5() {
|
|||
{0x7800, 0x780F, 0xFD20, 0xFBE0} // Sunset
|
||||
};
|
||||
|
||||
uint32_t t = millis();
|
||||
uint32_t t = now_time;
|
||||
|
||||
// Time into palette blend cycle
|
||||
uint32_t paletteTime = t % PALETTE_BLEND_DURATION;
|
||||
|
@ -696,20 +896,22 @@ void transition5() {
|
|||
|
||||
// Blend the two palettes into a temporary working palette
|
||||
uint16_t blendedPalette[NUM_COLORS];
|
||||
for (uint8_t i = 0; i < NUM_COLORS; i++) {
|
||||
for (uint8_t i = 0; i < NUM_COLORS; i++)
|
||||
{
|
||||
blendedPalette[i] = interpolateRGB565(
|
||||
palettes[paletteIndex1][i],
|
||||
palettes[paletteIndex2][i],
|
||||
paletteBlendT
|
||||
);
|
||||
paletteBlendT);
|
||||
}
|
||||
|
||||
// Center coordinates
|
||||
int16_t centerX = XMAX / 2;
|
||||
int16_t centerY = YMAX / 2;
|
||||
|
||||
for (int x = 0; x < XMAX; x++) {
|
||||
for (int y = 0; y < YMAX; y++) {
|
||||
for (int x = 0; x < XMAX; x++)
|
||||
{
|
||||
for (int y = 0; y < YMAX; y++)
|
||||
{
|
||||
int16_t dx = x - centerX;
|
||||
int16_t dy = y - centerY;
|
||||
|
||||
|
@ -720,16 +922,15 @@ void transition5() {
|
|||
uint16_t color = interpolateRGB565(
|
||||
blendedPalette[colorIndex1],
|
||||
blendedPalette[colorIndex2],
|
||||
finalBlend
|
||||
);
|
||||
finalBlend);
|
||||
|
||||
dma_display->drawPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void transition6() {
|
||||
void transition6()
|
||||
{
|
||||
static const uint8_t NUM_COLORS = 4;
|
||||
static const uint8_t NUM_SEGMENTS = NUM_COLORS; // for smooth wrap (4 segments: 0→1→2→3→0)
|
||||
static const uint8_t NUM_PALETTES = 8;
|
||||
|
@ -748,7 +949,7 @@ void transition6() {
|
|||
{{214, 46, 0}, {255, 90, 54}, {255, 154, 139}, {209, 98, 169}} // Lesbian
|
||||
};
|
||||
|
||||
uint32_t t = millis();
|
||||
uint32_t t = now_time;
|
||||
|
||||
// Palette transition
|
||||
uint32_t paletteTime = t % PALETTE_BLEND_DURATION;
|
||||
|
@ -758,7 +959,8 @@ void transition6() {
|
|||
|
||||
// Create blended palette with wrap
|
||||
uint16_t blendedPalette[NUM_COLORS + 1]; // add one for wraparound
|
||||
for (uint8_t i = 0; i < NUM_COLORS; i++) {
|
||||
for (uint8_t i = 0; i < NUM_COLORS; i++)
|
||||
{
|
||||
uint32_t rgb1 = (palettes[paletteIndex1][i][0] << 16) |
|
||||
(palettes[paletteIndex1][i][1] << 8) |
|
||||
palettes[paletteIndex1][i][2];
|
||||
|
@ -777,10 +979,12 @@ void transition6() {
|
|||
uint16_t segmentWidth = gradientWidth / NUM_SEGMENTS;
|
||||
uint16_t scrollOffset = (uint32_t)(t % COLOR_DURATION) * gradientWidth / COLOR_DURATION;
|
||||
|
||||
for (int x = 0; x < XMAX; x++) {
|
||||
for (int x = 0; x < XMAX; x++)
|
||||
{
|
||||
uint16_t gradientX = (x + scrollOffset) % gradientWidth;
|
||||
uint8_t segment = gradientX / segmentWidth;
|
||||
if (segment >= NUM_SEGMENTS) segment = NUM_SEGMENTS - 1;
|
||||
if (segment >= NUM_SEGMENTS)
|
||||
segment = NUM_SEGMENTS - 1;
|
||||
|
||||
uint8_t localX = gradientX - segment * segmentWidth;
|
||||
uint8_t blendFactor = (localX * 255) / segmentWidth;
|
||||
|
@ -788,12 +992,11 @@ void transition6() {
|
|||
uint16_t color = interpolateRGB565(
|
||||
blendedPalette[segment],
|
||||
blendedPalette[segment + 1],
|
||||
blendFactor
|
||||
);
|
||||
blendFactor);
|
||||
|
||||
for (int y = 0; y < YMAX; y++) {
|
||||
for (int y = 0; y < YMAX; y++)
|
||||
{
|
||||
dma_display->drawPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue