wallart

8x8 pixel display firmware
git clone https://git.woozle.org/neale/wallart.git

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
M README.md
+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 
M network.cpp
+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());
M network.h
+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();
D timezones.h
+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);
M wallart.ino
+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();