diff --git a/transigione/platformio.ini b/transigione/platformio.ini index 3485e00..74fa393 100644 --- a/transigione/platformio.ini +++ b/transigione/platformio.ini @@ -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 +upload_port = transigione-902e41.local \ No newline at end of file diff --git a/transigione/src/main.cpp b/transigione/src/main.cpp index a8571f3..cb65e85 100644 --- a/transigione/src/main.cpp +++ b/transigione/src/main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include // 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"; @@ -27,17 +29,17 @@ void setupJsonForThisDevice(String name) { _json_this_device_doc["manufacturer"] = "jhbruhn"; } -MQTTRemote* _mqtt_remote; +MQTTRemote *_mqtt_remote; // Create the Home Assistant bridge. This is shared across all entities. // We only have one per device/hardware. In our example, the name of our device is "livingroom". // See constructor of HaBridge for more documentation. -HaBridge* ha_bridge; +HaBridge *ha_bridge; // Create the two lights with the "Human readable" strings. This what will show up in Home Assistant. // As we have two entities of the same type (light) for the same device, we need to add a child object // id to separate them. -HaEntityLight* _ha_entity_light; +HaEntityLight *_ha_entity_light; ESP32Encoder encoder1; ezButton encoder1Button(21); @@ -52,24 +54,23 @@ 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 - 1 // Chain length + 64, // module width + 32, // module height + 1 // Chain length ); // If you are using a 64x64 matrix you need to pass a value for the E pin // The trinity connects GPIO 18 to E. // This can be commented out for any smaller displays (but should work fine with it) - //mxconfig.gpio.e = 18; - + // mxconfig.gpio.e = 18; // May or may not be needed depending on your matrix // Example of what needing it looks like: @@ -78,13 +79,14 @@ void displaySetup() { // Some matrix panels use different ICs for driving them and some of them have strange quirks. // If the display is not working right, try this. - //mxconfig.driver = HUB75_I2S_CFG::FM6126A; + // mxconfig.driver = HUB75_I2S_CFG::FM6126A; dma_display = new MatrixPanel_I2S_DMA(mxconfig); 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 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"); @@ -108,27 +115,29 @@ void setup() { ESP32Encoder::useInternalWeakPullResistors = puType::none; - encoder1.attachSingleEdge(34, 35); - encoder2.attachSingleEdge(32, 33); + encoder1.attachSingleEdge(34, 35); + 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 + SPIFFS.begin(true); // On first run, will format after failing to mount WiFiSettings.hostname = "transigione-"; setupJsonForThisDevice(WiFiSettings.hostname); - String mqtt_host = WiFiSettings.string( "mqtt_host", "default.example.org"); - int mqtt_port = WiFiSettings.integer("mqtt_port", 0, 65535, 1883); + String mqtt_host = WiFiSettings.string("mqtt_host", "default.example.org"); + int mqtt_port = WiFiSettings.integer("mqtt_port", 0, 65535, 1883); 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 = []() { - setup_ota(); + // Set callbacks to start OTA when the portal is active + WiFiSettings.onPortal = []() + { + setup_ota(); }; - WiFiSettings.onPortalWaitLoop = []() { - ArduinoOTA.handle(); + WiFiSettings.onPortalWaitLoop = []() + { + ArduinoOTA.handle(); }; WiFiSettings.connect(); @@ -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,23 +175,25 @@ 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(); Serial.println("Ready"); 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,338 +229,537 @@ 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) { - case 0: - transition6(); - break; - case 1: - transition2(); - break; - case 2: - transition3(); - break; - case 3: - transition4(); - break; - case 4: - transition5(); - break; - case 5: - transition1(); - break; + switch (program) + { + case 0: + transition1(); + break; + case 1: + transition2(); + break; + case 2: + transition3(); + break; + case 3: + transition4(); + break; + case 4: + transition5(); + break; + case 5: + 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; uint8_t b1 = color1 & 0x1F; - + // Extract the RGB components from color2 uint8_t r2 = (color2 >> 11) & 0x1F; uint8_t g2 = (color2 >> 5) & 0x3F; uint8_t b2 = color2 & 0x1F; - // Calculate the difference for each component - int16_t rdiff = r2 - r1; - int16_t gdiff = g2 - g1; - int16_t bdiff = b2 - b1; - - // Interpolate each component - // Using 32-bit integers for intermediate calculations to avoid overflow - uint8_t r = r1 + ((rdiff * t + 128) >> 8); - uint8_t g = g1 + ((gdiff * t + 128) >> 8); - uint8_t b = b1 + ((bdiff * t + 128) >> 8); - - + // Calculate the difference for each component + int16_t rdiff = r2 - r1; + int16_t gdiff = g2 - g1; + int16_t bdiff = b2 - b1; + + // Interpolate each component + // Using 32-bit integers for intermediate calculations to avoid overflow + uint8_t r = r1 + ((rdiff * t + 128) >> 8); + 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. - * + * * @param color1 Starting color in RGB565 format (5 bits R, 6 bits G, 5 bits B) * @param color2 Ending color in RGB565 format (5 bits R, 6 bits G, 5 bits B) * @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; uint8_t b1 = color1 & 0x1F; - + uint8_t r2 = (color2 >> 11) & 0x1F; uint8_t g2 = (color2 >> 5) & 0x3F; uint8_t b2 = color2 & 0x1F; - + // Scale up to 8 bits for better precision r1 = (r1 << 3) | (r1 >> 2); g1 = (g1 << 2) | (g1 >> 4); b1 = (b1 << 3) | (b1 >> 2); - + r2 = (r2 << 3) | (r2 >> 2); g2 = (g2 << 2) | (g2 >> 4); b2 = (b2 << 3) | (b2 >> 2); - + // Convert RGB to HSV for both colors (using fixed-point) uint16_t h1, s1, v1; uint16_t h2, s2, v2; - + // For color1 uint8_t max1 = r1 > g1 ? (r1 > b1 ? r1 : b1) : (g1 > b1 ? g1 : b1); uint8_t min1 = r1 < g1 ? (r1 < b1 ? r1 : b1) : (g1 < b1 ? g1 : b1); uint16_t delta1 = max1 - min1; - + // Value is easy v1 = max1; - + // Saturation (scaled to 0-255) 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) { - h1 = 0; // undefined, default to red - } 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) { - int16_t diff = b1 - r1; - h1 = 512 + (((int32_t)diff * 256) / delta1); - } else { // max1 == b1 - int16_t diff = r1 - g1; - h1 = 1024 + (((int32_t)diff * 256) / delta1); + if (delta1 == 0) + { + h1 = 0; // undefined, default to red } - + 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) + { + int16_t diff = b1 - r1; + h1 = 512 + (((int32_t)diff * 256) / delta1); + } + else + { // max1 == b1 + int16_t diff = r1 - g1; + h1 = 1024 + (((int32_t)diff * 256) / delta1); + } + // For color2 (same calculations) uint8_t max2 = r2 > g2 ? (r2 > b2 ? r2 : b2) : (g2 > b2 ? g2 : b2); uint8_t min2 = r2 < g2 ? (r2 < b2 ? r2 : b2) : (g2 < b2 ? g2 : b2); uint16_t delta2 = max2 - min2; - + v2 = max2; s2 = max2 == 0 ? 0 : ((uint32_t)delta2 * 255) / max2; - - if (delta2 == 0) { - h2 = 0; - } 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) { - int16_t diff = b2 - r2; - h2 = 512 + (((int32_t)diff * 256) / delta2); - } else { // max2 == b2 - int16_t diff = r2 - g2; - h2 = 1024 + (((int32_t)diff * 256) / delta2); + + if (delta2 == 0) + { + h2 = 0; } - + 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) + { + int16_t diff = b2 - r2; + h2 = 512 + (((int32_t)diff * 256) / delta2); + } + 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); - + // Convert interpolated HSV back to RGB using more precise fixed-point math uint8_t r, g, b; - + uint8_t region = h_interp / 256; uint16_t remainder = (h_interp % 256); - + // These calculations now use 32-bit fixed-point to avoid rounding errors uint32_t p = ((uint32_t)v_interp * (255 - s_interp)) / 255; 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) { - case 0: - r = v_interp; - g = t_val; - b = p; - break; - case 1: - r = q; - g = v_interp; - b = p; - break; - case 2: - r = p; - g = v_interp; - b = t_val; - break; - case 3: - r = p; - g = q; - b = v_interp; - break; - case 4: - r = t_val; - g = p; - b = v_interp; - break; - default: // case 5 - r = v_interp; - g = p; - b = q; - break; + + switch (region) + { + case 0: + r = v_interp; + g = t_val; + b = p; + break; + case 1: + r = q; + g = v_interp; + b = p; + break; + case 2: + r = p; + g = v_interp; + b = t_val; + break; + case 3: + r = p; + g = q; + b = v_interp; + break; + case 4: + r = t_val; + g = p; + b = v_interp; + break; + default: // case 5 + r = v_interp; + g = p; + b = q; + break; } - + // Convert back to RGB565 format r = (r >> 3); g = (g >> 2); b = (b >> 3); - + 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 red = (rgb888 >> 16) & 0xFF; uint8_t green = (rgb888 >> 8) & 0xFF; - uint8_t blue = rgb888 & 0xFF; - + uint8_t blue = rgb888 & 0xFF; + // Convert 8-bit components to 5-6-5 bit format - uint16_t r = (red >> 3) & 0x1F; // 5 bits for red - uint16_t g = (green >> 2) & 0x3F; // 6 bits for green - uint16_t b = (blue >> 3) & 0x1F; // 5 bits for blue - + uint16_t r = (red >> 3) & 0x1F; // 5 bits for red + uint16_t g = (green >> 2) & 0x3F; // 6 bits for green + uint16_t b = (blue >> 3) & 0x1F; // 5 bits for blue + // Combine the components into a 16-bit value 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; + // 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; +} + +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; + +// 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 + + // Animation speed constant + const unsigned long ANIMATION_SPEED = 30; + + // Transition zone width + const int TRANSITION_WIDTH = XMAX / 3; + + // 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); } - state[y] += dir; - if (state[y] < 0) state[y] = 0; - if (state[y] > 255) state[y] = 255; - } - last_move_time = millis(); - } - - for (int x = 0; x < XMAX; x++) { - for(int y = 0; y < YMAX; y++) { - int t = x << 2; - - t += state[y] - 127; - - if (t <= 0) t = 1; - if (t >= 255) t = 254; - - int color = interpolateHSV565(color1, color2, t); - - dma_display->drawPixel(x, y, 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() { - //0x274001, 0x828a00, 0xf29f05, 0xf25c05, 0xd6568c, 0x4d8584, 0xa62f03, 0x400d01 +void transition3() +{ + // 0x274001, 0x828a00, 0xf29f05, 0xf25c05, 0xd6568c, 0x4d8584, 0xa62f03, 0x400d01 const int color1 = RGB888ToRGB565(0x274001); const int color2 = RGB888ToRGB565(0x828a00); const int color3 = RGB888ToRGB565(0xf29f05); @@ -561,81 +768,72 @@ 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; - int colorA = interpolateHSV565(color1, color5, t1); - int colorB = interpolateHSV565(color2, color6, t2); - int colorC = interpolateHSV565(color3, color7, t1); - int colorD = interpolateHSV565(color4, color8, t2); + // Ensure t1 and t2 stay within 1-254 range + t1 = constrain(t1, 1, 254); + t2 = constrain(t2, 1, 254); - int colorX = interpolateRGB565(colorA, colorB, x << 2); - int colorY = interpolateRGB565(colorC, colorD, x << 2); + // 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); + int colorD = interpolateHSV565(color4, color8, t2); - int color = interpolateRGB565(colorX, colorY, y << 3); + int colorX = interpolateRGB565(colorA, colorB, x << 2); + int colorY = interpolateRGB565(colorC, colorD, x << 2); - dma_display->drawPixel(x, y, color); - } + int color = interpolateRGB565(colorX, colorY, y << 3); + + dma_display->drawPixel(x, y, color); } } } -void transition4() { +void transition4() +{ const uint8_t NUM_COLORS = 4; const uint8_t NUM_PALETTES = 8; -// Color palettes (up to 4 colors per palette) -const uint16_t palettes[NUM_PALETTES][NUM_COLORS] = { - // 0: Soft purples and blues - { 0x780F, 0x401F, 0x001F, 0x4010 }, - // 1: Warm tones - { 0xF800, 0xFA60, 0xFFE0, 0xFD20 }, - // 2: Cool tones - { 0x001F, 0x07FF, 0x05BF, 0x041F }, - // 3: Fire - { 0xF800, 0xFC00, 0xFFE0, 0xFCA0 }, - // 4: Ocean - { 0x001F, 0x021F, 0x03FF, 0x05DF }, - // 5: Sunset - { 0xF800, 0xF81F, 0x780F, 0x001F }, - // 6: Neon - { 0xF81F, 0x07FF, 0xFFE0, 0x07E0 }, - // 7: Grayscale - { 0x0000, 0x528A, 0xAD55, 0xFFFF } -}; + // Color palettes (up to 4 colors per palette) + const uint16_t palettes[NUM_PALETTES][NUM_COLORS] = { + // 0: Soft purples and blues + {0x780F, 0x401F, 0x001F, 0x4010}, + // 1: Warm tones + {0xF800, 0xFA60, 0xFFE0, 0xFD20}, + // 2: Cool tones + {0x001F, 0x07FF, 0x05BF, 0x041F}, + // 3: Fire + {0xF800, 0xFC00, 0xFFE0, 0xFCA0}, + // 4: Ocean + {0x001F, 0x021F, 0x03FF, 0x05DF}, + // 5: Sunset + {0xF800, 0xF81F, 0x780F, 0x001F}, + // 6: Neon + {0xF81F, 0x07FF, 0xFFE0, 0x07E0}, + // 7: Grayscale + {0x0000, 0x528A, 0xAD55, 0xFFFF}}; -// Select the active palette here (index 0-7) -uint8_t currentPaletteIndex = parameter % 8; + // Select the active palette here (index 0-7) + uint8_t currentPaletteIndex = (now_time / (1000 * 60 * 20)) % 8; const uint32_t TRANSITION_DURATION = 200000UL; - uint32_t t = millis(); - const uint16_t* currentPalette = palettes[currentPaletteIndex]; + uint32_t t = now_time; + const uint16_t *currentPalette = palettes[currentPaletteIndex]; uint32_t totalCycle = NUM_COLORS * TRANSITION_DURATION; uint32_t localTime = t % totalCycle; @@ -646,37 +844,39 @@ 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++) { - uint8_t offset = ((x + y) * 2 + (t >> 4)) & 0x3F; - uint8_t finalBlend = (blendT + offset) & 0xFF; + 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; - uint16_t color = interpolateRGB565(currentPalette[index1], currentPalette[index2], finalBlend); - dma_display->drawPixel(x, y, color); - } + uint16_t color = interpolateRGB565(currentPalette[index1], currentPalette[index2], finalBlend); + dma_display->drawPixel(x, y, color); + } } } - -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 + static const uint32_t COLOR_DURATION = 100000UL; // 100s per color blend static const uint32_t PALETTE_BLEND_DURATION = 200000UL; // 200s per full palette blend // More vibrant and distinct palettes static const uint16_t palettes[NUM_PALETTES][NUM_COLORS] = { - { 0xF81F, 0x07FF, 0xFFE0, 0x07E0 }, // Neon Glow - { 0xF800, 0xFC00, 0xFFE0, 0xFFFF }, // Lava - { 0x001F, 0x07FF, 0x05BF, 0x87FF }, // Ocean - { 0x07FF, 0x780F, 0xFD20, 0xFFFF }, // Retro - { 0x001F, 0xF81F, 0x780F, 0x07E0 }, // Cyberpunk - { 0x07E0, 0xF81F, 0x780F, 0x07FF }, // Aurora - { 0xF800, 0xFFE0, 0x07E0, 0x001F }, // Rainbow - { 0x7800, 0x780F, 0xFD20, 0xFBE0 } // Sunset + {0xF81F, 0x07FF, 0xFFE0, 0x07E0}, // Neon Glow + {0xF800, 0xFC00, 0xFFE0, 0xFFFF}, // Lava + {0x001F, 0x07FF, 0x05BF, 0x87FF}, // Ocean + {0x07FF, 0x780F, 0xFD20, 0xFFFF}, // Retro + {0x001F, 0xF81F, 0x780F, 0x07E0}, // Cyberpunk + {0x07E0, 0xF81F, 0x780F, 0x07FF}, // Aurora + {0xF800, 0xFFE0, 0x07E0, 0x001F}, // Rainbow + {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,59 +896,60 @@ 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++) { - blendedPalette[i] = interpolateRGB565( - palettes[paletteIndex1][i], - palettes[paletteIndex2][i], - paletteBlendT - ); + for (uint8_t i = 0; i < NUM_COLORS; i++) + { + blendedPalette[i] = interpolateRGB565( + palettes[paletteIndex1][i], + palettes[paletteIndex2][i], + 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++) { - int16_t dx = x - centerX; - int16_t dy = y - centerY; + for (int x = 0; x < XMAX; x++) + { + for (int y = 0; y < YMAX; y++) + { + int16_t dx = x - centerX; + int16_t dy = y - centerY; - uint16_t distance = (uint16_t)sqrt(dx * dx + dy * dy); - uint8_t wave = (distance * 8 + (t >> 5)) & 0xFF; - uint8_t finalBlend = (baseBlend + wave) & 0xFF; + uint16_t distance = (uint16_t)sqrt(dx * dx + dy * dy); + uint8_t wave = (distance * 8 + (t >> 5)) & 0xFF; + uint8_t finalBlend = (baseBlend + wave) & 0xFF; - uint16_t color = interpolateRGB565( - blendedPalette[colorIndex1], - blendedPalette[colorIndex2], - finalBlend - ); + uint16_t color = interpolateRGB565( + blendedPalette[colorIndex1], + blendedPalette[colorIndex2], + finalBlend); - dma_display->drawPixel(x, y, color); - } + 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_SEGMENTS = NUM_COLORS; // for smooth wrap (4 segments: 0→1→2→3→0) static const uint8_t NUM_PALETTES = 8; static const uint32_t COLOR_DURATION = 200000UL; // 200s scroll loop static const uint32_t PALETTE_BLEND_DURATION = 800000UL; // 800s palette transition static const uint8_t palettes[NUM_PALETTES][NUM_COLORS][3] = { - { {255, 0, 0}, {255, 127, 0}, {255, 255, 0}, {0, 255, 0} }, // Rainbow - { {85, 207, 252}, {247, 168, 184}, {255, 255, 255}, {85, 207, 252} }, // Trans - { {214, 2, 111}, {155, 79, 150}, {0, 56, 168}, {214, 2, 111} }, // Bi - { {255, 33, 140}, {255, 181, 0}, {0, 173, 239}, {255, 33, 140} }, // Pan - { {0, 0, 0}, {164, 164, 164}, {255, 255, 255}, {128, 0, 128} }, // Asexual - { {244, 200, 0}, {255, 255, 255}, {165, 165, 165}, {155, 89, 182} }, // Non-binary - { {255, 117, 160}, {243, 60, 140}, {108, 30, 143}, {76, 109, 155} }, // Genderfluid - { {214, 46, 0}, {255, 90, 54}, {255, 154, 139}, {209, 98, 169} } // Lesbian + {{255, 0, 0}, {255, 127, 0}, {255, 255, 0}, {0, 255, 0}}, // Rainbow + {{85, 207, 252}, {247, 168, 184}, {255, 255, 255}, {85, 207, 252}}, // Trans + {{214, 2, 111}, {155, 79, 150}, {0, 56, 168}, {214, 2, 111}}, // Bi + {{255, 33, 140}, {255, 181, 0}, {0, 173, 239}, {255, 33, 140}}, // Pan + {{0, 0, 0}, {164, 164, 164}, {255, 255, 255}, {128, 0, 128}}, // Asexual + {{244, 200, 0}, {255, 255, 255}, {165, 165, 165}, {155, 89, 182}}, // Non-binary + {{255, 117, 160}, {243, 60, 140}, {108, 30, 143}, {76, 109, 155}}, // Genderfluid + {{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; @@ -757,17 +958,18 @@ void transition6() { uint8_t blendT = (paletteTime * 255UL) / PALETTE_BLEND_DURATION; // Create blended palette with wrap - uint16_t blendedPalette[NUM_COLORS + 1]; // add one for wraparound - 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]; - uint32_t rgb2 = (palettes[paletteIndex2][i][0] << 16) | - (palettes[paletteIndex2][i][1] << 8) | - palettes[paletteIndex2][i][2]; - uint16_t c1 = RGB888ToRGB565(rgb1); - uint16_t c2 = RGB888ToRGB565(rgb2); - blendedPalette[i] = interpolateRGB565(c1, c2, blendT); + uint16_t blendedPalette[NUM_COLORS + 1]; // add one for wraparound + 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]; + uint32_t rgb2 = (palettes[paletteIndex2][i][0] << 16) | + (palettes[paletteIndex2][i][1] << 8) | + palettes[paletteIndex2][i][2]; + uint16_t c1 = RGB888ToRGB565(rgb1); + uint16_t c2 = RGB888ToRGB565(rgb2); + blendedPalette[i] = interpolateRGB565(c1, c2, blendT); } // Wrap the first color to the end blendedPalette[NUM_COLORS] = blendedPalette[0]; @@ -777,23 +979,24 @@ 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++) { - uint16_t gradientX = (x + scrollOffset) % gradientWidth; - uint8_t segment = gradientX / segmentWidth; - if (segment >= NUM_SEGMENTS) segment = NUM_SEGMENTS - 1; + 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; - uint8_t localX = gradientX - segment * segmentWidth; - uint8_t blendFactor = (localX * 255) / segmentWidth; + uint8_t localX = gradientX - segment * segmentWidth; + uint8_t blendFactor = (localX * 255) / segmentWidth; - uint16_t color = interpolateRGB565( - blendedPalette[segment], - blendedPalette[segment + 1], - blendFactor - ); + uint16_t color = interpolateRGB565( + blendedPalette[segment], + blendedPalette[segment + 1], + blendFactor); - for (int y = 0; y < YMAX; y++) { - dma_display->drawPixel(x, y, color); - } + for (int y = 0; y < YMAX; y++) + { + dma_display->drawPixel(x, y, color); + } } } -