- commit
- 7f1756e
- parent
- 584dc77
- author
- Neale Pickett
- date
- 2024-06-09 21:53:19 -0600 MDT
New numeric clock
+10,
-0
1@@ -0,0 +1,10 @@
2+## v2 - 2024-06-09
3+### Added
4+* New Clock mode with arabic numerals
5+* Firmware version now displayed at boot time
6+* Rotation can be hardcoded based on MAC address.
7+
8+### Changed
9+* Tweaked gamma adjustment for better yellows
10+* Makefile closer to something useful
11+* Updated library versions
M
Makefile
+3,
-3
1@@ -1,8 +1,8 @@
2-BOARD = --board adafruit:samd:adafruit_trinket_m0
3+BOARD = --board esp32:esp32:featheresp32
4
5-verify: main.ino
6+verify: wallart.ino
7 arduino --verify $(BOARD) $<
8
9-install: main.ino
10+install: wallart.ino
11 arduino --upload $(BOARD) $<
12
+20,
-0
1@@ -96,6 +96,16 @@ it displays something like a clock.
2 * Each pixel in the bottom row is 25 seconds
3 * There are four pixels around the bottom that move every 5 seconds
4
5+Build Dependencies
6+------------
7+
8+You'll need the following:
9+
10+* esp32 boards: Arduino ESP32 Feather
11+* FastLED library
12+* WifiManagerTZ library (and its dependencies)
13+* ArduinoHttpClient library
14+
15
16 Updating Firmware
17 -----------------
18@@ -123,3 +133,13 @@ but if you want to make NeoPixel art,
19 think hard about what the end result should look like.
20 It's not enough to make a cool light show;
21 it has to make people wonder "what is that for?"
22+
23+
24+Apology
25+----------
26+
27+I am no longer a C++ programmer.
28+The structure of this code is awful.
29+I'm sorry.
30+I didn't feel like a 2-day refresher in a language I never use,
31+for code nobody else is likely to ever compile.
A
clock.h
+85,
-0
1@@ -0,0 +1,85 @@
2+const uint8_t digits4x4[5][4] = {
3+ {
4+ 0b00100110,
5+ 0b00101010,
6+ 0b00101010,
7+ 0b00101100,
8+ },
9+ {
10+ 0b11001110,
11+ 0b01100010,
12+ 0b00101000,
13+ 0b11001110,
14+ },
15+ {
16+ 0b11101010,
17+ 0b10001010,
18+ 0b00101110,
19+ 0b11100010,
20+ },
21+ {
22+ 0b11101000,
23+ 0b00101100,
24+ 0b00101010,
25+ 0b00100110,
26+ },
27+ {
28+ 0b11001110,
29+ 0b10101010,
30+ 0b01101110,
31+ 0b00101110,
32+ }
33+};
34+
35+void digitDraw(int xoffset, int yoffset, int val, CRGB color) {
36+ for (int y=0; y<4; y++) {
37+ uint8_t row = digits4x4[val/2][y] >> (4*(val%2));
38+ for (int x=0; x<4; x++) {
39+ bool bit = (row>>(3-x)) & 1;
40+ int pos = (yoffset+y)*8 + (xoffset+x);
41+ if (bit) {
42+ grid[pos] = color;
43+ }
44+ }
45+ }
46+}
47+
48+void displayTimeDigits(bool day, unsigned long duration = 20*SECOND) {
49+ unsigned long end = millis() + duration;
50+
51+ bool flash = false;
52+
53+ while (millis() < end) {
54+ struct tm info;
55+ getLocalTime(&info);
56+
57+ int h0 = (info.tm_hour / 1) % 10;
58+ int h1 = (info.tm_hour / 10) % 10;
59+ int m0 = (info.tm_min / 1) % 10;
60+ int m1 = (info.tm_min / 10) % 10;
61+
62+ uint8_t hhue = day?HUE_AQUA:HUE_ORANGE;
63+ uint8_t mhue = day?HUE_ORANGE:HUE_RED;
64+
65+ // Draw background
66+ if (day) {
67+ fill_solid(grid, 32, CHSV(hhue, 120, 32));
68+ fill_solid(grid+32, 32, CHSV(mhue, 120, 32));
69+ } else {
70+ clear();
71+ }
72+
73+ // Draw foreground
74+ CRGB hcolor = CHSV(hhue, 240, 120);
75+ CRGB mcolor = CHSV(mhue, 120, 120);
76+ digitDraw(0, 0, h1, hcolor);
77+ digitDraw(4, 0, h0, hcolor);
78+ digitDraw(0, 4, m1, mcolor);
79+ digitDraw(4, 4, m0, mcolor);
80+
81+ show();
82+ pause(SECOND);
83+
84+ flash = !flash;
85+ }
86+}
+1,
-1
1@@ -31,7 +31,7 @@ void network_setup(char *password) {
2 String hostname = "WallArt";
3
4 WiFiManagerNS::NTP::onTimeAvailable(&on_time_available);
5- WiFiManagerNS::init(&wfm);
6+ WiFiManagerNS::init(&wfm, nullptr);
7
8 std::vector<const char *> menu = {"wifi", "info", "custom", "param", "sep", "update", "restart", "exit"};
9 wfm.setMenu(menu);
+4,
-4
1@@ -3,14 +3,14 @@
2 #include "picker.h"
3
4 Picker::Picker() {
5- val = random(1, 256);
6+ this->val = random(1, 256);
7 }
8
9 bool Picker::Pick(uint8_t likelihood) {
10+ bool picked = false;
11 if ((val > 0) && (val <= likelihood)) {
12- val = 0;
13- return true;
14+ picked = true;
15 }
16 val -= likelihood;
17- return false;
18+ return picked;
19 }
M
picker.h
+1,
-1
1@@ -7,5 +7,5 @@ public:
2 Picker();
3 bool Pick(uint8_t);
4 private:
5- uint8_t val;
6+ int val;
7 };
+29,
-0
1@@ -0,0 +1,29 @@
2+#pragma once
3+
4+/*
5+ * The hours when the day begins and ends.
6+ * At night, all you get is a dim clock.
7+ */
8+#define DAY_BEGIN 6
9+#define DAY_END 20
10+#define DAY_BRIGHTNESS 0x80
11+#define NIGHT_BRIGHTNESS 0x10
12+
13+/*
14+ * Define these to fetch from a wallart-server
15+ *
16+ * https://git.woozle.org/neale/wallart-server
17+ */
18+#define ART_HOSTNAME "www.woozle.org"
19+#define ART_PORT 443
20+#define ART_PATH "/wallart/wallart.bin"
21+
22+/*
23+ * The password used when running as an access point.
24+ */
25+#define WFM_PASSWORD "artsy fartsy"
26+
27+/*
28+ * The output pin your neopixel array is connected to.
29+ */
30+#define NEOPIXEL_PIN 32
+90,
-55
1@@ -6,34 +6,51 @@
2 #include "durations.h"
3 #include "picker.h"
4 #include "network.h"
5+#include "settings.h"
6
7-#define NEOPIXEL_PIN 32
8+#define VERSION 2
9 #define GRIDLEN 64
10-#define WFM_PASSWORD "artsy fartsy"
11-
12-/*
13- * The hours when the day begins and ends.
14- * At night, all you get is a dim clock.
15- */
16-#define DAY_BEGIN 6
17-#define DAY_END 20
18-#define DAY_BRIGHTNESS 0x80
19-#define NIGHT_BRIGHTNESS 0x10
20-
21-/*
22- * Define these to fetch from a wallart-server
23- *
24- * https://git.woozle.org/neale/wallart-server
25- */
26-#define ART_HOSTNAME "www.woozle.org"
27-#define ART_PORT 443
28-#define ART_PATH "/wallart/wallart.bin"
29
30 #define HTTPS_TIMEOUT (2 * SECOND)
31 #define IMAGE_PULL_MIN_INTERVAL (5 * MINUTE)
32
33-
34 CRGB grid[GRIDLEN];
35+CRGB actual[GRIDLEN];
36+
37+// Rotation, in degrees: [0, 90, 180, 270]
38+int rotation = 0;
39+
40+void show() {
41+ for (int y = 0; y < 8; y++) {
42+ for (int x = 0; x < 8; x++) {
43+ int pos;
44+ switch (rotation) {
45+ case 90:
46+ pos = (x)*8 + (7-y);
47+ break;
48+ case 180:
49+ pos = (7-y)*8 + (7-x);
50+ break;
51+ case 270:
52+ pos = (7-x)*8 + y;
53+ break;
54+ default:
55+ pos = (y)*8 + x;
56+ break;
57+ }
58+ actual[pos] = grid[y*8 + x];
59+ }
60+ }
61+ FastLED.show();
62+}
63+
64+void clear() {
65+ fill_solid(grid, GRIDLEN, CRGB::Black);
66+}
67+
68+// I am so ashamed of this.
69+// But C++ is a real pain for me at this point.
70+#include "clock.h"
71
72 void fade(int cycles = 2) {
73 int reps = (cycles*GRIDLEN) + random(GRIDLEN);
74@@ -43,7 +60,7 @@ void fade(int cycles = 2) {
75 uint8_t p = cm5xlat(8, (i+pos) % GRIDLEN);
76 grid[p] = CHSV(hue, 255, pos * 32);
77 }
78- FastLED.show();
79+ show();
80 pause(80);
81 }
82 }
83@@ -51,7 +68,7 @@ void fade(int cycles = 2) {
84 void singleCursor(int count = 80) {
85 for (int i = 0; i < count; i++) {
86 grid[20] = CHSV(0, 210, 127 * (i%2));
87- FastLED.show();
88+ show();
89 pause(120);
90 }
91 }
92@@ -66,7 +83,7 @@ void sparkle(int cycles=50) {
93 pos[j] = random(GRIDLEN);
94 grid[pos[j]] = CRGB::Gray;
95 }
96- FastLED.show();
97+ show();
98 pause(40);
99 }
100 }
101@@ -101,7 +118,7 @@ void glitchPulse(int cycles=1000) {
102 grid[pos[i]] = c;
103 steps[i]--;
104 }
105- FastLED.show();
106+ show();
107 pause(100);
108 }
109 }
110@@ -129,7 +146,7 @@ void conwayish(int cycles=5000) {
111 left[i]--;
112 }
113 }
114- FastLED.show();
115+ show();
116 pause(20);
117 }
118 }
119@@ -158,27 +175,45 @@ void cm5(uint8_t width=0, int cycles=200) {
120 grid[xpos] = CHSV(0, 255, val);
121 }
122 }
123- FastLED.show();
124+ show();
125 pause(500);
126 }
127 }
128
129+// Display MAC address at startup.
130 void displayMacAddress(int cycles=40) {
131 uint64_t addr = ESP.getEfuseMac();
132
133- for (; cycles > 0; cycles -= 1) {
134- bool conn = connected();
135+ Serial.println("mac=" + String(ESP.getEfuseMac(), HEX));
136+
137+ // Set some custom things per device.
138+ // It would have been nice if doing this in the Access Point UI were easier than switching the MAC address.
139+ switch (addr) {
140+ case 0x18fc1d519140:
141+ rotation = 270;
142+ break;
143+ }
144
145- fill_solid(grid, GRIDLEN, CHSV(conn?HUE_AQUA:HUE_RED, 128, 64));
146- for (int i = 0; i < 48; i++) {
147- int pos = i + 8;
148- grid[pos] = CHSV(HUE_YELLOW, 255, ((addr>>(47-i)) & 1)?255:64);
149+ for (; cycles > 0; cycles -= 1) {
150+ // Top: version
151+ for (int i = 0; i < 8; i++) {
152+ bool bit = (VERSION>>i) & 1;
153+ grid[7-i] = bit ? CRGB::Black : CRGB::Aqua;
154 }
155- grid[0] = CRGB::Black;
156- if (!conn && (cycles % 2)) {
157- grid[1] = CRGB::Black;
158+
159+ // Middle: MAC address
160+ for (int octet = 0; octet < 6; octet++) {
161+ for (int i = 0; i < 8; i++) {
162+ int pos = 8 + (octet*8) + (7-i);
163+ bool bit = (addr>>(octet*8 + i)) & 1;
164+ grid[pos] = bit ? CRGB::Yellow: CRGB::Black;
165+ }
166 }
167- FastLED.show();
168+
169+ // Bottom: connected status
170+ fill_solid(grid+56, 8, connected() ? CRGB::Aqua : CRGB::Red);
171+
172+ show();
173 pause(250*MILLISECOND);
174 }
175 }
176@@ -194,7 +229,7 @@ void netart(int count=40) {
177
178 for (int i = 0; i < count; i++) {
179 memcpy(grid, NetArt[i%NetArtFrames], GRIDLEN*3);
180- FastLED.show();
181+ show();
182 pause(500);
183 }
184 }
185@@ -206,7 +241,7 @@ uint8_t netgetStatus(uint8_t hue) {
186 positions[j] = random(GRIDLEN);
187 grid[positions[j]] = CHSV(hue, 255, 180);
188 }
189- FastLED.show();
190+ show();
191 pause(500);
192 return hue;
193 }
194@@ -267,17 +302,17 @@ void spinner(int count=32) {
195 for (int i = 0; i < count; i++) {
196 int pos = spinner_pos[i % 4];
197 grid[pos] = CRGB::OliveDrab;
198- FastLED.show();
199+ show();
200 pause(125);
201 grid[pos] = CRGB::Black;
202 }
203 }
204
205-void displayTime(unsigned long duration = 20*SECOND) {
206+void displayTimeDozenal(unsigned long duration = 20*SECOND) {
207 if (!clock_is_set()) return;
208 unsigned long end = millis() + duration;
209
210- FastLED.clear();
211+ clear();
212
213 while (millis() < end) {
214 struct tm info;
215@@ -317,7 +352,7 @@ void displayTime(unsigned long duration = 20*SECOND) {
216 grid[pos + 24] = CHSV(HUE_RED, 255, (i<mm)?128:48);
217 grid[pos + 48] = CHSV(HUE_PINK, 128, (i<ss)?96:48);
218 }
219- FastLED.show();
220+ show();
221
222 pause(250 * MILLISECOND);
223 }
224@@ -327,20 +362,19 @@ void setup() {
225 pinMode(RESET_PIN, INPUT_PULLUP);
226 pinMode(LED_BUILTIN, OUTPUT);
227 Serial.begin(19200);
228- FastLED.addLeds<WS2812, NEOPIXEL_PIN, GRB>(grid, GRIDLEN);
229+ FastLED.addLeds<WS2812, NEOPIXEL_PIN, GRB>(actual, GRIDLEN);
230 // Maybe it's the plexiglass, but for my build, I need to dial back the red
231- FastLED.setCorrection(0xd0ffff);
232+ //FastLED.setCorrection(0xd0ffff);
233 network_setup(WFM_PASSWORD);
234
235 // Show our mac address, for debugging?
236+ FastLED.setBrightness(DAY_BRIGHTNESS);
237 displayMacAddress();
238 sparkle();
239 }
240
241 void loop() {
242 Picker p;
243- uint8_t getprob = 4;
244- bool conn = connected();
245 bool day = true;
246
247 if (clock_is_set()) {
248@@ -350,16 +384,17 @@ void loop() {
249 }
250 FastLED.setBrightness(day?DAY_BRIGHTNESS:NIGHT_BRIGHTNESS);
251
252- // If we don't yet have net art, try a little harder to get it.
253- if ((NetArtFrames == 0) || !conn) {
254- getprob = 16;
255- }
256-
257+ // At night, always display the clock
258 if (!day && clock_is_set()) {
259- displayTime();
260+ displayTimeDigits(day);
261+ return;
262+ }
263+
264+ if (p.Pick(4) && clock_is_set()) {
265+ displayTimeDigits(day, 2 * MINUTE);
266 } else if (p.Pick(4) && clock_is_set()) {
267- displayTime(2 * MINUTE);
268- } else if (p.Pick(getprob)) {
269+ displayTimeDozenal();
270+ } else if (p.Pick(4)) {
271 netget();
272 } else if (day && p.Pick(4)) {
273 // These can be hella bright