Merge branch 'master' of https://github.com/nealey/wallart
This commit is contained in:
commit
584dc77319
45
README.md
45
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
|
||||
----------
|
||||
|
||||
|
|
42
network.cpp
42
network.cpp
|
@ -1,28 +1,60 @@
|
|||
#include <FastLED.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <WiFiManagerTz.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_sntp.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#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<const char *> 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());
|
||||
|
|
|
@ -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();
|
||||
|
|
17
timezones.h
17
timezones.h
|
@ -1,17 +0,0 @@
|
|||
#include <Timezone.h>
|
||||
|
||||
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);
|
127
wallart.ino
127
wallart.ino
|
@ -2,17 +2,14 @@
|
|||
#include <ArduinoHttpClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <NTPClient.h>
|
||||
#include <Time.h>
|
||||
#include <TimeLib.h>
|
||||
#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<WS2812, NEOPIXEL_PIN, GRB>(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<WS2812, NEOPIXEL_PIN, GRB>(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();
|
||||
|
|
Loading…
Reference in New Issue