- commit
- 584dc77
- parent
- ff69a45
- author
- Neale Pickett
- date
- 2023-12-31 10:26:12 -0700 MST
Merge branch 'master' of https://github.com/nealey/wallart
5 files changed,
+165,
-69
+42,
-3
1@@ -21,6 +21,41 @@ form different ideas about what it's displaying.
2 That's cool.
3
4
5+Setup
6+-----
7+
8+When you first plug it in,
9+you will see a yellow pattern with blue or red bars around it.
10+The pattern is your mac address.
11+If the bars are red and a pixel is flashing,
12+that means you need to set up WiFi.
13+
14+You can also look at the back for a red LED.
15+If it's lit, you need to set up WiFi.
16+
17+Get your phone or computer to connect to an access point
18+called "WallArt".
19+The password is "artsy fartsy", unless you changed it in the source code.
20+Once connected,
21+you should get a browser window that lets you connect.
22+If not, try going to http://neverssl.com/.
23+
24+Please configure the clock before the WiFi.
25+This will set up your time zone,
26+so it doesn't blind you in the middle of the night.
27+
28+You can clear the wifi information with a reset.
29+
30+
31+Reset
32+------
33+
34+Plug the device in,
35+and connect GND to pin A0 (right next to GND).
36+The red LED on the Feather board should come on immediately,
37+indicating it needs the network set up again.
38+
39+
40 Network Server
41 --------------
42
43@@ -55,9 +90,6 @@ Clock
44 At night,
45 and sometimes during the day,
46 it displays something like a clock.
47-You will need to tell it your time zone.
48-It doesn't do daylight saving time, sorry.
49-I suggest you set it to standard time and pretend it's in sync with the sun.
50
51 * Each pixel in the top row is 1 hour (3600 seconds)
52 * Each pixel in the middle row is 5 minutes (300 seconds)
53@@ -65,6 +97,13 @@ I suggest you set it to standard time and pretend it's in sync with the sun.
54 * There are four pixels around the bottom that move every 5 seconds
55
56
57+Updating Firmware
58+-----------------
59+
60+ python3 esptool.py --chip esp32 --port "/dev/ttyUSB0" --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x1000 wallart.ino.bootloader.bin 0x8000 wallart.ino.partitions.bin 0xe000 boot_app0.bin 0x10000 wallart.ino.bin
61+
62+
63+
64 Philosophy
65 ----------
66
+36,
-4
1@@ -1,28 +1,60 @@
2 #include <FastLED.h>
3 #include <WiFiManager.h>
4+#include <WiFiManagerTz.h>
5 #include <esp_wifi.h>
6+#include <esp_sntp.h>
7 #include <HTTPClient.h>
8 #include <WiFiClientSecure.h>
9 #include "network.h"
10
11 WiFiManager wfm;
12
13+void network_reset() {
14+ Serial.println("Resetting network");
15+ wfm.resetSettings();
16+}
17+
18+
19+bool time_was_accurate_once = false;
20+bool clock_is_set() {
21+ return time_was_accurate_once;
22+}
23+
24+void on_time_available(struct timeval *t) {
25+ struct tm timeInfo;
26+ getLocalTime(&timeInfo, 1000);
27+ Serial.println(&timeInfo, "%A, %B %d %Y %H:%M:%S %Z %z ");
28+ time_was_accurate_once = true;
29+}
30+
31 void network_setup(char *password) {
32- String hostid = String(ESP.getEfuseMac(), HEX);
33- String hostname = "Wall Art " + hostid;
34+ String hostname = "WallArt";
35+
36+ WiFiManagerNS::NTP::onTimeAvailable(&on_time_available);
37+ WiFiManagerNS::init(&wfm);
38
39+ std::vector<const char *> menu = {"wifi", "info", "custom", "param", "sep", "update", "restart", "exit"};
40+ wfm.setMenu(menu);
41 wfm.setConfigPortalBlocking(false);
42 wfm.setHostname(hostname);
43 wfm.autoConnect(hostname.c_str(), password);
44-
45- pinMode(LED_BUILTIN, OUTPUT);
46 }
47
48 bool connected() {
49 return WiFi.status() == WL_CONNECTED;
50 }
51
52+bool timeConfigured = false;
53+
54 void pause(uint32_t dwMs) {
55+ if (connected() && !timeConfigured) {
56+ WiFiManagerNS::configTime();
57+ timeConfigured = true;
58+ }
59+ if (!digitalRead(RESET_PIN)) {
60+ network_reset();
61+ }
62+
63 for (uint32_t t = 0; t < dwMs; t += 10) {
64 wfm.process();
65 digitalWrite(LED_BUILTIN, !connected());
+5,
-0
1@@ -1,6 +1,11 @@
2 #pragma once
3
4+// Short this to ground to reset the network
5+#define RESET_PIN 26
6+
7+void network_reset();
8 void network_setup(char *password);
9 bool connected();
10 void pause(uint32_t dwMs);
11 void netget(int count);
12+bool clock_is_set();
+0,
-17
1@@ -1,17 +0,0 @@
2-#include <Timezone.h>
3-
4-TimeChangeRule usEDT = {"EDT", Second, Sun, Mar, 2, -240};
5-TimeChangeRule usEST = {"EST", First, Sun, Nov, 2, -300};
6-Timezone TZ_US_Eastern(usEDT, usEST);
7-
8-TimeChangeRule usCDT = {"CDT", Second, Sun, Mar, 2, -300};
9-TimeChangeRule usCST = {"CST", First, Sun, Nov, 2, -360};
10-Timezone TZ_US_Central(usCDT, usCST);
11-
12-TimeChangeRule usMDT = {"MDT", Second, Sun, Mar, 2, -360};
13-TimeChangeRule usMST = {"MST", First, Sun, Nov, 2, -420};
14-Timezone TZ_US_Mountain(usMDT, usMST);
15-
16-TimeChangeRule usPDT = {"EDT", Second, Sun, Mar, 2, -420};
17-TimeChangeRule usPST = {"EST", First, Sun, Nov, 2, -480};
18-Timezone TZ_US_Pacific(usPDT, usPST);
+82,
-45
1@@ -2,17 +2,14 @@
2 #include <ArduinoHttpClient.h>
3 #include <WiFiClientSecure.h>
4 #include <WiFiUdp.h>
5-#include <NTPClient.h>
6-#include <Time.h>
7+#include <TimeLib.h>
8 #include "durations.h"
9-#include "timezones.h"
10 #include "picker.h"
11 #include "network.h"
12
13 #define NEOPIXEL_PIN 32
14 #define GRIDLEN 64
15 #define WFM_PASSWORD "artsy fartsy"
16-#define TIMEZONE TZ_US_Mountain
17
18 /*
19 * The hours when the day begins and ends.
20@@ -33,28 +30,10 @@
21 #define ART_PATH "/wallart/wallart.bin"
22
23 #define HTTPS_TIMEOUT (2 * SECOND)
24+#define IMAGE_PULL_MIN_INTERVAL (5 * MINUTE)
25
26-CRGB grid[GRIDLEN];
27-
28-WiFiUDP ntpUDP;
29-NTPClient timeClient(ntpUDP);
30-
31-void setup() {
32- FastLED.addLeds<WS2812, NEOPIXEL_PIN, GRB>(grid, GRIDLEN);
33- // Maybe it's the plexiglass but for my build I really need to dial back the red
34- FastLED.setCorrection(0xc0ffff);
35- network_setup(WFM_PASSWORD);
36-}
37
38-bool updateTime() {
39- if (timeClient.update()) {
40- time_t now = timeClient.getEpochTime();
41- time_t local = TIMEZONE.toLocal(now);
42- setTime(local);
43- return true;
44- }
45- return false;
46-}
47+CRGB grid[GRIDLEN];
48
49 void fade(int cycles = 2) {
50 int reps = (cycles*GRIDLEN) + random(GRIDLEN);
51@@ -184,6 +163,26 @@ void cm5(uint8_t width=0, int cycles=200) {
52 }
53 }
54
55+void displayMacAddress(int cycles=40) {
56+ uint64_t addr = ESP.getEfuseMac();
57+
58+ for (; cycles > 0; cycles -= 1) {
59+ bool conn = connected();
60+
61+ fill_solid(grid, GRIDLEN, CHSV(conn?HUE_AQUA:HUE_RED, 128, 64));
62+ for (int i = 0; i < 48; i++) {
63+ int pos = i + 8;
64+ grid[pos] = CHSV(HUE_YELLOW, 255, ((addr>>(47-i)) & 1)?255:64);
65+ }
66+ grid[0] = CRGB::Black;
67+ if (!conn && (cycles % 2)) {
68+ grid[1] = CRGB::Black;
69+ }
70+ FastLED.show();
71+ pause(250*MILLISECOND);
72+ }
73+}
74+
75 // Art from the network
76 int NetArtFrames = 0;
77 CRGB NetArt[8][GRIDLEN];
78@@ -214,25 +213,45 @@ uint8_t netgetStatus(uint8_t hue) {
79
80 void netget(int count=60) {
81 uint8_t hue = netgetStatus(HUE_BLUE);
82+ static unsigned long nextPull = 0; // when to pull next
83
84 #if defined(ART_HOSTNAME) && defined(ART_PORT) && defined(ART_PATH)
85- if (connected()) {
86+ if (millis() < nextPull) {
87+ // Let's not bombard the server
88+ hue = HUE_ORANGE;
89+ } else if (connected()) {
90 WiFiClientSecure scli;
91
92+ nextPull = millis() + IMAGE_PULL_MIN_INTERVAL;
93+
94 hue = netgetStatus(HUE_AQUA);
95 scli.setInsecure();
96
97 HttpClient https(scli, ART_HOSTNAME, ART_PORT);
98 do {
99- if (https.get(ART_PATH) != 0) break;
100+ String path = String(ART_PATH) + "?mac=" + String(ESP.getEfuseMac(), HEX);
101+ Serial.println(path);
102+ if (https.get(path) != 0) break;
103 hue = netgetStatus(HUE_GREEN);
104
105 if (https.skipResponseHeaders() != HTTP_SUCCESS) break;
106 hue = netgetStatus(HUE_YELLOW);
107
108- int artlen = https.read((uint8_t *)NetArt, sizeof(NetArt));
109- hue = netgetStatus(HUE_ORANGE);
110- NetArtFrames = (artlen / 3) / GRIDLEN;
111+ size_t readBytes = 0;
112+ for (int i = 0; i < 12; i++) {
113+ size_t artBytesLeft = sizeof(NetArt) - readBytes;
114+
115+ if (https.endOfBodyReached() || (artBytesLeft == 0)) {
116+ hue = netgetStatus(HUE_ORANGE);
117+ NetArtFrames = (readBytes / 3) / GRIDLEN;
118+ break;
119+ }
120+ int l = https.read((uint8_t *)NetArt + readBytes, artBytesLeft);
121+ if (-1 == l) {
122+ break;
123+ }
124+ readBytes += l;
125+ }
126 } while(false);
127 https.stop();
128 }
129@@ -254,19 +273,22 @@ void spinner(int count=32) {
130 }
131 }
132
133-void displayTime(unsigned long duration = 20 * SECOND) {
134- if (timeStatus() != timeSet) return;
135+void displayTime(unsigned long duration = 20*SECOND) {
136+ if (!clock_is_set()) return;
137 unsigned long end = millis() + duration;
138+
139 FastLED.clear();
140
141 while (millis() < end) {
142- updateTime();
143- int hh = hour();
144- int mmss = now() % 3600;
145+ struct tm info;
146+ getLocalTime(&info);
147+
148+ int hh = info.tm_hour;
149+ int mmss = (info.tm_min * 60) + info.tm_sec;
150 uint8_t hue = HUE_YELLOW;
151
152 // Top: Hours
153- if (isPM()) {
154+ if (hh >= 12) {
155 hue = HUE_ORANGE;
156 hh -= 12;
157 }
158@@ -279,10 +301,10 @@ void displayTime(unsigned long duration = 20 * SECOND) {
159
160 // Outer: 5s
161 uint8_t s = (mmss/5) % 5;
162- grid[64 - 7 - 1] = CHSV(HUE_PURPLE, 128, (s==1)?96:0);
163- grid[64 - 15 - 1] = CHSV(HUE_PURPLE, 128, (s==2)?96:0);
164- grid[64 - 8 - 1] = CHSV(HUE_PURPLE, 128, (s==3)?96:0);
165- grid[64 - 0 - 1] = CHSV(HUE_PURPLE, 128, (s==4)?96:0);
166+ grid[64 - 7 - 1] = CHSV(HUE_GREEN, 128, (s==1)?96:0);
167+ grid[64 - 15 - 1] = CHSV(HUE_GREEN, 128, (s==2)?96:0);
168+ grid[64 - 8 - 1] = CHSV(HUE_GREEN, 128, (s==3)?96:0);
169+ grid[64 - 0 - 1] = CHSV(HUE_GREEN, 128, (s==4)?96:0);
170
171 for (int i = 0; i < 12; i++) {
172 // Omit first and last position on a row
173@@ -301,16 +323,30 @@ void displayTime(unsigned long duration = 20 * SECOND) {
174 }
175 }
176
177+void setup() {
178+ pinMode(RESET_PIN, INPUT_PULLUP);
179+ pinMode(LED_BUILTIN, OUTPUT);
180+ Serial.begin(19200);
181+ FastLED.addLeds<WS2812, NEOPIXEL_PIN, GRB>(grid, GRIDLEN);
182+ // Maybe it's the plexiglass, but for my build, I need to dial back the red
183+ FastLED.setCorrection(0xd0ffff);
184+ network_setup(WFM_PASSWORD);
185+
186+ // Show our mac address, for debugging?
187+ displayMacAddress();
188+ sparkle();
189+}
190+
191 void loop() {
192 Picker p;
193 uint8_t getprob = 4;
194 bool conn = connected();
195 bool day = true;
196
197- updateTime();
198- if (timeStatus() == timeSet) {
199- int hh = hour();
200- day = ((hh >= DAY_BEGIN) && (hh < DAY_END));
201+ if (clock_is_set()) {
202+ struct tm info;
203+ getLocalTime(&info);
204+ day = ((info.tm_hour >= DAY_BEGIN) && (info.tm_hour < DAY_END));
205 }
206 FastLED.setBrightness(day?DAY_BRIGHTNESS:NIGHT_BRIGHTNESS);
207
208@@ -319,8 +355,9 @@ void loop() {
209 getprob = 16;
210 }
211
212- if (!day || p.Pick(4)) {
213- // At night, only ever show the clock
214+ if (!day && clock_is_set()) {
215+ displayTime();
216+ } else if (p.Pick(4) && clock_is_set()) {
217 displayTime(2 * MINUTE);
218 } else if (p.Pick(getprob)) {
219 netget();