|
|
@@ -11,51 +11,72 @@
|
|
|
#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson
|
|
|
|
|
|
/*
|
|
|
- * Lixie II NTP Clock Example for ESP8266
|
|
|
- * by Connor Nishijima (November 2nd, 2019)
|
|
|
- *
|
|
|
- * This example relies on an ESP8266/ESP32 controller,
|
|
|
- * which is used to pull UTC time from an NTP server over WiFi.
|
|
|
- *
|
|
|
- * It requires two external buttons to fully function: (defined as HUE_BUTTON and HOUR_BUTTON below)
|
|
|
- * one for changing color/modes, and one for setting the UTC offset (timezone)
|
|
|
- *
|
|
|
- * Upon booting, it will fail to connect to WiFi as it doesn't yet know your
|
|
|
- * credentials. It will then host it's own WiFi access point named "LIXIE CONFIG"
|
|
|
- * that you can connect your phone to.
|
|
|
- *
|
|
|
- * Once connected, go to http://192.168.4.1 in your phone's browser, and you
|
|
|
- * will see a menu that will let you send the WiFi details down to the clock,
|
|
|
- * to be remembered from this point forward. (Even through power cycles!)
|
|
|
- *
|
|
|
- * A short tap of the HUE button will change color modes, and holding it down will
|
|
|
- * start to cycle the displays through the color wheel. Release the HUE button
|
|
|
- * when it gets to a color you like.
|
|
|
- *
|
|
|
- * A short tap of the HOUR button will increase the UTC offset by 1, (wrapping back
|
|
|
- * to -12 after +12) and a long tap toggles between 12 and 24-hour mode.
|
|
|
- *
|
|
|
- * Holding the HOUR button down during power up will let you access the WiFi
|
|
|
- * configuration page again.
|
|
|
- *
|
|
|
- * MAKE SURE SPIFFS IS ENABLED in Tools > Flash Size
|
|
|
- *
|
|
|
- * Enjoy!
|
|
|
- */
|
|
|
+ Lixie II NTP Clock for ESP8266
|
|
|
+ by Connor Nishijima (November 2nd, 2019)
|
|
|
+
|
|
|
+ This example relies on an ESP8266/ESP32 controller,
|
|
|
+ which is used to pull UTC time from an NTP server over WiFi.
|
|
|
+
|
|
|
+ REQUIRED HARDWARE ---------------------------------------------------------------------
|
|
|
+
|
|
|
+ It requires two external buttons to fully function: (defined as HUE_BUTTON and HOUR_BUTTON below)
|
|
|
+ one for changing color/modes, and one for setting the UTC offset (timezone)
|
|
|
+
|
|
|
+ INSTRUCTIONS --------------------------------------------------------------------------
|
|
|
+
|
|
|
+ Upon booting, it will failt to connect to WiFi as it doesn't yet know your
|
|
|
+ credentials. It will then host it's own WiFi access point named "LIXIE CONFIG"
|
|
|
+ that you can connect your phone to.
|
|
|
+
|
|
|
+ Once connected, go to http://192.168.4.1/ in your phone's browser, and you
|
|
|
+ will see a menu that will let you send the WiFi details down to the clock,
|
|
|
+ to be remembered from this point forward. (Even through power cycles!)
|
|
|
+
|
|
|
+ A short tap of the HUE button will change color modes, and holding it down will
|
|
|
+ start to cycle the displays through the color wheel. Release the HUE button
|
|
|
+ when it gets to a color you like.
|
|
|
+
|
|
|
+ A short tap of the HOUR button will increase the UTC offset by 1, (wrapping back
|
|
|
+ to -12 after +12) and a long tap toggles between 12 and 24-hour mode.
|
|
|
+
|
|
|
+ Holding the HOUR button down during power up will erase WiFi credentials and let
|
|
|
+ you access the WiFi configuration page again.
|
|
|
+
|
|
|
+ OPTIONAL HARDWARE ---------------------------------------------------------------------
|
|
|
+
|
|
|
+ - A piezo buzzer or small speaker between the BUZZER pin and GND
|
|
|
+ - Two SPST / SPDT switches from both the COLOR_CYCLE and NIGHT_DIMMING pins, that
|
|
|
+ optionally tie those pins to GND
|
|
|
+
|
|
|
+ The buzzer is just for feedback when buttons are pressed, and the two switches
|
|
|
+ enable automated color cycling on modes that support it (full cycle every 5 minutes)
|
|
|
+ and nighttime dimming to 40% brightness from 9PM (21:00) to 6AM (06:00)
|
|
|
+
|
|
|
+ If you don't have a buzzer or switches on-hand, just ignore the buzzer and
|
|
|
+ short the COLOR_CYCLE / NIGHT_DIMMING pins to GND if you want to enable those features.
|
|
|
+
|
|
|
+ MAKE SURE SPIFFS IS ENABLED in Tools > Flash Size
|
|
|
+
|
|
|
+ Enjoy!
|
|
|
+*/
|
|
|
|
|
|
// USER SETTINGS //////////////////////////////////////////////////////////////////
|
|
|
-#define DATA_PIN D7 // Lixie DIN connects to this pin
|
|
|
+#define DATA_PIN D6 // Lixie DIN connects to this pin
|
|
|
#define NUM_DIGITS 4
|
|
|
|
|
|
#define SIX_DIGIT_CLOCK false // 6 or 4-digit clock? (6 has seconds shown)
|
|
|
|
|
|
-#define HOUR_BUTTON D1 // These are pulled up internally, and should be
|
|
|
-#define HUE_BUTTON D3 // tied to GND through momentary switches
|
|
|
+#define HOUR_BUTTON D7 // These are pulled up internally, and should be
|
|
|
+#define HUE_BUTTON D2 // tied to GND through momentary switches
|
|
|
+
|
|
|
+#define BUZZER D8 // OPTIONAL
|
|
|
+#define COLOR_CYCLE D1 // OPTIONAL
|
|
|
+#define NIGHT_DIMMING D5 // OPTIONAL
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
Lixie_II lix(DATA_PIN, NUM_DIGITS);
|
|
|
WiFiUDP ntp_UDP;
|
|
|
-NTPClient time_client(ntp_UDP, "pool.ntp.org", 3600, 900000); // Updates NTP sync every 15 minutes
|
|
|
+NTPClient time_client(ntp_UDP, "pool.ntp.org", 3600, 60000);
|
|
|
#define SECONDS_PER_HOUR 3600
|
|
|
bool time_found = false;
|
|
|
|
|
|
@@ -64,10 +85,11 @@ struct conf {
|
|
|
uint8_t hour_12_mode = false;
|
|
|
uint8_t base_hue = 0;
|
|
|
uint8_t current_mode = 0;
|
|
|
-
|
|
|
+
|
|
|
};
|
|
|
conf clock_config; // <- global configuration object
|
|
|
|
|
|
+float base_hue_f = 0;
|
|
|
uint32_t settings_last_update = 0;
|
|
|
bool settings_changed = false;
|
|
|
const char* settings_file = "/settings.json";
|
|
|
@@ -78,6 +100,14 @@ uint8_t last_seconds = 0;
|
|
|
#define STABLE 0
|
|
|
#define RISE 1
|
|
|
#define FALL 2
|
|
|
+
|
|
|
+uint8_t hh = 0;
|
|
|
+uint8_t mm = 0;
|
|
|
+uint8_t ss = 0;
|
|
|
+
|
|
|
+bool color_cycle_state = HIGH;
|
|
|
+bool night_dimming_state = HIGH;
|
|
|
+
|
|
|
bool hour_button_state = HIGH;
|
|
|
bool hue_button_state = HIGH;
|
|
|
bool hour_button_state_last = HIGH;
|
|
|
@@ -143,7 +173,7 @@ void run_clock() {
|
|
|
}
|
|
|
|
|
|
void save_settings() {
|
|
|
-// Delete existing file, otherwise the configuration is appended to the file
|
|
|
+ // Delete existing file, otherwise the configuration is appended to the file
|
|
|
SPIFFS.remove(settings_file);
|
|
|
|
|
|
// Open file for writing
|
|
|
@@ -152,7 +182,7 @@ void save_settings() {
|
|
|
Serial.println(F("Failed to create config file"));
|
|
|
return;
|
|
|
}
|
|
|
- else{
|
|
|
+ else {
|
|
|
Serial.println("Config file opened");
|
|
|
}
|
|
|
|
|
|
@@ -169,7 +199,7 @@ void save_settings() {
|
|
|
if (serializeJson(doc_out, file) == 0) {
|
|
|
Serial.println(F("Failed to write to config file"));
|
|
|
}
|
|
|
- else{
|
|
|
+ else {
|
|
|
Serial.println("Config Saved!");
|
|
|
}
|
|
|
|
|
|
@@ -186,16 +216,17 @@ void load_settings() {
|
|
|
|
|
|
// Deserialize the JSON document
|
|
|
DeserializationError error = deserializeJson(doc_in, file);
|
|
|
- if (error){
|
|
|
+ if (error) {
|
|
|
Serial.println(F("Failed to read file, using default configuration"));
|
|
|
}
|
|
|
- else{
|
|
|
+ else {
|
|
|
Serial.println("Config file opened");
|
|
|
Serial.println("Config Loaded!");
|
|
|
}
|
|
|
|
|
|
// Copy values from the JsonDocument to the Config
|
|
|
clock_config.base_hue = doc_in["base_hue"];
|
|
|
+ base_hue_f = doc_in["base_hue"];
|
|
|
clock_config.current_mode = doc_in["current_mode"];
|
|
|
clock_config.time_zone_shift = doc_in["time_zone_shift"];
|
|
|
clock_config.hour_12_mode = doc_in["hour_12_mode"];
|
|
|
@@ -205,10 +236,23 @@ void load_settings() {
|
|
|
}
|
|
|
|
|
|
void show_time() {
|
|
|
- uint8_t hh = time_client.getHours();
|
|
|
- uint8_t mm = time_client.getMinutes();
|
|
|
- uint8_t ss = time_client.getSeconds();
|
|
|
+ hh = time_client.getHours();
|
|
|
+ mm = time_client.getMinutes();
|
|
|
+ ss = time_client.getSeconds();
|
|
|
|
|
|
+ if (hh < 6 || hh >= 21) { // Dim overnight from 9PM to 6AM (21:00 to 06:00)
|
|
|
+ if (night_dimming_state == HIGH) { // But only if dimming is enabled
|
|
|
+ lix.brightness(0.4);
|
|
|
+ }
|
|
|
+ else { // If not enabled, full brightness
|
|
|
+ lix.brightness(1.0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else { // Or if not in the overnight time window, full brightness
|
|
|
+ lix.brightness(1.0);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 12 hour format conversion
|
|
|
if (clock_config.hour_12_mode == true) {
|
|
|
if (hh > 12) {
|
|
|
hh -= 12;
|
|
|
@@ -219,26 +263,34 @@ void show_time() {
|
|
|
}
|
|
|
|
|
|
uint32_t t_lixie = 1000000; // "1000000" is used to get zero-padding on hours digits
|
|
|
- // This turns a time of 22:34:57 into the integer (1)223457, whose leftmost numeral will not be shown
|
|
|
+ // This turns a time of 22:34:57 into the integer (1)223457, whose leftmost numeral (1) will not be shown
|
|
|
t_lixie += (hh * 10000);
|
|
|
t_lixie += (mm * 100);
|
|
|
t_lixie += ss;
|
|
|
|
|
|
if (!SIX_DIGIT_CLOCK) {
|
|
|
- t_lixie /= 100; // Eliminate second places
|
|
|
+ t_lixie /= 100; // Eliminate second places if using a 4 digit clock
|
|
|
}
|
|
|
|
|
|
- lix.write(t_lixie);
|
|
|
- if (!time_found) {
|
|
|
+ lix.write(t_lixie); // Update numerals
|
|
|
+
|
|
|
+ if (!time_found) { // Cues initial fade in
|
|
|
time_found = true;
|
|
|
lix.fade_in();
|
|
|
}
|
|
|
- Serial.println(t_lixie);
|
|
|
+
|
|
|
+ Serial.print("TIME: ");
|
|
|
+ Serial.println((hh * 10000)+(mm * 100)+ss);
|
|
|
|
|
|
last_seconds = ss;
|
|
|
}
|
|
|
|
|
|
void color_for_mode() {
|
|
|
+ if (color_cycle_state == HIGH) {
|
|
|
+ base_hue_f += 0.017; // Fully cycles the color wheel every 5 minutes
|
|
|
+ }
|
|
|
+
|
|
|
+ clock_config.base_hue = base_hue_f;
|
|
|
uint8_t temp_hue = clock_config.base_hue + hue_countdown;
|
|
|
|
|
|
if (clock_config.current_mode == MODE_SOLID) {
|
|
|
@@ -281,6 +333,9 @@ void check_buttons() {
|
|
|
hour_button_state = digitalRead(HOUR_BUTTON);
|
|
|
hue_button_state = digitalRead(HUE_BUTTON);
|
|
|
|
|
|
+ color_cycle_state = !digitalRead(COLOR_CYCLE);
|
|
|
+ night_dimming_state = !digitalRead(NIGHT_DIMMING);
|
|
|
+
|
|
|
if (hour_button_state > hour_button_state_last) {
|
|
|
hour_button_edge = RISE;
|
|
|
}
|
|
|
@@ -323,11 +378,18 @@ void parse_buttons() {
|
|
|
Serial.println("UP");
|
|
|
clock_config.time_zone_shift += 1;
|
|
|
|
|
|
- if(clock_config.time_zone_shift >= 12){
|
|
|
+ if (clock_config.time_zone_shift >= 12) {
|
|
|
clock_config.time_zone_shift = -12;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
time_client.setTimeOffset(clock_config.time_zone_shift * SECONDS_PER_HOUR);
|
|
|
+ hh = time_client.getHours();
|
|
|
+ if (hh == 0) {
|
|
|
+ beep(1000, 100);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ beep(2000, 100);
|
|
|
+ }
|
|
|
show_time();
|
|
|
update_settings();
|
|
|
}
|
|
|
@@ -347,6 +409,10 @@ void parse_buttons() {
|
|
|
clock_config.current_mode++;
|
|
|
if (clock_config.current_mode >= NUM_MODES) {
|
|
|
clock_config.current_mode = 0;
|
|
|
+ beep(1000, 100);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ beep(2000, 100);
|
|
|
}
|
|
|
hue_countdown = 127;
|
|
|
update_settings();
|
|
|
@@ -356,9 +422,9 @@ void parse_buttons() {
|
|
|
if (hue_button_state == LOW) { // CURRENTLY PRESSING
|
|
|
uint16_t hue_button_duration = t_now - hue_button_start;
|
|
|
if (hue_button_duration >= hue_push_wait) {
|
|
|
- clock_config.base_hue++;
|
|
|
+ base_hue_f++;
|
|
|
Serial.print("HUE: ");
|
|
|
- Serial.println(clock_config.base_hue);
|
|
|
+ Serial.println(base_hue_f);
|
|
|
update_settings();
|
|
|
}
|
|
|
}
|
|
|
@@ -368,6 +434,7 @@ void parse_buttons() {
|
|
|
if (hour_button_duration >= hour_button_wait && hour_mode_started == false) {
|
|
|
hour_mode_started = true;
|
|
|
Serial.println("CHANGE HOUR MODE");
|
|
|
+ beep(4000, 250);
|
|
|
clock_config.hour_12_mode = !clock_config.hour_12_mode;
|
|
|
update_settings();
|
|
|
}
|
|
|
@@ -386,15 +453,39 @@ void update_settings() {
|
|
|
settings_last_update = t_now;
|
|
|
}
|
|
|
|
|
|
+void enter_config_mode() {
|
|
|
+ lix.write(808080);
|
|
|
+ lix.color_all(ON, CRGB(64, 0, 255));
|
|
|
+ beep_dual(2000, 1000, 500);
|
|
|
+}
|
|
|
+
|
|
|
+void config_mode_callback (WiFiManager *myWiFiManager) {
|
|
|
+ enter_config_mode();
|
|
|
+ Serial.println("Entered config mode");
|
|
|
+ Serial.println(WiFi.softAPIP());
|
|
|
+ //if you used auto generated SSID, print it
|
|
|
+ Serial.println(myWiFiManager->getConfigPortalSSID());
|
|
|
+}
|
|
|
+
|
|
|
void init_wifi() {
|
|
|
WiFiManager wifiManager;
|
|
|
+ wifiManager.setAPCallback(config_mode_callback);
|
|
|
+ wifiManager.setTimeout(300); // Five minutes
|
|
|
+
|
|
|
if (digitalRead(HOUR_BUTTON) == LOW) {
|
|
|
wifiManager.resetSettings();
|
|
|
- lix.write(808080);
|
|
|
- lix.color_all(ON, CRGB(64,0,255));
|
|
|
+ enter_config_mode();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ beep(2000, 100);
|
|
|
+ }
|
|
|
+
|
|
|
+ while (!wifiManager.autoConnect("LIXIE CONFIG")) {
|
|
|
+ lix.sweep_color(CRGB(0, 255, 127), 20, 3, false);
|
|
|
+ lix.clear(); // Removes "8"s before fading in the time
|
|
|
}
|
|
|
|
|
|
- wifiManager.autoConnect("LIXIE CONFIG");
|
|
|
+ beep_dual(1000, 2000, 100);
|
|
|
lix.sweep_color(CRGB(0, 255, 127), 20, 3, false);
|
|
|
lix.clear(); // Removes "8"s before fading in the time
|
|
|
color_for_mode();
|
|
|
@@ -416,8 +507,13 @@ void init_fs() {
|
|
|
}
|
|
|
|
|
|
void init_buttons() {
|
|
|
- pinMode(HOUR_BUTTON, INPUT_PULLUP);
|
|
|
+ pinMode(HOUR_BUTTON, INPUT_PULLUP);
|
|
|
pinMode(HUE_BUTTON, INPUT_PULLUP);
|
|
|
+
|
|
|
+ pinMode(BUZZER, OUTPUT);
|
|
|
+
|
|
|
+ pinMode(COLOR_CYCLE, INPUT_PULLUP);
|
|
|
+ pinMode(NIGHT_DIMMING, INPUT_PULLUP);
|
|
|
}
|
|
|
|
|
|
void init_displays() {
|
|
|
@@ -425,3 +521,33 @@ void init_displays() {
|
|
|
lix.max_power(5, 1000);
|
|
|
lix.write(888888);
|
|
|
}
|
|
|
+
|
|
|
+void beep(uint16_t freq, uint16_t len) {
|
|
|
+ uint32_t period = (F_CPU / freq) / 2;
|
|
|
+ uint32_t cycle_ms = F_CPU / 1000;
|
|
|
+ uint32_t t_start = ESP.getCycleCount();
|
|
|
+ uint32_t last_flip = t_start;
|
|
|
+ uint32_t t_end = t_start + (len * cycle_ms);
|
|
|
+ uint32_t t_now = t_start;
|
|
|
+ bool state = LOW;
|
|
|
+ while (t_now < t_end) {
|
|
|
+ t_now = ESP.getCycleCount();
|
|
|
+ if (t_now - last_flip >= period) {
|
|
|
+ last_flip += period;
|
|
|
+ state = !state;
|
|
|
+
|
|
|
+ if (state) {
|
|
|
+ GPOS = (1 << BUZZER);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ GPOC = (1 << BUZZER);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ digitalWrite(BUZZER, LOW);
|
|
|
+}
|
|
|
+
|
|
|
+void beep_dual(uint16_t del1, uint16_t del2, uint16_t len) {
|
|
|
+ beep(del1, len);
|
|
|
+ beep(del2, len);
|
|
|
+}
|