diff --git a/README.md b/README.md index 9d67e20..511a420 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,41 @@ form different ideas about what it's displaying. That's cool. +Setup +----- + +When you first plug it in, +you will see a yellow pattern with blue or red bars around it. +The pattern is your mac address. +If the bars are red and a pixel is flashing, +that means you need to set up WiFi. + +You can also look at the back for a red LED. +If it's lit, you need to set up WiFi. + +Get your phone or computer to connect to an access point +called "WallArt". +The password is "artsy fartsy", unless you changed it in the source code. +Once connected, +you should get a browser window that lets you connect. +If not, try going to http://neverssl.com/. + +Please configure the clock before the WiFi. +This will set up your time zone, +so it doesn't blind you in the middle of the night. + +You can clear the wifi information with a reset. + + +Reset +------ + +Plug the device in, +and connect GND to pin A0 (right next to GND). +The red LED on the Feather board should come on immediately, +indicating it needs the network set up again. + + Network Server -------------- @@ -55,9 +90,6 @@ Clock At night, and sometimes during the day, it displays something like a clock. -You will need to tell it your time zone. -It doesn't do daylight saving time, sorry. -I suggest you set it to standard time and pretend it's in sync with the sun. * Each pixel in the top row is 1 hour (3600 seconds) * Each pixel in the middle row is 5 minutes (300 seconds) @@ -65,6 +97,13 @@ I suggest you set it to standard time and pretend it's in sync with the sun. * There are four pixels around the bottom that move every 5 seconds +Updating Firmware +----------------- + + 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 + + + Philosophy ---------- diff --git a/network.cpp b/network.cpp index e53a270..f72e252 100644 --- a/network.cpp +++ b/network.cpp @@ -1,28 +1,60 @@ #include #include +#include #include +#include #include #include #include "network.h" WiFiManager wfm; -void network_setup(char *password) { - String hostid = String(ESP.getEfuseMac(), HEX); - String hostname = "Wall Art " + hostid; +void network_reset() { + Serial.println("Resetting network"); + wfm.resetSettings(); +} + +bool time_was_accurate_once = false; +bool clock_is_set() { + return time_was_accurate_once; +} + +void on_time_available(struct timeval *t) { + struct tm timeInfo; + getLocalTime(&timeInfo, 1000); + Serial.println(&timeInfo, "%A, %B %d %Y %H:%M:%S %Z %z "); + time_was_accurate_once = true; +} + +void network_setup(char *password) { + String hostname = "WallArt"; + + WiFiManagerNS::NTP::onTimeAvailable(&on_time_available); + WiFiManagerNS::init(&wfm); + + std::vector menu = {"wifi", "info", "custom", "param", "sep", "update", "restart", "exit"}; + wfm.setMenu(menu); wfm.setConfigPortalBlocking(false); wfm.setHostname(hostname); wfm.autoConnect(hostname.c_str(), password); - - pinMode(LED_BUILTIN, OUTPUT); } bool connected() { return WiFi.status() == WL_CONNECTED; } +bool timeConfigured = false; + void pause(uint32_t dwMs) { + if (connected() && !timeConfigured) { + WiFiManagerNS::configTime(); + timeConfigured = true; + } + if (!digitalRead(RESET_PIN)) { + network_reset(); + } + for (uint32_t t = 0; t < dwMs; t += 10) { wfm.process(); digitalWrite(LED_BUILTIN, !connected()); diff --git a/network.h b/network.h index db935c7..4143a85 100644 --- a/network.h +++ b/network.h @@ -1,6 +1,11 @@ #pragma once +// Short this to ground to reset the network +#define RESET_PIN 26 + +void network_reset(); void network_setup(char *password); bool connected(); void pause(uint32_t dwMs); void netget(int count); +bool clock_is_set(); diff --git a/timezones.h b/timezones.h deleted file mode 100644 index e7a64b2..0000000 --- a/timezones.h +++ /dev/null @@ -1,17 +0,0 @@ -#include - -TimeChangeRule usEDT = {"EDT", Second, Sun, Mar, 2, -240}; -TimeChangeRule usEST = {"EST", First, Sun, Nov, 2, -300}; -Timezone TZ_US_Eastern(usEDT, usEST); - -TimeChangeRule usCDT = {"CDT", Second, Sun, Mar, 2, -300}; -TimeChangeRule usCST = {"CST", First, Sun, Nov, 2, -360}; -Timezone TZ_US_Central(usCDT, usCST); - -TimeChangeRule usMDT = {"MDT", Second, Sun, Mar, 2, -360}; -TimeChangeRule usMST = {"MST", First, Sun, Nov, 2, -420}; -Timezone TZ_US_Mountain(usMDT, usMST); - -TimeChangeRule usPDT = {"EDT", Second, Sun, Mar, 2, -420}; -TimeChangeRule usPST = {"EST", First, Sun, Nov, 2, -480}; -Timezone TZ_US_Pacific(usPDT, usPST); diff --git a/wallart.ino b/wallart.ino index ae5e658..448f627 100644 --- a/wallart.ino +++ b/wallart.ino @@ -2,17 +2,14 @@ #include #include #include -#include -#include +#include #include "durations.h" -#include "timezones.h" #include "picker.h" #include "network.h" #define NEOPIXEL_PIN 32 #define GRIDLEN 64 #define WFM_PASSWORD "artsy fartsy" -#define TIMEZONE TZ_US_Mountain /* * The hours when the day begins and ends. @@ -33,29 +30,11 @@ #define ART_PATH "/wallart/wallart.bin" #define HTTPS_TIMEOUT (2 * SECOND) +#define IMAGE_PULL_MIN_INTERVAL (5 * MINUTE) + CRGB grid[GRIDLEN]; -WiFiUDP ntpUDP; -NTPClient timeClient(ntpUDP); - -void setup() { - FastLED.addLeds(grid, GRIDLEN); - // Maybe it's the plexiglass but for my build I really need to dial back the red - FastLED.setCorrection(0xc0ffff); - network_setup(WFM_PASSWORD); -} - -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); int hue = random(256); @@ -184,6 +163,26 @@ void cm5(uint8_t width=0, int cycles=200) { } } +void displayMacAddress(int cycles=40) { + uint64_t addr = ESP.getEfuseMac(); + + for (; cycles > 0; cycles -= 1) { + bool conn = connected(); + + 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); + } + grid[0] = CRGB::Black; + if (!conn && (cycles % 2)) { + grid[1] = CRGB::Black; + } + FastLED.show(); + pause(250*MILLISECOND); + } +} + // Art from the network int NetArtFrames = 0; CRGB NetArt[8][GRIDLEN]; @@ -214,25 +213,45 @@ uint8_t netgetStatus(uint8_t hue) { void netget(int count=60) { uint8_t hue = netgetStatus(HUE_BLUE); + static unsigned long nextPull = 0; // when to pull next #if defined(ART_HOSTNAME) && defined(ART_PORT) && defined(ART_PATH) - if (connected()) { + if (millis() < nextPull) { + // Let's not bombard the server + hue = HUE_ORANGE; + } else if (connected()) { WiFiClientSecure scli; + nextPull = millis() + IMAGE_PULL_MIN_INTERVAL; + hue = netgetStatus(HUE_AQUA); scli.setInsecure(); HttpClient https(scli, ART_HOSTNAME, ART_PORT); do { - if (https.get(ART_PATH) != 0) break; + String path = String(ART_PATH) + "?mac=" + String(ESP.getEfuseMac(), HEX); + Serial.println(path); + if (https.get(path) != 0) break; hue = netgetStatus(HUE_GREEN); if (https.skipResponseHeaders() != HTTP_SUCCESS) break; hue = netgetStatus(HUE_YELLOW); - int artlen = https.read((uint8_t *)NetArt, sizeof(NetArt)); - hue = netgetStatus(HUE_ORANGE); - NetArtFrames = (artlen / 3) / GRIDLEN; + size_t readBytes = 0; + for (int i = 0; i < 12; i++) { + size_t artBytesLeft = sizeof(NetArt) - readBytes; + + if (https.endOfBodyReached() || (artBytesLeft == 0)) { + hue = netgetStatus(HUE_ORANGE); + NetArtFrames = (readBytes / 3) / GRIDLEN; + break; + } + int l = https.read((uint8_t *)NetArt + readBytes, artBytesLeft); + if (-1 == l) { + break; + } + readBytes += l; + } } while(false); https.stop(); } @@ -254,19 +273,22 @@ void spinner(int count=32) { } } -void displayTime(unsigned long duration = 20 * SECOND) { - if (timeStatus() != timeSet) return; +void displayTime(unsigned long duration = 20*SECOND) { + if (!clock_is_set()) return; unsigned long end = millis() + duration; + FastLED.clear(); while (millis() < end) { - updateTime(); - int hh = hour(); - int mmss = now() % 3600; + struct tm info; + getLocalTime(&info); + + int hh = info.tm_hour; + int mmss = (info.tm_min * 60) + info.tm_sec; uint8_t hue = HUE_YELLOW; // Top: Hours - if (isPM()) { + if (hh >= 12) { hue = HUE_ORANGE; hh -= 12; } @@ -279,10 +301,10 @@ void displayTime(unsigned long duration = 20 * SECOND) { // Outer: 5s uint8_t s = (mmss/5) % 5; - 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); + grid[64 - 7 - 1] = CHSV(HUE_GREEN, 128, (s==1)?96:0); + grid[64 - 15 - 1] = CHSV(HUE_GREEN, 128, (s==2)?96:0); + grid[64 - 8 - 1] = CHSV(HUE_GREEN, 128, (s==3)?96:0); + grid[64 - 0 - 1] = CHSV(HUE_GREEN, 128, (s==4)?96:0); for (int i = 0; i < 12; i++) { // Omit first and last position on a row @@ -301,16 +323,30 @@ void displayTime(unsigned long duration = 20 * SECOND) { } } +void setup() { + pinMode(RESET_PIN, INPUT_PULLUP); + pinMode(LED_BUILTIN, OUTPUT); + Serial.begin(19200); + FastLED.addLeds(grid, GRIDLEN); + // Maybe it's the plexiglass, but for my build, I need to dial back the red + FastLED.setCorrection(0xd0ffff); + network_setup(WFM_PASSWORD); + + // Show our mac address, for debugging? + displayMacAddress(); + sparkle(); +} + void loop() { Picker p; uint8_t getprob = 4; bool conn = connected(); bool day = true; - updateTime(); - if (timeStatus() == timeSet) { - int hh = hour(); - day = ((hh >= DAY_BEGIN) && (hh < DAY_END)); + if (clock_is_set()) { + struct tm info; + getLocalTime(&info); + day = ((info.tm_hour >= DAY_BEGIN) && (info.tm_hour < DAY_END)); } FastLED.setBrightness(day?DAY_BRIGHTNESS:NIGHT_BRIGHTNESS); @@ -319,8 +355,9 @@ void loop() { getprob = 16; } - if (!day || p.Pick(4)) { - // At night, only ever show the clock + if (!day && clock_is_set()) { + displayTime(); + } else if (p.Pick(4) && clock_is_set()) { displayTime(2 * MINUTE); } else if (p.Pick(getprob)) { netget();