wallart/wallart.ino

349 lines
7.6 KiB
Arduino
Raw Normal View History

2021-12-31 15:58:27 -07:00
#include <FastLED.h>
2022-07-19 21:14:34 -06:00
#include <ArduinoHttpClient.h>
#include <WiFiClientSecure.h>
2023-01-07 12:12:46 -07:00
#include <WiFiUdp.h>
#include <NTPClient.h>
2023-04-09 12:42:02 -06:00
#include <Time.h>
#include "durations.h"
#include "timezones.h"
#include "picker.h"
2022-07-17 22:22:16 -06:00
#include "network.h"
2021-12-31 15:58:27 -07:00
2022-07-17 22:22:16 -06:00
#define NEOPIXEL_PIN 32
#define GRIDLEN 64
#define WFM_PASSWORD "artsy fartsy"
2023-04-09 12:42:02 -06:00
#define TIMEZONE TZ_US_Mountain
2022-07-19 21:14:34 -06:00
2023-01-07 12:12:46 -07:00
/*
* The hours when the day begins and ends.
2023-04-09 12:42:02 -06:00
* At night, all you get is a dim clock.
2023-01-07 12:12:46 -07:00
*/
2023-04-09 12:42:02 -06:00
#define DAY_BEGIN 6
#define DAY_END 20
2023-01-07 12:12:46 -07:00
#define DAY_BRIGHTNESS 0x80
#define NIGHT_BRIGHTNESS 0x10
/*
* Define these to fetch from a wallart-server
*
* https://git.woozle.org/neale/wallart-server
*/
#define ART_HOSTNAME "www.woozle.org"
#define ART_PORT 443
#define ART_PATH "/wallart/wallart.bin"
2021-12-31 15:58:27 -07:00
2022-07-19 21:14:34 -06:00
#define HTTPS_TIMEOUT (2 * SECOND)
2021-12-31 15:58:27 -07:00
CRGB grid[GRIDLEN];
2023-01-07 12:12:46 -07:00
WiFiUDP ntpUDP;
2023-04-09 12:42:02 -06:00
NTPClient timeClient(ntpUDP);
2023-01-07 12:12:46 -07:00
2022-07-17 22:22:16 -06:00
void setup() {
FastLED.addLeds<WS2812, NEOPIXEL_PIN, GRB>(grid, GRIDLEN);
2023-01-06 12:50:46 -07:00
// Maybe it's the plexiglass but for my build I really need to dial back the red
FastLED.setCorrection(0xc0ffff);
2022-07-17 22:22:16 -06:00
network_setup(WFM_PASSWORD);
}
2023-04-09 12:42:02 -06:00
bool updateTime() {
if (timeClient.update()) {
time_t now = timeClient.getEpochTime();
time_t local = TIMEZONE.toLocal(now);
setTime(local);
return true;
}
return false;
}
void fade(int cycles = 2) {
int reps = (cycles*GRIDLEN) + random(GRIDLEN);
2021-12-31 15:58:27 -07:00
int hue = random(256);
for (int i = 0; i < reps; i++) {
for (int pos = 0; pos < 8; pos++) {
2023-01-06 12:50:46 -07:00
uint8_t p = cm5xlat(8, (i+pos) % GRIDLEN);
grid[p] = CHSV(hue, 255, pos * 32);
2021-12-31 15:58:27 -07:00
}
FastLED.show();
2022-07-17 22:22:16 -06:00
pause(80);
2021-12-31 15:58:27 -07:00
}
}
void singleCursor(int count = 80) {
2022-07-17 22:22:16 -06:00
for (int i = 0; i < count; i++) {
2021-12-31 15:58:27 -07:00
grid[20] = CHSV(0, 210, 127 * (i%2));
FastLED.show();
2022-07-17 22:22:16 -06:00
pause(120);
2021-12-31 15:58:27 -07:00
}
}
#define NUM_SPARKS 3
void sparkle(int cycles=50) {
2022-07-18 07:42:52 -06:00
int pos[NUM_SPARKS] = {0};
for (int i = 0; i < cycles; i++) {
for (int j = 0; j < NUM_SPARKS; j++) {
grid[pos[j]] = CRGB::Black;
pos[j] = random(GRIDLEN);
grid[pos[j]] = CRGB::Gray;
}
FastLED.show();
pause(40);
}
2021-12-31 15:58:27 -07:00
}
#define NUM_GLITCH 4
#define GLITCH_FRAMES 64
void glitchPulse(int cycles=1000) {
2021-12-31 15:58:27 -07:00
int steps[NUM_GLITCH] = {0};
int pos[NUM_GLITCH] = {0};
CRGB color[NUM_GLITCH];
for (int i = 0; i < NUM_GLITCH; i++) {
steps[i] = GLITCH_FRAMES / NUM_GLITCH * i;
color[i] = CRGB::Brown;
}
for (int frame = 0; frame < cycles; frame++) {
2021-12-31 15:58:27 -07:00
for (int i = 0; i < NUM_GLITCH; i++) {
if (steps[i] == 0) {
steps[i] = GLITCH_FRAMES;
pos[i] = random(GRIDLEN);
color[i] = CHSV(random(256), 64 + random(64), 255);
}
CRGB c = color[i];
int bmask = (0xff * steps[i] / 32) & 0xff;
if (steps[i] == GLITCH_FRAMES/2) {
bmask = 0xff - bmask;
}
c.red &= bmask;
c.green &= bmask;
c.blue &= bmask;
grid[pos[i]] = c;
steps[i]--;
}
FastLED.show();
2022-07-17 22:22:16 -06:00
pause(100);
2021-12-31 15:58:27 -07:00
}
}
2022-07-17 22:22:16 -06:00
void conwayish(int cycles=5000) {
uint8_t total[GRIDLEN];
uint8_t left[GRIDLEN] = {0};
uint8_t hue = random(0, 64);
for (int i = 0; i < GRIDLEN; i++) {
total[i] = random(64, 256);
left[i] = total[i];
}
2021-12-31 15:58:27 -07:00
for (int frame = 0; frame < cycles; frame++) {
for (int i = 0; i < GRIDLEN; i++) {
if (left[i] == 0) {
left[i] = total[i];
if (grid[i].getLuma() == 0) {
grid[i].setHSV(hue, 180, 192);
} else {
grid[i] = CRGB::Black;
}
} else {
left[i]--;
}
}
FastLED.show();
2022-07-17 22:22:16 -06:00
pause(20);
}
2021-12-31 15:58:27 -07:00
}
2023-01-06 12:50:46 -07:00
uint8_t cm5xlat(uint8_t width, uint8_t pos) {
if (width == 0) {
return pos;
}
uint8_t x = pos % width;
uint8_t y = pos / width;
uint8_t odd = y % 2;
return (y*width) + ((width-x-1)*odd) + (x*(1-odd));
}
void cm5(uint8_t width=0, int cycles=200) {
2022-07-17 22:22:16 -06:00
for (int frame = 0; frame < cycles; frame++) {
int val = 127 * random(2);
2023-01-06 12:50:46 -07:00
for (uint8_t pos = 0; pos < GRIDLEN; pos++) {
uint8_t xpos = cm5xlat(width, pos);
2022-07-17 22:22:16 -06:00
if (pos < GRIDLEN-1) {
2023-01-06 12:50:46 -07:00
uint8_t x2pos = cm5xlat(width, pos+1);
grid[xpos] = grid[x2pos];
2022-07-17 22:22:16 -06:00
} else {
2023-01-06 12:50:46 -07:00
grid[xpos] = CHSV(0, 255, val);
2022-07-17 22:22:16 -06:00
}
}
FastLED.show();
pause(500);
}
}
// Art from the network
2022-07-19 21:14:34 -06:00
int NetArtFrames = 0;
CRGB NetArt[8][GRIDLEN];
2022-07-17 22:22:16 -06:00
2022-07-19 21:14:34 -06:00
void netart(int count=40) {
if (NetArtFrames < 1) {
return;
}
for (int i = 0; i < count; i++) {
memcpy(grid, NetArt[i%NetArtFrames], GRIDLEN*3);
2022-07-17 22:22:16 -06:00
FastLED.show();
2022-07-19 21:14:34 -06:00
pause(500);
2022-07-17 22:22:16 -06:00
}
}
2022-07-19 21:14:34 -06:00
uint8_t netgetStatus(uint8_t hue) {
2022-07-17 22:22:16 -06:00
static int positions[4] = {0};
for (int j = 0; j < 4; j++) {
grid[positions[j]] = CHSV(0, 0, 0);
positions[j] = random(GRIDLEN);
grid[positions[j]] = CHSV(hue, 255, 180);
}
FastLED.show();
pause(500);
return hue;
2021-12-31 15:58:27 -07:00
}
2022-07-19 21:14:34 -06:00
void netget(int count=60) {
uint8_t hue = netgetStatus(HUE_BLUE);
2022-07-17 22:22:16 -06:00
2023-01-06 12:50:46 -07:00
#if defined(ART_HOSTNAME) && defined(ART_PORT) && defined(ART_PATH)
2022-07-17 22:22:16 -06:00
if (connected()) {
WiFiClientSecure scli;
2023-01-06 12:50:46 -07:00
hue = netgetStatus(HUE_AQUA);
2022-07-17 22:22:16 -06:00
scli.setInsecure();
2022-07-19 21:14:34 -06:00
HttpClient https(scli, ART_HOSTNAME, ART_PORT);
do {
if (https.get(ART_PATH) != 0) break;
2023-01-06 12:50:46 -07:00
hue = netgetStatus(HUE_GREEN);
2022-07-19 21:14:34 -06:00
if (https.skipResponseHeaders() != HTTP_SUCCESS) break;
2023-01-06 12:50:46 -07:00
hue = netgetStatus(HUE_YELLOW);
2022-07-19 21:14:34 -06:00
int artlen = https.read((uint8_t *)NetArt, sizeof(NetArt));
2023-01-06 12:50:46 -07:00
hue = netgetStatus(HUE_ORANGE);
2022-07-19 21:14:34 -06:00
NetArtFrames = (artlen / 3) / GRIDLEN;
} while(false);
https.stop();
2022-07-17 22:22:16 -06:00
}
2023-01-06 12:50:46 -07:00
#endif
2022-07-17 22:22:16 -06:00
for (int i = 0; i < count; i++) {
netgetStatus(hue);
}
}
2022-07-18 07:43:19 -06:00
const int spinner_pos[4] = {27, 28, 36, 35};
void spinner(int count=32) {
2022-07-18 07:42:52 -06:00
for (int i = 0; i < count; i++) {
2022-07-18 07:43:19 -06:00
int pos = spinner_pos[i % 4];
2022-07-18 07:42:52 -06:00
grid[pos] = CRGB::OliveDrab;
FastLED.show();
pause(125);
grid[pos] = CRGB::Black;
}
}
2022-07-17 22:22:16 -06:00
2023-01-07 12:12:46 -07:00
void displayTime(unsigned long duration = 20 * SECOND) {
2023-04-09 12:42:02 -06:00
if (timeStatus() != timeSet) return;
2023-01-07 12:12:46 -07:00
unsigned long end = millis() + duration;
FastLED.clear();
while (millis() < end) {
2023-04-09 12:42:02 -06:00
updateTime();
int hh = hour();
int mmss = now() % 3600;
2023-01-07 12:12:46 -07:00
uint8_t hue = HUE_YELLOW;
// Top: Hours
2023-04-09 12:42:02 -06:00
if (isPM()) {
2023-01-07 12:12:46 -07:00
hue = HUE_ORANGE;
hh -= 12;
}
// Middle: 5m (300s)
uint8_t mm = (mmss/300) % 12;
// Bottom: 25s
uint8_t ss = (mmss/25) % 12;
// Outer: 5s
uint8_t s = (mmss/5) % 5;
2023-01-09 07:19:55 -07:00
grid[64 - 7 - 1] = CHSV(HUE_PURPLE, 128, (s==1)?96:0);
grid[64 - 15 - 1] = CHSV(HUE_PURPLE, 128, (s==2)?96:0);
grid[64 - 8 - 1] = CHSV(HUE_PURPLE, 128, (s==3)?96:0);
grid[64 - 0 - 1] = CHSV(HUE_PURPLE, 128, (s==4)?96:0);
2023-01-07 12:12:46 -07:00
for (int i = 0; i < 12; i++) {
// Omit first and last position on a row
int pos = i + 1;
if (pos > 6) {
pos += 2;
}
grid[pos + 0] = CHSV(hue, 255, (i<hh)?128:48);
grid[pos + 24] = CHSV(HUE_RED, 255, (i<mm)?128:48);
grid[pos + 48] = CHSV(HUE_PINK, 128, (i<ss)?96:48);
2023-01-07 12:12:46 -07:00
}
FastLED.show();
pause(250 * MILLISECOND);
}
}
2021-12-31 15:58:27 -07:00
void loop() {
2022-07-17 22:22:16 -06:00
Picker p;
2023-01-06 12:50:46 -07:00
uint8_t getprob = 4;
2023-01-07 12:12:46 -07:00
bool conn = connected();
bool day = true;
updateTime();
if (timeStatus() == timeSet) {
2023-04-09 12:42:02 -06:00
int hh = hour();
2023-01-09 07:19:55 -07:00
day = ((hh >= DAY_BEGIN) && (hh < DAY_END));
2023-01-07 12:12:46 -07:00
}
FastLED.setBrightness(day?DAY_BRIGHTNESS:NIGHT_BRIGHTNESS);
2022-07-17 22:22:16 -06:00
// If we don't yet have net art, try a little harder to get it.
2023-01-07 12:12:46 -07:00
if ((NetArtFrames == 0) || !conn) {
2023-01-06 12:50:46 -07:00
getprob = 16;
}
if (!day || p.Pick(4)) {
// At night, only ever show the clock
displayTime(2 * MINUTE);
} else if (p.Pick(getprob)) {
2023-01-07 12:12:46 -07:00
netget();
} else if (day && p.Pick(4)) {
// These can be hella bright
2023-01-06 12:50:46 -07:00
netart();
2023-01-07 12:12:46 -07:00
} else if (p.Pick(1)) {
2022-07-17 22:22:16 -06:00
fade();
singleCursor(20);
} else if (p.Pick(1)) {
sparkle();
} else if (p.Pick(4)) {
singleCursor();
} else if (p.Pick(8)) {
conwayish();
} else if (p.Pick(8)) {
glitchPulse();
2023-01-06 12:50:46 -07:00
} else if (p.Pick(2)) {
cm5(0);
} else if (p.Pick(2)) {
cm5(8);
} else if (p.Pick(2)) {
cm5(16);
}
2022-07-17 22:22:16 -06:00
}