From 7f1756eccced195ac7b483e455838516954df2db Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 9 Jun 2024 20:53:19 -0700 Subject: [PATCH] New numeric clock --- CHANGELOG.md | 10 ++++ Makefile | 6 +-- README.md | 20 +++++++ clock.h | 85 ++++++++++++++++++++++++++++++ network.cpp | 2 +- picker.cpp | 8 +-- picker.h | 2 +- settings.h | 29 +++++++++++ wallart.ino | 145 ++++++++++++++++++++++++++++++++------------------- 9 files changed, 243 insertions(+), 64 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 clock.h create mode 100644 settings.h diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..438bf82 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +## v2 - 2024-06-09 +### Added +* New Clock mode with arabic numerals +* Firmware version now displayed at boot time +* Rotation can be hardcoded based on MAC address. + +### Changed +* Tweaked gamma adjustment for better yellows +* Makefile closer to something useful +* Updated library versions diff --git a/Makefile b/Makefile index 4a0d768..f1d972e 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -BOARD = --board adafruit:samd:adafruit_trinket_m0 +BOARD = --board esp32:esp32:featheresp32 -verify: main.ino +verify: wallart.ino arduino --verify $(BOARD) $< -install: main.ino +install: wallart.ino arduino --upload $(BOARD) $< diff --git a/README.md b/README.md index 511a420..b3b3ca5 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,16 @@ it displays something like a clock. * Each pixel in the bottom row is 25 seconds * There are four pixels around the bottom that move every 5 seconds +Build Dependencies +------------ + +You'll need the following: + +* esp32 boards: Arduino ESP32 Feather +* FastLED library +* WifiManagerTZ library (and its dependencies) +* ArduinoHttpClient library + Updating Firmware ----------------- @@ -123,3 +133,13 @@ but if you want to make NeoPixel art, think hard about what the end result should look like. It's not enough to make a cool light show; it has to make people wonder "what is that for?" + + +Apology +---------- + +I am no longer a C++ programmer. +The structure of this code is awful. +I'm sorry. +I didn't feel like a 2-day refresher in a language I never use, +for code nobody else is likely to ever compile. diff --git a/clock.h b/clock.h new file mode 100644 index 0000000..952e270 --- /dev/null +++ b/clock.h @@ -0,0 +1,85 @@ +const uint8_t digits4x4[5][4] = { + { + 0b00100110, + 0b00101010, + 0b00101010, + 0b00101100, + }, + { + 0b11001110, + 0b01100010, + 0b00101000, + 0b11001110, + }, + { + 0b11101010, + 0b10001010, + 0b00101110, + 0b11100010, + }, + { + 0b11101000, + 0b00101100, + 0b00101010, + 0b00100110, + }, + { + 0b11001110, + 0b10101010, + 0b01101110, + 0b00101110, + } +}; + +void digitDraw(int xoffset, int yoffset, int val, CRGB color) { + for (int y=0; y<4; y++) { + uint8_t row = digits4x4[val/2][y] >> (4*(val%2)); + for (int x=0; x<4; x++) { + bool bit = (row>>(3-x)) & 1; + int pos = (yoffset+y)*8 + (xoffset+x); + if (bit) { + grid[pos] = color; + } + } + } +} + +void displayTimeDigits(bool day, unsigned long duration = 20*SECOND) { + unsigned long end = millis() + duration; + + bool flash = false; + + while (millis() < end) { + struct tm info; + getLocalTime(&info); + + int h0 = (info.tm_hour / 1) % 10; + int h1 = (info.tm_hour / 10) % 10; + int m0 = (info.tm_min / 1) % 10; + int m1 = (info.tm_min / 10) % 10; + + uint8_t hhue = day?HUE_AQUA:HUE_ORANGE; + uint8_t mhue = day?HUE_ORANGE:HUE_RED; + + // Draw background + if (day) { + fill_solid(grid, 32, CHSV(hhue, 120, 32)); + fill_solid(grid+32, 32, CHSV(mhue, 120, 32)); + } else { + clear(); + } + + // Draw foreground + CRGB hcolor = CHSV(hhue, 240, 120); + CRGB mcolor = CHSV(mhue, 120, 120); + digitDraw(0, 0, h1, hcolor); + digitDraw(4, 0, h0, hcolor); + digitDraw(0, 4, m1, mcolor); + digitDraw(4, 4, m0, mcolor); + + show(); + pause(SECOND); + + flash = !flash; + } +} diff --git a/network.cpp b/network.cpp index f72e252..0c7f03a 100644 --- a/network.cpp +++ b/network.cpp @@ -31,7 +31,7 @@ void network_setup(char *password) { String hostname = "WallArt"; WiFiManagerNS::NTP::onTimeAvailable(&on_time_available); - WiFiManagerNS::init(&wfm); + WiFiManagerNS::init(&wfm, nullptr); std::vector menu = {"wifi", "info", "custom", "param", "sep", "update", "restart", "exit"}; wfm.setMenu(menu); diff --git a/picker.cpp b/picker.cpp index 1bd2c3e..cc49500 100644 --- a/picker.cpp +++ b/picker.cpp @@ -3,14 +3,14 @@ #include "picker.h" Picker::Picker() { - val = random(1, 256); + this->val = random(1, 256); } bool Picker::Pick(uint8_t likelihood) { + bool picked = false; if ((val > 0) && (val <= likelihood)) { - val = 0; - return true; + picked = true; } val -= likelihood; - return false; + return picked; } \ No newline at end of file diff --git a/picker.h b/picker.h index 1b29e06..ad3cbe7 100644 --- a/picker.h +++ b/picker.h @@ -7,5 +7,5 @@ public: Picker(); bool Pick(uint8_t); private: - uint8_t val; + int val; }; diff --git a/settings.h b/settings.h new file mode 100644 index 0000000..c3c4f09 --- /dev/null +++ b/settings.h @@ -0,0 +1,29 @@ +#pragma once + +/* + * The hours when the day begins and ends. + * At night, all you get is a dim clock. + */ +#define DAY_BEGIN 6 +#define DAY_END 20 +#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" + +/* + * The password used when running as an access point. + */ +#define WFM_PASSWORD "artsy fartsy" + +/* + * The output pin your neopixel array is connected to. + */ +#define NEOPIXEL_PIN 32 diff --git a/wallart.ino b/wallart.ino index 448f627..b20ac54 100644 --- a/wallart.ino +++ b/wallart.ino @@ -6,34 +6,51 @@ #include "durations.h" #include "picker.h" #include "network.h" +#include "settings.h" -#define NEOPIXEL_PIN 32 +#define VERSION 2 #define GRIDLEN 64 -#define WFM_PASSWORD "artsy fartsy" - -/* - * The hours when the day begins and ends. - * At night, all you get is a dim clock. - */ -#define DAY_BEGIN 6 -#define DAY_END 20 -#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" #define HTTPS_TIMEOUT (2 * SECOND) #define IMAGE_PULL_MIN_INTERVAL (5 * MINUTE) - CRGB grid[GRIDLEN]; +CRGB actual[GRIDLEN]; + +// Rotation, in degrees: [0, 90, 180, 270] +int rotation = 0; + +void show() { + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 8; x++) { + int pos; + switch (rotation) { + case 90: + pos = (x)*8 + (7-y); + break; + case 180: + pos = (7-y)*8 + (7-x); + break; + case 270: + pos = (7-x)*8 + y; + break; + default: + pos = (y)*8 + x; + break; + } + actual[pos] = grid[y*8 + x]; + } + } + FastLED.show(); +} + +void clear() { + fill_solid(grid, GRIDLEN, CRGB::Black); +} + +// I am so ashamed of this. +// But C++ is a real pain for me at this point. +#include "clock.h" void fade(int cycles = 2) { int reps = (cycles*GRIDLEN) + random(GRIDLEN); @@ -43,7 +60,7 @@ void fade(int cycles = 2) { uint8_t p = cm5xlat(8, (i+pos) % GRIDLEN); grid[p] = CHSV(hue, 255, pos * 32); } - FastLED.show(); + show(); pause(80); } } @@ -51,7 +68,7 @@ void fade(int cycles = 2) { void singleCursor(int count = 80) { for (int i = 0; i < count; i++) { grid[20] = CHSV(0, 210, 127 * (i%2)); - FastLED.show(); + show(); pause(120); } } @@ -66,7 +83,7 @@ void sparkle(int cycles=50) { pos[j] = random(GRIDLEN); grid[pos[j]] = CRGB::Gray; } - FastLED.show(); + show(); pause(40); } } @@ -101,7 +118,7 @@ void glitchPulse(int cycles=1000) { grid[pos[i]] = c; steps[i]--; } - FastLED.show(); + show(); pause(100); } } @@ -129,7 +146,7 @@ void conwayish(int cycles=5000) { left[i]--; } } - FastLED.show(); + show(); pause(20); } } @@ -158,27 +175,45 @@ void cm5(uint8_t width=0, int cycles=200) { grid[xpos] = CHSV(0, 255, val); } } - FastLED.show(); + show(); pause(500); } } +// Display MAC address at startup. void displayMacAddress(int cycles=40) { uint64_t addr = ESP.getEfuseMac(); - for (; cycles > 0; cycles -= 1) { - bool conn = connected(); + Serial.println("mac=" + String(ESP.getEfuseMac(), HEX)); - fill_solid(grid, GRIDLEN, CHSV(conn?HUE_AQUA:HUE_RED, 128, 64)); - for (int i = 0; i < 48; i++) { - int pos = i + 8; - grid[pos] = CHSV(HUE_YELLOW, 255, ((addr>>(47-i)) & 1)?255:64); + // Set some custom things per device. + // It would have been nice if doing this in the Access Point UI were easier than switching the MAC address. + switch (addr) { + case 0x18fc1d519140: + rotation = 270; + break; + } + + for (; cycles > 0; cycles -= 1) { + // Top: version + for (int i = 0; i < 8; i++) { + bool bit = (VERSION>>i) & 1; + grid[7-i] = bit ? CRGB::Black : CRGB::Aqua; } - grid[0] = CRGB::Black; - if (!conn && (cycles % 2)) { - grid[1] = CRGB::Black; + + // Middle: MAC address + for (int octet = 0; octet < 6; octet++) { + for (int i = 0; i < 8; i++) { + int pos = 8 + (octet*8) + (7-i); + bool bit = (addr>>(octet*8 + i)) & 1; + grid[pos] = bit ? CRGB::Yellow: CRGB::Black; + } } - FastLED.show(); + + // Bottom: connected status + fill_solid(grid+56, 8, connected() ? CRGB::Aqua : CRGB::Red); + + show(); pause(250*MILLISECOND); } } @@ -194,7 +229,7 @@ void netart(int count=40) { for (int i = 0; i < count; i++) { memcpy(grid, NetArt[i%NetArtFrames], GRIDLEN*3); - FastLED.show(); + show(); pause(500); } } @@ -206,7 +241,7 @@ uint8_t netgetStatus(uint8_t hue) { positions[j] = random(GRIDLEN); grid[positions[j]] = CHSV(hue, 255, 180); } - FastLED.show(); + show(); pause(500); return hue; } @@ -267,17 +302,17 @@ void spinner(int count=32) { for (int i = 0; i < count; i++) { int pos = spinner_pos[i % 4]; grid[pos] = CRGB::OliveDrab; - FastLED.show(); + show(); pause(125); grid[pos] = CRGB::Black; } } -void displayTime(unsigned long duration = 20*SECOND) { +void displayTimeDozenal(unsigned long duration = 20*SECOND) { if (!clock_is_set()) return; unsigned long end = millis() + duration; - FastLED.clear(); + clear(); while (millis() < end) { struct tm info; @@ -317,7 +352,7 @@ void displayTime(unsigned long duration = 20*SECOND) { grid[pos + 24] = CHSV(HUE_RED, 255, (i(grid, GRIDLEN); + FastLED.addLeds(actual, GRIDLEN); // Maybe it's the plexiglass, but for my build, I need to dial back the red - FastLED.setCorrection(0xd0ffff); + //FastLED.setCorrection(0xd0ffff); network_setup(WFM_PASSWORD); // Show our mac address, for debugging? + FastLED.setBrightness(DAY_BRIGHTNESS); displayMacAddress(); sparkle(); } void loop() { Picker p; - uint8_t getprob = 4; - bool conn = connected(); bool day = true; if (clock_is_set()) { @@ -350,16 +384,17 @@ void loop() { } FastLED.setBrightness(day?DAY_BRIGHTNESS:NIGHT_BRIGHTNESS); - // If we don't yet have net art, try a little harder to get it. - if ((NetArtFrames == 0) || !conn) { - getprob = 16; - } - + // At night, always display the clock if (!day && clock_is_set()) { - displayTime(); + displayTimeDigits(day); + return; + } + + if (p.Pick(4) && clock_is_set()) { + displayTimeDigits(day, 2 * MINUTE); } else if (p.Pick(4) && clock_is_set()) { - displayTime(2 * MINUTE); - } else if (p.Pick(getprob)) { + displayTimeDozenal(); + } else if (p.Pick(4)) { netget(); } else if (day && p.Pick(4)) { // These can be hella bright