From d45427531c4b020e95ce7dd968ec5c854f5c7af0 Mon Sep 17 00:00:00 2001 From: Elnoggernog Date: Thu, 26 Feb 2026 22:03:45 +0100 Subject: [PATCH] initial commit --- animation.cpp | 878 +++++++++++++++++++++++++++++++++++ animation.h | 32 ++ manu_matrix.ino | 1126 +++++++++++++++++++++++++++++++++++++++++++++ sin_table_256.inc | 256 +++++++++++ 4 files changed, 2292 insertions(+) create mode 100644 animation.cpp create mode 100644 animation.h create mode 100644 manu_matrix.ino create mode 100644 sin_table_256.inc diff --git a/animation.cpp b/animation.cpp new file mode 100644 index 0000000..752a565 --- /dev/null +++ b/animation.cpp @@ -0,0 +1,878 @@ +#include "animation.h" +#include +#include + +#define WIDTH 20 +#define HEIGHT 20 + +#define SIN_TABLE_SIZE 256 +#define TWO_PI 6.28318530717958647692f +#define INV_TWO_PI (1.0f / TWO_PI) + +const float sinTable[SIN_TABLE_SIZE] PROGMEM = { + #include "sin_table_256.inc" +}; + +// Pumpkin pixel art 8x8 (simplified) +// 0 = empty, 1 = pumpkin body, 2 = eye +const uint8_t pumpkinSprite[8][8] = { + {0,1,1,1,1,1,1,0}, + {1,1,1,1,1,1,1,1}, + {1,2,0,1,1,0,2,1}, + {1,1,1,1,1,1,1,1}, + {1,1,1,0,0,1,1,1}, + {1,1,0,1,1,0,1,1}, + {0,1,1,1,1,1,1,0}, + {0,0,1,1,1,1,0,0} +}; + +const uint8_t imageManuBude[20][20] = { + {0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0}, + {1,1,1,1,1,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,0,0,0,0,1,1,1,1,1,1,1,1,1}, + {0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}, + {1,1,1,1,1,1,1,0,0,0,0,0,1,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0}, + {1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} +}; + +// Colors +uint32_t pumpkinColor = Adafruit_NeoPixel::Color(255,140,0); // orange +uint32_t eyeBaseColor = Adafruit_NeoPixel::Color(255,60,0); // fiery red-orange + +static inline float sinPreCalc(float value) +{ + // Scale factor (precompute once as constant) + const float scale = SIN_TABLE_SIZE / TWO_PI; + + // Convert radians → table index (float) + float index = value * scale; + + // Convert to integer index + int i0 = (int)index; + + // Fractional part for interpolation + float frac = index - i0; + + // Wrap index using power-of-two mask (256 -> 0xFF) + i0 &= (SIN_TABLE_SIZE - 1); + int i1 = (i0 + 1) & (SIN_TABLE_SIZE - 1); + + // Read from flash + float s0 = pgm_read_float(&sinTable[i0]); + float s1 = pgm_read_float(&sinTable[i1]); + + // Linear interpolation + return s0 + frac * (s1 - s0); +} + + + + + +// Bubble animation function +uint32_t bubbles(uint8_t x, uint8_t y, float timeMs) { + float colorR = 0, colorG = 0, colorB = 0; + + // Generate several bubbles per column + for (uint8_t i = 0; i < 5; i++) { + // Each bubble has its own horizontal position and speed + float speed = 0.02f + 0.01f * (hash8(i*3, x)/255.0f); // pixels/ms + float phase = hash8(i*7, x)/255.0f * HEIGHT; // starting row + float bubbleY = fmod((timeMs*speed + phase), HEIGHT); + + // Distance from this bubble to pixel + float dist = fabs(bubbleY - y); + if (dist < 1.5) { // bubble radius + // Brightness fades at edges + float brightness = 1.0f - (dist/1.5f); + colorR += 100 * brightness; + colorG += 180 * brightness; + colorB += 255 * brightness; + } + } + + // Clamp colors + if (colorR > 255) colorR = 255; + if (colorG > 255) colorG = 255; + if (colorB > 255) colorB = 255; + + return Adafruit_NeoPixel::Color((uint8_t)colorR, (uint8_t)colorG, (uint8_t)colorB); +} + +// Pseudo-random flicker +float flicker(float timeMs, uint8_t seed) { + return 0.6f + 0.4f * sinPreCalc(timeMs*0.01f + seed*3.14f); // 0.6..1.0 +} + +// Pumpkin pixel function +uint32_t minecraftPumpkin(uint8_t x, uint8_t y, float timeMs) { + int spriteWidth = 8; + int spriteHeight = 8; + int offsetX = WIDTH/2 - spriteWidth/2; + int offsetY = HEIGHT/2 - spriteHeight/2; + + int localX = x - offsetX; + int localY = y - offsetY; + + if (localX >= 0 && localX < spriteWidth && localY >= 0 && localY < spriteHeight) { + uint8_t val = pumpkinSprite[localY][localX]; + if (val == 1) return pumpkinColor; + if (val == 2) { + // Flickering fire eyes + float f = flicker(timeMs, localX*10 + localY); + uint8_t r = (uint8_t)(255 * f); + uint8_t g = (uint8_t)(60 * f); + uint8_t b = 0; + return Adafruit_NeoPixel::Color(r,g,b); + } + } + return Adafruit_NeoPixel::Color(0,0,0); // background black +} + + +// Among Us pixel art 7x7 (simplified) +// 0 = empty, 1 = body, 2 = visor +const uint8_t amongUsSprite[8][8] = { + {0,0,1,1,1,1,0,0}, + {0,1,1,3,3,3,3,0}, + {1,1,3,2,2,2,2,3}, + {1,1,3,2,2,2,2,3}, + {1,1,1,3,3,3,3,0}, + {1,1,1,1,1,1,1,0}, + {0,1,1,1,1,1,1,0}, + {0,1,1,0,0,1,1,0} +}; + +// Colors +uint32_t bodyColor = Adafruit_NeoPixel::Color(255,0,0); // red crewmate +uint32_t visorColor = Adafruit_NeoPixel::Color(150,200,255); // visor +uint32_t visorColorInner = Adafruit_NeoPixel::Color(0,0,255); // visor + +// Function to get pixel color of Among Us crewmate +uint32_t amongUsPixel(uint8_t x, uint8_t y, float timeMs) { + // Move crewmate horizontally across screen + int spriteWidth = 8; + int spriteHeight = 8; + + int posX = (int)(fmod(timeMs * 0.01f, WIDTH + spriteWidth)) - spriteWidth; + int posY = HEIGHT / 2 - spriteHeight / 2; + + int localX = x - posX; + int localY = y - posY; + + if (localX >= 0 && localX < spriteWidth && localY >= 0 && localY < spriteHeight) { + uint8_t val = amongUsSprite[localY][localX]; + if (val == 1) return bodyColor; + if (val == 2) return visorColorInner; + if (val == 3) return visorColor; + } + + return Adafruit_NeoPixel::Color(0,0,0); // background black +} + + + +// Fast pseudo-random hash function (per pixel) +uint8_t hash8(uint16_t x, uint16_t y) { + uint32_t h = x * 374761393 + y * 668265263; // large primes + h = (h ^ (h >> 13)) * 1274126177; + return (h >> 16) & 0xFF; +} + +// Purple rain waterfall color function +uint32_t purpleRain(uint8_t x, uint8_t y, float timeMs) { + // Each column has its own speed + float speed = 0.03f + 0.01f * (hash8(x, 0) / 255.0f); // pixels/ms + float t = timeMs * speed; + + // Determine the head of the falling stream + int headRow = (int)t % HEIGHT; + + // Distance from head + int dist = (y + HEIGHT - headRow) % HEIGHT; + + // Brightness fades with distance + uint8_t brightness = 0; + if (dist == 0) brightness = 255; // brightest head + else if (dist < 6) brightness = 255 - dist * 60; // fading tail + else brightness = 0; + + // Purple color: R and B nonzero, G low + uint8_t r = brightness; + uint8_t g = brightness / 2; // small green tint + uint8_t b = brightness; + + return Adafruit_NeoPixel::Color(r, g, b); +} +// Starry sky function +uint32_t starrySky(uint8_t x, uint8_t y, float timeMs) { + uint8_t seed = hash8(x, y); + + // Only ~5% of pixels have stars + if (seed < 20) { // 0..12 + // Twinkle: small brightness variation + float phase = (timeMs * 0.002f + seed) * 0.1f; + float blink = 0.7f + 0.8f * sinPreCalc(phase * 6.2831f); // 0.4..1.0 + uint8_t brightness = (uint8_t)(150 * blink + 50); // 50..200 + return Adafruit_NeoPixel::Color(brightness, brightness, brightness); + } + + // Shooting star: rare (~0.5%) moving diagonally + float t = fmod(timeMs * 0.01f, WIDTH + HEIGHT); + if (seed > 250 && fabs(x - t) < 1 && fabs(y - t) < 1) { + return Adafruit_NeoPixel::Color(255, 200, 150); + } + + // Otherwise empty sky + return Adafruit_NeoPixel::Color(0, 0, 0); +} + + +uint32_t spinningDavidStarPixel(uint8_t x, uint8_t y, float timeMs) { + float t = timeMs * 0.001f; // seconds + + // Center of the matrix + float cx = 10.0f; + float cy = 10.0f; + + // Translate to center + float dx = x - cx; + float dy = y - cy; + + // Rotation angle + float angle = t * 2.0f; // radians, spins over time + float cosA = sinPreCalc(TWO_PI/4 - angle); + float sinA = sinPreCalc(angle); + + float rx = dx * cosA - dy * sinA; + float ry = dx * sinA + dy * cosA; + + // Star of David = two overlapping equilateral triangles + // Approximate with slopes: y = ±sqrt(3)*x for the triangles + + bool inStar = false; + + // Upper triangle + if (ry >= -rx * 1.7f && ry >= rx * 1.7f && ry <= 0) inStar = true; + // Lower triangle + if (ry <= -rx * 1.7f && ry <= rx * 1.7f && ry >= 0) inStar = true; + + // Draw the star + if (inStar) { + // Optional: pulsating color + uint8_t pulse = 150 + 105 * sinPreCalc(t * 3.0f); + return NeoPixel.Color(pulse, pulse, 255); + } + + // Background + return NeoPixel.Color(0, 0, 20); +} + + +uint32_t jumpingJackPixel(uint8_t x, uint8_t y, float timeMs) { + float t = timeMs * 0.001f; // seconds + + // Center of the stick figure + int cx = 10; + int cy = 12; + + // Oscillation amplitudes + float armSpread = 4.0f * sinPreCalc(t * 2.0f); // arms + float legSpread = 4.0f * sinPreCalc(t * 2.0f); // legs + float jump = sinPreCalc(t * 2.0f) * -3.0f; // up/down jump + + // Background + uint32_t bg = NeoPixel.Color(0, 0, 0); + + // ----------------- + // Head (3x3) + // ----------------- + int headY = cy - 5 + (int)jump; + if ((x >= cx - 1 && x <= cx + 1) && (y >= headY && y <= headY + 2)) { + return NeoPixel.Color(255, 255, 255); + } + + // ----------------- + // Body (vertical) + // ----------------- + int bodyTop = cy - 2 + (int)jump; + int bodyBottom = cy + 2 + (int)jump; + if (x == cx && y >= bodyTop && y <= bodyBottom) { + return NeoPixel.Color(255, 255, 255); + } + + // ----------------- + // Arms (diagonal) + // ----------------- + int leftArmX = cx - 1 - (int)armSpread; + int rightArmX = cx + 1 + (int)armSpread; + int armY = cy - 1 + (int)jump; + + if ((x == leftArmX && y == armY - 1) || (x == leftArmX + 1 && y == armY) || + (x == rightArmX && y == armY - 1) || (x == rightArmX - 1 && y == armY)) { + return NeoPixel.Color(255, 255, 255); + } + + // ----------------- + // Legs (diagonal) + // ----------------- + int leftLegX = cx - 1 - (int)legSpread; + int rightLegX = cx + 1 + (int)legSpread; + int legY = cy + 3 + (int)jump; + + if ((x == leftLegX && y == legY) || (x == leftLegX + 1 && y == legY + 1) || + (x == rightLegX && y == legY) || (x == rightLegX - 1 && y == legY + 1)) { + return NeoPixel.Color(255, 255, 255); + } + + return bg; +} + + + + +uint32_t bonfireMoonPixel(uint8_t x, uint8_t y, float timeMs) { + float t = timeMs * 0.001f; // seconds + + // ------------------------ + // Quarter moon (crescent) + // ------------------------ + int moonX = 15; + int moonY = 4; + int moonR = 3; + + float mdx = x - moonX; + float mdy = y - moonY; + float mdist = sqrt(mdx * mdx + mdy * mdy); + + bool inMoon = (mdist <= moonR); + + // Cut-out circle to form crescent + float cutX = moonX + 2; // shift right to carve shadow + float cdx = x - cutX; + float cdist = sqrt(cdx * cdx + mdy * mdy); + + bool inShadow = (cdist <= moonR); + + if (inMoon && !inShadow) { + uint8_t glow = 140 + 40 * sinPreCalc(t * 0.5f); + return NeoPixel.Color(glow, glow, glow + 20); + } + + // ------------ + // Big bonfire + // ------------ + int baseX = 10; + int baseY = 16; + + bool log1 = (y == baseY && x >= 4 && x <= 15); + bool log2 = (y == baseY - 1 && x >= 5 && x <= 14); + bool log3 = (y == baseY - 2 && x >= 7 && x <= 12); + + if (log1 || log2 || log3) { + return NeoPixel.Color(100, 50, 15); + } + + int dx = x - baseX; + int dy = baseY - y; + + if (dy >= 0 && dy <= 10 && abs(dx) <= (5 - dy / 2)) { + float flicker = sinPreCalc(t * 9.0f + x * 2.1f + y * 1.4f) * 0.5f + 0.5f; + + uint8_t r = 170 + 85 * flicker; + uint8_t g = 70 + 160 * flicker - dy * 8; + uint8_t b = 0; + + return NeoPixel.Color(constrain(r, 150, 255), + constrain(g, 40, 220), + b); + } + + if (dy == 0 && abs(dx) <= 6) { + uint8_t glow = 90 + 50 * sinPreCalc(t * 7.0f + x * 0.8f); + return NeoPixel.Color(glow, glow / 2, 0); + } + + // ---------------- + // Night sky bg + // ---------------- + uint8_t stars = 4 + 6 * sinPreCalc(t * 0.3f + x * 0.6f + y * 0.4f); + return NeoPixel.Color(0, 0, stars); +} + + + + + +uint32_t nyanCatPixel(uint8_t x, uint8_t y, float timeMs) { + float t = timeMs * 0.0015f; // seconds + + // Cat position (flies left -> right, loops) + float catXf = fmod(t * 6.0f, 30.0f) - 6.0f; // offscreen -> onscreen + int catX = (int)catXf; + int catY = 10 + (int)(sinPreCalc(t * 2.0f) * 2.0f); + + // Background space + uint8_t stars = 5 + 10 * sinPreCalc(t * 0.7f + x * 0.9f + y * 0.4f); + uint32_t bg = NeoPixel.Color(0, 0, stars); + + // Rainbow trail (behind cat) + if (x < catX && x > catX - 8 && abs(y - catY) <= 1) { + float phase = t * 6.0f + x * 0.5f; + uint8_t r = 127 + 127 * sinPreCalc(phase + 0.0f); + uint8_t g = 127 + 127 * sinPreCalc(phase + 2.1f); + uint8_t b = 127 + 127 * sinPreCalc(phase + 4.2f); + return NeoPixel.Color(r, g, b); + } + + // Simple cat sprite (5x4) + bool cat = + (x == catX && y == catY) || + (x == catX + 1 && y == catY) || + (x == catX + 2 && y == catY) || + (x == catX + 3 && y == catY) || + + (x == catX && y == catY - 1) || + (x == catX + 1 && y == catY - 1) || + (x == catX + 2 && y == catY - 1) || + (x == catX + 3 && y == catY - 1) || + + (x == catX + 1 && y == catY - 2) || + (x == catX + 2 && y == catY - 2) || + + (x == catX + 1 && y == catY + 1) || + (x == catX + 2 && y == catY + 1); + + if (cat) { + return NeoPixel.Color(200, 180, 160); // cat body + } + + // Face pixels (eyes) + if (x == catX + 1 && y == catY - 1) return NeoPixel.Color(0, 0, 0); + if (x == catX + 2 && y == catY - 1) return NeoPixel.Color(0, 0, 0); + + return bg; +} + + +uint32_t xwingDeathStarPixel(uint8_t x, uint8_t y, float timeMs) { + // Convert ms to seconds for smooth trig + float t = timeMs * 0.001f; + + // Scene timing (ms) + float flyTime = 2000.0f; + float shootTime = 1200.0f; + float explodeTime = 2000.0f; + float total = flyTime + shootTime + explodeTime; + + float local = fmod(timeMs, total); + + // Positions + int xwingX = (int)(-5 + (local / flyTime) * 30); // flies left -> right + int xwingY = 10 + (int)(sinPreCalc(t * 2.0f) * 2.0f); + + int dsX = 15; + int dsY = 10; + int dsR = 3; + + // Background stars + uint8_t bg = 10 + 10 * sinPreCalc(t * 0.5f + x * 0.7f + y * 0.3f); + uint32_t color = NeoPixel.Color(0, 0, bg); + + // Death Star body + float dx = x - dsX; + float dy = y - dsY; + float dist = sqrt(dx * dx + dy * dy); + bool onDeathStar = dist <= dsR; + + // Phase 1: Fly-by + if (local < flyTime) { + if (onDeathStar) { + return NeoPixel.Color(120, 120, 120); // gray Death Star + } + } + + // Phase 2: Shooting + if (local >= flyTime && local < flyTime + shootTime) { + if (onDeathStar) { + return NeoPixel.Color(150, 150, 150); + } + + // Laser beam + int laserY = xwingY; + if (y == laserY && x > xwingX && x < dsX) { + return NeoPixel.Color(255, 0, 0); + } + } + + // Phase 3: Explosion + if (local >= flyTime + shootTime) { + float e = local - flyTime - shootTime; + float radius = e * 0.005f; + + if (dist < radius) { + uint8_t r = 255; + uint8_t g = max(0, 200 - (int)(e * 0.1f)); + uint8_t b = 0; + return NeoPixel.Color(r, g, b); + } + } + + // X-Wing sprite (tiny cross) + bool xwing = + (x == xwingX && y == xwingY) || + (x == xwingX - 1 && y == xwingY) || + (x == xwingX + 1 && y == xwingY) || + (x == xwingX && y == xwingY - 1) || + (x == xwingX && y == xwingY + 1); + + if (xwing) { + return NeoPixel.Color(200, 200, 200); + } + + return color; +} + + +uint32_t flappyBirdPixel(uint8_t x, uint8_t y, float t) { + const int W = 20; + const int H = 20; + + float time = 0.0; + time = t / 400; + // Bird position (fixed X, smooth Y motion) + int birdX = 5; + float birdY = 10.0f + sinPreCalc(time * 3.0f) * 3.0f; // flap motion + + // Pipe movement (smooth scrolling) + float pipePos = 19.0f - fmod(time * 4.0f, 25.0f); + int pipeX = (int)pipePos; + + // Moving gap + float gapCenter = 10.0f + sinPreCalc(time * 0.7f) * 4.0f; + int gapSize = 5; + + // Background (sky) + uint8_t bgR = 0, bgG = 0, bgB = 20; + + // Pipes + bool isPipe = (x == pipeX || x == pipeX - 1); + bool inGap = (y >= gapCenter - gapSize / 2.0f && + y <= gapCenter + gapSize / 2.0f); + + if (isPipe && !inGap) { + return NeoPixel.Color(0, 160, 0); + } + + // Bird (2x2) + bool isBird = + (x == birdX || x == birdX + 1) && + (y == (int)birdY || y == (int)(birdY + 1)); + + if (isBird) { + return NeoPixel.Color(255, 220, 0); + } + + return NeoPixel.Color(bgR, bgG, bgB); +} + + + +uint32_t creeper(uint8_t x, uint8_t y, uint32_t timestep) { +int phase = (timestep / 40) % 120; // animation cycle + int cx = 10; + int cy = 10; + + // Base color = off + uint8_t r = 0, g = 0, b = 0; + + // Creeper face mask + bool face = false; + bool eyeL = (x >= 5 && x <= 7 && y >= 5 && y <= 7); + bool eyeR = (x >= 12 && x <= 14 && y >= 5 && y <= 7); + bool mouth = + ((x >= 7 && x <= 12 && y >= 11 && y <= 13) || + (x >= 8 && x <= 9 && y >= 9 && y <= 11) || + (x >= 10 && x <= 11 && y >= 9 && y <= 11)); + + bool head = (x >= 3 && x <= 16 && y >= 3 && y <= 16); + + // Phase 1 – Normal creeper + if (phase < 50) { + if (head) { + if (eyeL || eyeR || mouth) { + return NeoPixel.Color(0, 0, 0); + } else { + return NeoPixel.Color(0, 180, 0); + } + } + return NeoPixel.Color(0, 0, 0); + } + + // Phase 2 – White flash (about to explode) + if (phase < 65) { + if (head) { + uint8_t flash = 200 + 55 * sinPreCalc(phase * 0.8); + return NeoPixel.Color(flash, flash, flash); + } + return NeoPixel.Color(0, 0, 0); + } + + // Phase 3 – Explosion + int t = phase - 65; + float dx = x - cx; + float dy = y - cy; + float dist = sqrt(dx * dx + dy * dy); + + // Expanding explosion radius + float radius = t * 0.4; + + if (dist > radius && dist < radius + 2) { + // fiery ring + uint8_t rr = 255; + uint8_t gg = random(80, 160); + return NeoPixel.Color(rr, gg, 0); + } + + if (dist < radius) { + // fading embers + uint8_t fade = max(0, 200 - t * 6); + return NeoPixel.Color(fade, fade / 2, 0); + } + + return NeoPixel.Color(0, 0, 0); +} + +uint32_t breathe(uint8_t x, uint8_t y, float t) { + float cx = 9.5, cy = 9.5; + float dx = x - cx; + float dy = y - cy; + float dist = sqrt(dx * dx + dy * dy); + + float wave = sinPreCalc(dist * 0.6 - t * 0.05 / 200.0); + float breath = (sinPreCalc(t * 0.02 / 200.0) + 1) * 0.5; + + uint8_t r = 50 + 200 * breath; + uint8_t g = 30 + 100 * wave * breath; + uint8_t b = 150 + 100 * (1 - breath); + + return NeoPixel.Color(r, g, b); +} + +uint32_t galaxy(uint8_t x, uint8_t y, float t) { + float cx = 9.5, cy = 9.5; + float dx = x - cx; + float dy = y - cy; + float angle = atan2(dy, dx); + float dist = sqrt(dx * dx + dy * dy); + + float spin = angle + dist * 0.3 - t / 10 * 0.02; + float v = (sinPreCalc(spin * 3) + 1) * 0.5; + + uint8_t r = 80 + 175 * v; + uint8_t g = 0; + uint8_t b = 120 + 135 * (1 - v); + + return NeoPixel.Color(r, g, b); +} + +float distToSegment(float px, float py, float x1, float y1, float x2, float y2) { + float vx = x2 - x1; + float vy = y2 - y1; + float wx = px - x1; + float wy = py - y1; + + float c1 = vx * wx + vy * wy; + if (c1 <= 0) return sqrt((px - x1)*(px - x1) + (py - y1)*(py - y1)); + + float c2 = vx * vx + vy * vy; + if (c2 <= c1) return sqrt((px - x2)*(px - x2) + (py - y2)*(py - y2)); + + float b = c1 / c2; + float bx = x1 + b * vx; + float by = y1 + b * vy; + + return sqrt((px - bx)*(px - bx) + (py - by)*(py - by)); +} + +uint32_t flyingWireCubeHard(uint8_t x, uint8_t y, float t) { + float cx = 9.5; + float cy = 9.5; + + float time = t * 0.0015 / 2.0; + + float cube[8][3] = { + {-1,-1,-1}, {1,-1,-1}, {1,1,-1}, {-1,1,-1}, + {-1,-1, 1}, {1,-1, 1}, {1,1, 1}, {-1,1, 1} + }; + + const uint8_t edges[12][2] = { + {0,1},{1,2},{2,3},{3,0}, + {4,5},{5,6},{6,7},{7,4}, + {0,4},{1,5},{2,6},{3,7} + }; + + float zpos = fmod(time * 3.0, 6.0) + 2.0; + float scale = 7.0; + + float rx = time * 1.3; + float ry = time * 0.9; + float rz = time * 0.7; + + float px = x; + float py = y; + + float proj[8][2]; + + for (int i = 0; i < 8; i++) { + float X = cube[i][0]; + float Y = cube[i][1]; + float Z = cube[i][2]; + + float y1 = Y * sinPreCalc(TWO_PI/4 - rx) - Z * sinPreCalc(rx); + float z1 = Y * sinPreCalc(rx) + Z * sinPreCalc(TWO_PI/4 - rx); + Y = y1; Z = z1; + + float x2 = X * sinPreCalc(TWO_PI/4 - ry) + Z * sinPreCalc(ry); + float z2 = -X * sinPreCalc(ry) + Z * sinPreCalc(TWO_PI/4 - ry); + X = x2; Z = z2; + + float x3 = X * sinPreCalc(TWO_PI/4 - rz) - Y * sinPreCalc(rz); + float y3 = X * sinPreCalc(rz) + Y * sinPreCalc(TWO_PI/4 - rz); + X = x3; Y = y3; + + proj[i][0] = cx + X * scale; + proj[i][1] = cy + Y * scale; + } + + bool onEdge = false; + const float thickness = 0.6; // edge thickness in pixels + + for (int e = 0; e < 12; e++) { + int a = edges[e][0]; + int b = edges[e][1]; + + float d = distToSegment(px, py, + proj[a][0], proj[a][1], + proj[b][0], proj[b][1]); + + if (d < thickness) { + onEdge = true; + break; + } + } + + if (onEdge) { + return NeoPixel.Color(0, 0, 255); // sharp neon wireframe + } else { + return NeoPixel.Color(0, 0, 0); // background off + } +} + + +bool isPacmanPixel(int px, int py, float cx, float cy, float r, float mouthOpen) { + float dx = px - cx; + float dy = py - cy; + float dist2 = dx*dx + dy*dy; + + if (dist2 > r*r) return false; + + float angle = atan2(dy, dx); // -PI..PI + + // Mouth opening angle + float mouth = mouthOpen * 0.8; // max opening + if (angle > -mouth && angle < mouth) return false; + + return true; +} + +uint32_t pacman(uint8_t x, uint8_t y, uint32_t t) { + // Movement + float time = t * 0.005 / 3.0; + float cx = fmod(time * 6.0, 26.0) - 3.0; // move across screen + float cy = 10.0; + float r = 4.0; + + // Mouth animation (chomp) + float mouth = (sinPreCalc(time * 6.0) + 1.0) * 0.5; // 0..1 + + // Optional pellets + int pelletX = ((int)(time * 6.0)) % 20; + bool pellet = (x == pelletX && y == 10); + + if (isPacmanPixel(x, y, cx, cy, r, mouth)) { + return NeoPixel.Color(255, 220, 0); // Pac-Man yellow + } + + if (pellet) { + return NeoPixel.Color(200, 200, 200); // pellet + } + + return NeoPixel.Color(0, 0, 0); +} + +uint32_t coolEffect(uint8_t x, uint8_t y, float t) { + // Center of the grid + float cx = 9.5; + float cy = 9.5; + float time = 0; + time = t / 100.0; + + // Distance from center + float dx = x - cx; + float dy = y - cy; + float dist = sqrt(dx * dx + dy * dy); + + // Animated wave + float wave = sinPreCalc(dist * 0.6 - time * 0.15); + + // Rainbow hue shifts over time and space + float hue = dist * 20 + time * 3; + + // Breathing brightness + float brightness = (sinPreCalc(time * 0.08) + 1.0) * 0.5; // 0..1 + + // Convert HSV → RGB (simple version) + uint8_t r, g, b; + float h = fmod(hue, 360.0); + + int i = int(h / 60.0) % 6; + float f = (h / 60.0) - i; + float v = brightness * (0.5 + 0.5 * wave); + if (v < 0) v = 0; + if (v > 1) v = 1; + + float p = 0; + float q = v * (1 - f); + float s = v * f; + + float R, G, B; + switch (i) { + case 0: R = v; G = s; B = p; break; + case 1: R = q; G = v; B = p; break; + case 2: R = p; G = v; B = s; break; + case 3: R = p; G = q; B = v; break; + case 4: R = s; G = p; B = v; break; + default: R = v; G = p; B = q; break; + } + + r = (uint8_t)(R * 255); + g = (uint8_t)(G * 255); + b = (uint8_t)(B * 255); + + return NeoPixel.Color(r, g, b); +} \ No newline at end of file diff --git a/animation.h b/animation.h new file mode 100644 index 0000000..ab39a82 --- /dev/null +++ b/animation.h @@ -0,0 +1,32 @@ +#ifndef ANIMATION_H +#define ANIMATION_H + +#include +#include + +extern Adafruit_NeoPixel NeoPixel; +extern const uint8_t imageManuBude[20][20]; + +uint32_t breathe(uint8_t x, uint8_t y, float t); +uint32_t galaxy(uint8_t x, uint8_t y, float t); +uint32_t flyingWireCubeHard(uint8_t x, uint8_t y, float t); +uint32_t pacman(uint8_t x, uint8_t y, uint32_t t); +uint32_t coolEffect(uint8_t x, uint8_t y, float t); +uint32_t creeper(uint8_t x, uint8_t y, uint32_t timestep); +uint32_t flappyBirdPixel(uint8_t x, uint8_t y, float time); +uint32_t xwingDeathStarPixel(uint8_t x, uint8_t y, float timeMs); +uint32_t nyanCatPixel(uint8_t x, uint8_t y, float timeMs); +uint32_t bonfireMoonPixel(uint8_t x, uint8_t y, float timeMs); +uint32_t jumpingJackPixel(uint8_t x, uint8_t y, float timeMs); +uint32_t spinningDavidStarPixel(uint8_t x, uint8_t y, float timeMs); +uint32_t starrySkyPixel(uint8_t x, uint8_t y, float timeMs); +uint32_t starrySky(uint8_t x, uint8_t y, float timeMs); +uint32_t purpleRain(uint8_t x, uint8_t y, float timeMs); +uint32_t amongUsPixel(uint8_t x, uint8_t y, float timeMs); +uint32_t minecraftPumpkin(uint8_t x, uint8_t y, float timeMs); +uint32_t bubbles(uint8_t x, uint8_t y, float timeMs); +int displayAnimationFrame(int timeMs, int CycleAnimationOn, int AnimationIndex); + +uint8_t hash8(uint16_t x, uint16_t y); + +#endif \ No newline at end of file diff --git a/manu_matrix.ino b/manu_matrix.ino new file mode 100644 index 0000000..adf39dd --- /dev/null +++ b/manu_matrix.ino @@ -0,0 +1,1126 @@ +#include +#include +#include +#include +#include +#include +#include +#include "animation.h" +#include "driver/temperature_sensor.h" + +// #define PHILLIPS_HUE_URL "https://192.168.0.20/clip/v2/resource/light/5557eceb-fd62-4859-b587-28f67e948969" +#define PHILLIPS_HUE_URL "https://192.168.0.20/clip/v2/resource/light/" + +#define PHILLIPS_HUE_WZ_MITTE "5557eceb-fd62-4859-b587-28f67e948969" + + +#define PHILLIPS_HUE_APPLICATION_KEY_NAME "hue-application-key" +#define PHILLIPS_HUE_APPLICATION_KEY_VALUE "Xs4bbYSXvlPlNR7nKmpLpsLnLCbz42C4SrvO93Fz" + +#define PIN_NEO_PIXEL 2 // The ESP32 pin GPIO16 connected to NeoPixel +#define NUM_PIXELS 400 // The number of LEDs (pixels) on NeoPixel LED strip +#define COLLUMMS_PANEL 20 +#define ROWS_PANEL 20 +#define TARGET_UPDATE_RATE 1.0/30.0*1000.0 //Updaterate in ms +#define ANIMATION_CYCLE_SPEED_MS 2000 //Switch animation every 20s +#define FRAME_SIZE (COLLUMMS_PANEL * ROWS_PANEL * 3) // 1200 bytes per frame + +#define SD_CS 7 +#define SD_MOSI 6 +#define SD_MISO 5 +#define SD_SCK 4 +#define BUTTON 3 + +#define WebServerOn +enum EntryType { + COUNT_FOLDERS, + COUNT_FILES +}; + +enum displayType { + DISPLAY_ANIMATION, + DISPLAY_VIDEO, + DISPLAY_PICTURE +}; + +Adafruit_NeoPixel NeoPixel(NUM_PIXELS, PIN_NEO_PIXEL, NEO_GRB + NEO_KHZ800); +String ServerResponseBody; +String ServerResponseBody2; +HTTPClient http; +StaticJsonDocument<20000> doc; + +SPIClass spiSD(FSPI); // Use FSPI on ESP32 +File myFile; +temperature_sensor_handle_t temp_sensor = NULL; +#ifdef WebServerOn + WebServer server(80); +#endif + +// const char* ssid = "FRITZ!Box 6490 Cable"; +// const char* password = "29644657346255273822"; + +int serverFrameOverride = 0; + +const char* ssid = "Ununu-Schanze"; +const char* password = "68838192089895491452"; + + +void setup() { + Serial.begin(115200); + NeoPixel.begin(); // initialize NeoPixel strip object (REQUIRED) + WiFi.begin(ssid, password); + + connectWifi(); //Connect to Wifi and show a progress bar on the matrix. Also returns IP-Address over Serial + startSDCard(); //Tries to look for SD-Card and start SPI communication with it + + #ifdef WebServerOn //Activate WebServer and bind actions for requests (GET POST) + server.on("/", HTTP_GET, handleRoot); + server.on("/set", HTTP_POST, handleSet); + server.on("/normal", HTTP_POST, handleNormal); + server.begin(); + #endif + + NeoPixel.fill(NeoPixel.Color(0,0,0),0,99); + NeoPixel.setBrightness(50); + NeoPixel.show(); + + pinMode(BUTTON, INPUT); + + // while(digitalRead(BUTTON)){ + // displayAnimationFrame(millis(), 1, 0); + // } + + // while(true){ + // NeoPixel.fill(PhillipsHueColor(PHILLIPS_HUE_WZ_MITTE)); + // NeoPixel.show(); + // delay(200); + // } + // while(true){ + // showManuRoom(); + // } +} +// +void loop() { + // float bri = PhillipsHueBrightness(PHILLIPS_HUE_URL); + // int on = PhillipsHueOn(PHILLIPS_HUE_URL); + static int previousMillis = millis(); + static displayType Type = DISPLAY_ANIMATION; + server.handleClient();; + if(!serverFrameOverride){ + switch(Type){ + case DISPLAY_ANIMATION: + if(displayAnimationFrame(millis(), 1, 0) == 3){Type = DISPLAY_PICTURE;} + break; + + case DISPLAY_VIDEO: + if(updateVideoPlayback() == 3){Type = DISPLAY_ANIMATION;} + break; + + case DISPLAY_PICTURE: + if(updatePicturePlayback() == 3){Type = DISPLAY_VIDEO;} + break; + + default: + break; + } + }else{ + parseJsonAndSetPixel(); + } +} + +int updatePicturePlayback() { + static unsigned long previousMillis = -20000; + static int maxFolders = -1; + static int folderIndex = 0; + static int frameIndex = 0; + static bool playing = false; + + unsigned long currentMillis = millis(); + + // Respect update rate without blocking + if (currentMillis - previousMillis < TARGET_UPDATE_RATE*30*5) { //every 20sec + return 0; + } + previousMillis = currentMillis; + + // Initialize folder count once + if (maxFolders == -1) { + maxFolders = countTopLevelEntries(SD, "/pictures", COUNT_FOLDERS); + folderIndex = 0; + frameIndex = 0; + playing = true; + } + + + if (folderIndex >= maxFolders) { + // Restart from first folder (or stop if preferred) + folderIndex = 0; + return 3; + } + + String pathToRead = getEntryByIndex(SD, "/pictures", folderIndex, COUNT_FOLDERS); + + // Try to read ONE frame only (non-blocking) + bool frameExists = readEntryByPathSetPixelFrameStream( + SD, + pathToRead + "/frame.bin", + frameIndex + ); + + if (frameExists) { + NeoPixel.show(); + frameIndex++; + } else { + // Move to next folder when frames are done + frameIndex = 0; + folderIndex++; + } + + // Button handling (non-blocking edge detect) + static bool lastButtonState = HIGH; + static int lastButtonStateTime = millis(); + bool currentButtonState = digitalRead(BUTTON); + + if (lastButtonState == HIGH && currentButtonState == LOW) { + // Button pressed + delay(20); + folderIndex++; + frameIndex = 0; + } + + lastButtonState = currentButtonState; + lastButtonStateTime = millis(); +} + + +int updateVideoPlayback() { + static unsigned long previousMillis = 0; + static int maxFolders = -1; + static int folderIndex = 0; + static int frameIndex = 0; + static bool playing = false; + + unsigned long currentMillis = millis(); + + // Respect update rate without blocking + if (currentMillis - previousMillis < TARGET_UPDATE_RATE) { + return 0; + } + previousMillis = currentMillis; + + // Initialize folder count once + if (maxFolders == -1) { + maxFolders = countTopLevelEntries(SD, "/videos", COUNT_FOLDERS); + folderIndex = 0; + frameIndex = 0; + playing = true; + } + + + if (folderIndex >= maxFolders) { + // Restart from first folder (or stop if preferred) + folderIndex = 0; + return 3; + } + + String pathToRead = getEntryByIndex(SD, "/videos", folderIndex, COUNT_FOLDERS); + + // Try to read ONE frame only (non-blocking) + bool frameExists = readEntryByPathSetPixelFrameStream( + SD, + pathToRead + "/frame.bin", + frameIndex + ); + + if (frameExists) { + NeoPixel.show(); + frameIndex++; + } else { + // Move to next folder when frames are done + frameIndex = 0; + folderIndex++; + } + + // Button handling (non-blocking edge detect) + static bool lastButtonState = HIGH; + static int lastButtonStateTime = millis(); + bool currentButtonState = digitalRead(BUTTON); + + if (lastButtonState == HIGH && currentButtonState == LOW) { + // Button pressed + delay(20); + folderIndex++; + frameIndex = 0; + } + + lastButtonState = currentButtonState; + lastButtonStateTime = millis(); +} + +int displayAnimationFrame(int timeMs, int CycleAnimationOn, int AnimationIndex = 0){ + uint32_t color; + static int animation = AnimationIndex; + static int prevtimeanimation = timeMs; + + if(!CycleAnimationOn){ + animation = AnimationIndex; + } + + for (int y = 0; y < ROWS_PANEL; y++) { + for (int x = 0; x < COLLUMMS_PANEL; x++) { + switch(animation){ + case 0: + animation++; + break; + case 1: + color = bubbles(x, y, timeMs); + break; + case 2: + color = galaxy(x, y, timeMs); + break; + case 3: + animation++; + break; + case 4: + color = flyingWireCubeHard(x, y, timeMs); + break; + case 5: + color = pacman(x, y, timeMs); + break; + case 6: + color = coolEffect(x, y, timeMs); + break; + case 7: + color = creeper(x, y, timeMs); + break; + case 8: + color = flappyBirdPixel(x, y, timeMs); + break; + case 9: + color = xwingDeathStarPixel(x, y, timeMs); + break; + case 10: + color = nyanCatPixel(x, y, timeMs); + break; + case 11: + color = bonfireMoonPixel(x, y, timeMs); + break; + case 12: + color = jumpingJackPixel(x, y, timeMs); + break; + case 13: + color = spinningDavidStarPixel(x, y, timeMs); + break; + case 14: + color = starrySky(x, y, timeMs); + break; + case 15: + color = purpleRain(x, y, timeMs); + break; + case 16: + color = amongUsPixel(x, y, timeMs); + break; + case 17: + color = minecraftPumpkin(x, y, timeMs); + break; + default: + animation = 0; + return 3; + break; + } + + if(timeMs - prevtimeanimation >= ANIMATION_CYCLE_SPEED_MS){ + animation++; + prevtimeanimation = timeMs; + } + + NeoPixel.setPixelColor(getPixelByCoordinate(x,y), color); + } + } + + NeoPixel.show(); + return 0; +} + +void showManuRoom(void){ + for(int y = 0; y < ROWS_PANEL; y++){ + for(int x = 0; x < COLLUMMS_PANEL; x++){ + NeoPixel.setPixelColor(getPixelByCoordinate(x, y), NeoPixel.Color(imageManuBude[y][x] ? 255: 0, imageManuBude[y][x] ? 255: 0, imageManuBude[y][x] ? 255: 0)); + } + } + NeoPixel.setPixelColor(getPixelByCoordinate(13,16), PhillipsHueColor("5557eceb-fd62-4859-b587-28f67e948969")); + NeoPixel.setPixelColor(getPixelByCoordinate(13,17), PhillipsHueColor("5ccff572-baaa-4fa0-8998-d43e595e28c5")); + NeoPixel.setPixelColor(getPixelByCoordinate(13,18), PhillipsHueColor("29cc7114-9f1f-44ba-baab-00718cd4d1f3")); + + NeoPixel.setPixelColor(getPixelByCoordinate(0,17), PhillipsHueColor("ccc6bced-f859-4a3d-8af2-7885f79d166c")); + NeoPixel.setPixelColor(getPixelByCoordinate(2,17), PhillipsHueColor("76b2d794-657a-43ed-8f0e-f15c56e396e1")); + NeoPixel.setPixelColor(getPixelByCoordinate(4,17), PhillipsHueColor("ccc6bced-f859-4a3d-8af2-7885f79d166c")); + + NeoPixel.setPixelColor(getPixelByCoordinate(11,7), PhillipsHueColor("e3c9d639-72df-415f-a579-2311dedbd2e7")); + NeoPixel.setPixelColor(getPixelByCoordinate(11,8), PhillipsHueColor("acec0a2e-0755-4e79-8c77-a4e972d11149")); + + NeoPixel.setPixelColor(getPixelByCoordinate(10,4), PhillipsHueColorOnlyBrightness("563584d7-7252-45a0-9109-332f5714781d")); + NeoPixel.setPixelColor(getPixelByCoordinate(10,3), PhillipsHueColorOnlyBrightness("72f09c6e-0a5b-4836-8bda-7249248c1e01")); + + NeoPixel.setPixelColor(getPixelByCoordinate(0,11), PhillipsHueColor("5a13afda-216a-4b41-bec2-96beaee94012")); + NeoPixel.setPixelColor(getPixelByCoordinate(0,12), PhillipsHueColor("ee8b00b0-4969-4274-8e5e-df291b06c9f7")); + + NeoPixel.setPixelColor(getPixelByCoordinate(4,11), PhillipsHueColorOnlyBrightness("d95fa043-d067-48f5-8b3a-59ea650fe5c0")); + NeoPixel.setPixelColor(getPixelByCoordinate(4,12), PhillipsHueColor("ee63ef6a-72dd-4a8e-a2d3-7f446a241699")); + NeoPixel.setPixelColor(getPixelByCoordinate(4,13), PhillipsHueColorOnlyBrightness("eb7f0210-b132-46b1-a061-853efcc42cfb")); + NeoPixel.show(); +} + +int readEntryByPathSetPixelFrameStream(fs::FS &fs, const String &filePath, int frameIndex) { + static uint8_t frameBuffer[FRAME_SIZE]; // preallocated buffer + + File file = fs.open(filePath); + if(!file) { + Serial.println("Could not open animation file on SD!"); + return 0; + } + + // Seek to the frame start + if(!file.seek(frameIndex * FRAME_SIZE)) { + Serial.println("Seek failed!"); + file.close(); + return 0; + } + + // Read the frame into RAM + size_t bytesRead = file.read(frameBuffer, FRAME_SIZE); + file.close(); + + if(bytesRead != FRAME_SIZE) { + Serial.println("Frame read incomplete!"); + return 0; + } + + // Set pixels + int idx = 0; + for(int y = 0; y < ROWS_PANEL; y++) { + for(int x = 0; x < COLLUMMS_PANEL; x++) { + uint8_t r = frameBuffer[idx++]; + uint8_t g = frameBuffer[idx++]; + uint8_t b = frameBuffer[idx++]; + uint32_t color = NeoPixel.Color(r, g, b); + NeoPixel.setPixelColor(getPixelByCoordinate(x, y), color); + } + } + return 1; +} + +int readEntryByPathSetPixelFrame(fs::FS &fs, String path){ + int x = 0; + int y = 0; + uint8_t frameBuffer[COLLUMMS_PANEL*ROWS_PANEL*3]; + + File file = fs.open(path); + if(!file){ + Serial.println("Could not open file on SD!"); + return 0; + } + + size_t bytesRead = file.read(frameBuffer, COLLUMMS_PANEL*ROWS_PANEL*3); + file.close(); + + if(bytesRead != COLLUMMS_PANEL*ROWS_PANEL*3){ + Serial.println("Frame size mismatch!"); + return 0; + } + + int idx = 0; + for(int y = 0; y < ROWS_PANEL; y++){ + for(int x = 0; x < COLLUMMS_PANEL; x++){ + uint8_t r = frameBuffer[idx++]; + uint8_t g = frameBuffer[idx++]; + uint8_t b = frameBuffer[idx++]; + uint32_t color = NeoPixel.Color(r, g, b); + NeoPixel.setPixelColor(getPixelByCoordinate(x, y), color); + } + } + + Serial.println("Read File!"); + return 1; +} + +int readEntryByPath(fs::FS &fs,String &data, String path){ + File file = SD.open(path); + if(file){ + Serial.println("Reading File..."); + data = ""; + while(file.available()){ + int temp = file.read(); + Serial.write(temp); + ServerResponseBody2 += (char)temp; + } + file.close(); + Serial.println("Read File!"); + return 1; + }else{ + Serial.println("Could not read data from SD-Card"); + return 0; + } +} + +String getEntryByIndex(fs::FS &fs, String path, int index, EntryType type) { + File root = fs.open(path); + if (!root || !root.isDirectory()) return ""; + + int currentIndex = 0; + File file = root.openNextFile(); + + while (file) { + bool match = + (type == COUNT_FOLDERS && file.isDirectory()) || + (type == COUNT_FILES && !file.isDirectory()); + + if (match) { + if (currentIndex == index) { + return String(path) + "/" + file.name(); + } + currentIndex++; + } + + file = root.openNextFile(); + } + + return ""; // index out of range +} + +int countTopLevelEntries(fs::FS &fs, String path, EntryType type) { + File root = fs.open(path); + if (!root || !root.isDirectory()) return 0; + + int count = 0; + File file = root.openNextFile(); + while (file) { + if (type == COUNT_FOLDERS && file.isDirectory()) { + count++; + } + else if (type == COUNT_FILES && !file.isDirectory()) { + count++; + } + file = root.openNextFile(); + } + + return count; +} + +void startSDCard(){ + spiSD.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS); + for(int i = 0; i < 20; i++){ + if (!SD.begin(SD_CS, spiSD, 80000000)) { + Serial.println("❌ SD Card Mount Failed " + String(i)); + if (!SD.begin(SD_CS, spiSD, 80000000) && i == 19) { + return; + } + } + } + + Serial.println("✅ SD Card initialized."); +} + +void connectWifi(){ + int wifiSymbol[7][9] = {{0,0,1,1,1,1,1,0,0}, + {0,1,0,0,0,0,0,1,0}, + {1,0,0,0,0,0,0,0,1}, + {0,0,0,1,1,1,0,0,0}, + {0,0,1,0,0,0,1,0,0}, + {0,0,0,0,0,0,0,0,0}, + {0,0,0,0,1,0,0,0,0}}; + + while (WiFi.status() != WL_CONNECTED) { //Connect to WIFI + static int cycles = 0; + delay(50); + Serial.print("."); + NeoPixel.fill(NeoPixel.Color(0,0,0)); + for(int x = 0; x < 9; x++){ + for(int y = 0; y < 7; y++){ + if(wifiSymbol[y][x] && (cycles%10 > 7-y)) + NeoPixel.setPixelColor(getPixelByCoordinate(x+5, y+6), NeoPixel.Color(255,100,0)); + } + } + NeoPixel.show(); + cycles++; + } + Serial.println("\nConnected!"); + Serial.println(WiFi.localIP()); + + //Success Animation + for(int j = 0; j < 3; j++){ //3 Times + for(int x = 0; x < 9; x++){ + for(int y = 0; y < 7; y++){ + if(wifiSymbol[y][x]) + NeoPixel.setPixelColor(getPixelByCoordinate(x+5, y+6), NeoPixel.Color(0,255,0)); + } + } + NeoPixel.show(); + delay(300); + + NeoPixel.fill(NeoPixel.Color(0,0,0)); + NeoPixel.show(); + delay(300); + } +} + +float PhillipsHueBrightness(String ServiceLightID){ //return float in % (0-100) of current lightbrightness input: id of light to read + http.begin(PHILLIPS_HUE_URL); + http.addHeader(PHILLIPS_HUE_APPLICATION_KEY_NAME, PHILLIPS_HUE_APPLICATION_KEY_VALUE); //Add security key only received when pressing the link button + Serial.println("Sending GET"); + int ResponseCodeHttp = http.GET(); + ServerResponseBody = http.getString(); + // Serial.println("HTTP Response Code" + toString(ResponseCodeHttp)); + deserializeJson(doc, ServerResponseBody); + return (float)doc["data"][0]["dimming"]["brightness"]; +} + +int PhillipsHueOn(String ServiceLightID){ //Returns 1 when light is switched on input: id of light to read + http.begin(PHILLIPS_HUE_URL); + http.addHeader(PHILLIPS_HUE_APPLICATION_KEY_NAME, PHILLIPS_HUE_APPLICATION_KEY_VALUE); //Add security key only received when pressing the link button + Serial.println("Sending GET"); + int ResponseCodeHttp = http.GET(); + ServerResponseBody = http.getString(); + // Serial.println("HTTP Response Code" + toString(ResponseCodeHttp)); + deserializeJson(doc, ServerResponseBody); + return doc["data"][0]["on"]["on"] == true ? 1 : 0; +} + +uint32_t PhillipsHueColor(String ServiceLightID){ + http.begin(PHILLIPS_HUE_URL+ServiceLightID); + http.addHeader(PHILLIPS_HUE_APPLICATION_KEY_NAME, PHILLIPS_HUE_APPLICATION_KEY_VALUE); //Add security key only received when pressing the link button + Serial.println("Sending GET"); + int ResponseCodeHttp = http.GET(); + ServerResponseBody = http.getString(); + // Serial.println("HTTP Response Code" + toString(ResponseCodeHttp)); + deserializeJson(doc, ServerResponseBody); + float x = (float)doc["data"][0]["color"]["xy"]["x"]; + float y = (float)doc["data"][0]["color"]["xy"]["y"]; + float brightness = (float)doc["data"][0]["dimming"]["brightness"]; + doc["data"][0]["on"]["on"] == false ? brightness = 0 : brightness = brightness; + + return ConvertHueToRGB888Neo(x, y, brightness); +} + +uint32_t PhillipsHueColorOnlyBrightness(String ServiceLightID){ + http.begin(PHILLIPS_HUE_URL+ServiceLightID); + http.addHeader(PHILLIPS_HUE_APPLICATION_KEY_NAME, PHILLIPS_HUE_APPLICATION_KEY_VALUE); //Add security key only received when pressing the link button + Serial.println("Sending GET"); + int ResponseCodeHttp = http.GET(); + ServerResponseBody = http.getString(); + // Serial.println("HTTP Response Code" + toString(ResponseCodeHttp)); + deserializeJson(doc, ServerResponseBody); + float brightness = (float)doc["data"][0]["dimming"]["brightness"]; + doc["data"][0]["on"]["on"] == false ? brightness = 0 : brightness = brightness; + int bri = (int)(brightness * 255.0 / 100.0); + return NeoPixel.Color(bri,(int)(bri*0.8),(int)(bri*0.7)); +} + +uint32_t ConvertHueToRGB888Neo(float x, float y, float brightness){ + float bri = brightness/100.0; + float Y = bri; // Luminance + float X = (Y / y) * x; + float Z = (Y / y) * (1.0 - x - y); + float r = 1.656492 * X - 0.354851 * Y - 0.255038 * Z; + float g = -0.707196 * X + 1.655397 * Y + 0.036152 * Z; + float b = 0.051713 * X - 0.121364 * Y + 1.011530 * Z; + r = gammaCorrect(r); + g = gammaCorrect(g); + b = gammaCorrect(b); + r = constrain(r, 0.0, 1.0); + g = constrain(g, 0.0, 1.0); + b = constrain(b, 0.0, 1.0); + + uint8_t R = (uint8_t)(r * 255); + uint8_t G = (uint8_t)(g * 255); + uint8_t B = (uint8_t)(b * 255); + + return NeoPixel.Color(R,G,B); +} + +float gammaCorrect(float c) { + if (c <= 0.0031308) + return 12.92 * c; + else + return (1.0 + 0.055) * pow(c, 1.0 / 2.4) - 0.055; +} + +//Used for WebApp +void parseJsonAndSetPixel(){ + int prevmil = millis(); + deserializeJson(doc, ServerResponseBody2); + Serial.println(millis()-prevmil); + for(int frame = 0; frame < doc.size(); frame++){ + for(int y = 0; y < 20/*doc[0].size()*/; y++){ + for(int x = 0; x < 20/*doc[0][y].size()*/; x++){ + NeoPixel.setPixelColor(getPixelByCoordinate(x, y), NeoPixel.Color(doc[frame][y][x][0], doc[frame][y][x][1], doc[frame][y][x][2])); + Serial.print("Pixel "); + Serial.print(getPixelByCoordinate(x, y)); + Serial.print(" Red "); + Serial.println((int)doc[frame][y][x][0]); + } + } + NeoPixel.show(); + delay(1); + } +} + +int getPixelByCoordinate(int x, int y){ //Starts from top left 0,0 + int pixel = x+y*COLLUMMS_PANEL; + + if(x < COLLUMMS_PANEL/2 && y < ROWS_PANEL/2){ //TOPLEFT + return pixel = x+y*COLLUMMS_PANEL/2; + } + else if (x >= COLLUMMS_PANEL/2 && y < ROWS_PANEL/2){ //TOPRIGHT + return pixel = (x-10)+y*COLLUMMS_PANEL/2+100; + } + else if(x < COLLUMMS_PANEL/2 && y >= ROWS_PANEL/2){ //BOTLEFT + return pixel = x+(y-10)*COLLUMMS_PANEL/2 + 300; + } + else{ //BOTRIGHT + return pixel = (x-10)+(y-10)*COLLUMMS_PANEL/2 + 200; + } +} + +#ifdef WebServerOn +void handleSet() { + if (server.hasArg("plain") == false) { + server.send(400, "text/plain", "No ServerResponseBody received"); + return; + } + serverFrameOverride = 1; + ServerResponseBody2 = server.arg("plain"); // RAW POST ServerResponseBody + server.send(200, "application/json", "Server recieved data!"); + Serial.println(ServerResponseBody2); + parseJsonAndSetPixel(); +} + +void handleNormal(){ + serverFrameOverride = 0; + server.send(200, "application/json", "Server recieved data!"); +} +#endif + +#ifdef WebServerOn +void handleRoot() { + server.send(200, "text/html", R"rawliteral( + + + + +Multi-Frame RGB Pixel Editor + + + + + + + + +

Multi-Frame RGB Pixel Editor

+ +
+ + + + + + + Frame 1 / 1 + + + + + + + + + + + +
+ + + + + +
+
+
Previous
+ +
+
+ +
Pixel: —
+
🧽
+ + + + + + +)rawliteral"); +} +#endif diff --git a/sin_table_256.inc b/sin_table_256.inc new file mode 100644 index 0000000..c3f534c --- /dev/null +++ b/sin_table_256.inc @@ -0,0 +1,256 @@ +0.00000000f, +0.02454123f, +0.04906767f, +0.07356456f, +0.09801714f, +0.12241068f, +0.14673047f, +0.17096189f, +0.19509032f, +0.21910124f, +0.24298018f, +0.26671276f, +0.29028468f, +0.31368174f, +0.33688985f, +0.35989504f, +0.38268343f, +0.40524131f, +0.42755509f, +0.44961133f, +0.47139674f, +0.49289819f, +0.51410274f, +0.53499762f, +0.55557023f, +0.57580819f, +0.59569930f, +0.61523159f, +0.63439328f, +0.65317284f, +0.67155895f, +0.68954054f, +0.70710678f, +0.72424708f, +0.74095113f, +0.75720885f, +0.77301045f, +0.78834643f, +0.80320753f, +0.81758481f, +0.83146961f, +0.84485357f, +0.85772861f, +0.87008699f, +0.88192126f, +0.89322430f, +0.90398929f, +0.91420976f, +0.92387953f, +0.93299280f, +0.94154407f, +0.94952818f, +0.95694034f, +0.96377607f, +0.97003125f, +0.97570213f, +0.98078528f, +0.98527764f, +0.98917651f, +0.99247953f, +0.99518473f, +0.99729046f, +0.99879546f, +0.99969882f, +1.00000000f, +0.99969882f, +0.99879546f, +0.99729046f, +0.99518473f, +0.99247953f, +0.98917651f, +0.98527764f, +0.98078528f, +0.97570213f, +0.97003125f, +0.96377607f, +0.95694034f, +0.94952818f, +0.94154407f, +0.93299280f, +0.92387953f, +0.91420976f, +0.90398929f, +0.89322430f, +0.88192126f, +0.87008699f, +0.85772861f, +0.84485357f, +0.83146961f, +0.81758481f, +0.80320753f, +0.78834643f, +0.77301045f, +0.75720885f, +0.74095113f, +0.72424708f, +0.70710678f, +0.68954054f, +0.67155895f, +0.65317284f, +0.63439328f, +0.61523159f, +0.59569930f, +0.57580819f, +0.55557023f, +0.53499762f, +0.51410274f, +0.49289819f, +0.47139674f, +0.44961133f, +0.42755509f, +0.40524131f, +0.38268343f, +0.35989504f, +0.33688985f, +0.31368174f, +0.29028468f, +0.26671276f, +0.24298018f, +0.21910124f, +0.19509032f, +0.17096189f, +0.14673047f, +0.12241068f, +0.09801714f, +0.07356456f, +0.04906767f, +0.02454123f, +0.00000000f, +-0.02454123f, +-0.04906767f, +-0.07356456f, +-0.09801714f, +-0.12241068f, +-0.14673047f, +-0.17096189f, +-0.19509032f, +-0.21910124f, +-0.24298018f, +-0.26671276f, +-0.29028468f, +-0.31368174f, +-0.33688985f, +-0.35989504f, +-0.38268343f, +-0.40524131f, +-0.42755509f, +-0.44961133f, +-0.47139674f, +-0.49289819f, +-0.51410274f, +-0.53499762f, +-0.55557023f, +-0.57580819f, +-0.59569930f, +-0.61523159f, +-0.63439328f, +-0.65317284f, +-0.67155895f, +-0.68954054f, +-0.70710678f, +-0.72424708f, +-0.74095113f, +-0.75720885f, +-0.77301045f, +-0.78834643f, +-0.80320753f, +-0.81758481f, +-0.83146961f, +-0.84485357f, +-0.85772861f, +-0.87008699f, +-0.88192126f, +-0.89322430f, +-0.90398929f, +-0.91420976f, +-0.92387953f, +-0.93299280f, +-0.94154407f, +-0.94952818f, +-0.95694034f, +-0.96377607f, +-0.97003125f, +-0.97570213f, +-0.98078528f, +-0.98527764f, +-0.98917651f, +-0.99247953f, +-0.99518473f, +-0.99729046f, +-0.99879546f, +-0.99969882f, +-1.00000000f, +-0.99969882f, +-0.99879546f, +-0.99729046f, +-0.99518473f, +-0.99247953f, +-0.98917651f, +-0.98527764f, +-0.98078528f, +-0.97570213f, +-0.97003125f, +-0.96377607f, +-0.95694034f, +-0.94952818f, +-0.94154407f, +-0.93299280f, +-0.92387953f, +-0.91420976f, +-0.90398929f, +-0.89322430f, +-0.88192126f, +-0.87008699f, +-0.85772861f, +-0.84485357f, +-0.83146961f, +-0.81758481f, +-0.80320753f, +-0.78834643f, +-0.77301045f, +-0.75720885f, +-0.74095113f, +-0.72424708f, +-0.70710678f, +-0.68954054f, +-0.67155895f, +-0.65317284f, +-0.63439328f, +-0.61523159f, +-0.59569930f, +-0.57580819f, +-0.55557023f, +-0.53499762f, +-0.51410274f, +-0.49289819f, +-0.47139674f, +-0.44961133f, +-0.42755509f, +-0.40524131f, +-0.38268343f, +-0.35989504f, +-0.33688985f, +-0.31368174f, +-0.29028468f, +-0.26671276f, +-0.24298018f, +-0.21910124f, +-0.19509032f, +-0.17096189f, +-0.14673047f, +-0.12241068f, +-0.09801714f, +-0.07356456f, +-0.04906767f, +-0.02454123f