WIFI_NTP_CLOCK.ino 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. #include <Lixie_II.h> // https://github.com/connornishijima/Lixie_II
  2. #include <ESP8266WiFi.h> // https://github.com/esp8266/Arduino
  3. #include <DNSServer.h> // |
  4. #include <ESP8266WebServer.h> // |
  5. #include <WiFiUdp.h> // |
  6. #include <FS.h> // <
  7. #include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
  8. #include <NTPClient.h> // https://github.com/arduino-libraries/NTPClient
  9. #include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson
  10. /*
  11. * Lixie II NTP Clock Example for ESP8266
  12. * by Connor Nishijima (November 2nd, 2019)
  13. *
  14. * This example relies on an ESP8266/ESP32 controller,
  15. * which is used to pull UTC time from an NTP server over WiFi.
  16. *
  17. * It requires two external buttons to fully function: (defined as HUE_BUTTON and HOUR_BUTTON below)
  18. * one for changing color/modes, and one for setting the UTC offset (timezone)
  19. *
  20. * Upon booting, it will failt to connect to WiFi as it doesn't yet know your
  21. * credentials. It will then host it's own WiFi access point named "LIXIE CONFIG"
  22. * that you can connect your phone to.
  23. *
  24. * Once connected, go to http://192.168.4.1 in your phone's browser, and you
  25. * will see a menu that will let you send the WiFi details down to the clock,
  26. * to be remembered from this point forward. (Even through power cycles!)
  27. *
  28. * A short tap of the HUE button will change color modes, and holding it down will
  29. * start to cycle the displays through the color wheel. Release the HUE button
  30. * when it gets to a color you like.
  31. *
  32. * A short tap of the HOUR button will increase the UTC offset by 1, (wrapping back
  33. * to -12 after +12) and a long tap toggles between 12 and 24-hour mode.
  34. *
  35. * Holding the HOUR button down during power up will let you access the WiFi
  36. * configuration page again.
  37. *
  38. * MAKE SURE SPIFFS IS ENABLED in Tools > Flash Size
  39. *
  40. * Enjoy!
  41. */
  42. // USER SETTINGS //////////////////////////////////////////////////////////////////
  43. #define DATA_PIN D7 // Lixie DIN connects to this pin
  44. #define NUM_DIGITS 4
  45. #define SIX_DIGIT_CLOCK false // 6 or 4-digit clock? (6 has seconds shown)
  46. #define HOUR_BUTTON D1 // These are pulled up internally, and should be
  47. #define HUE_BUTTON D3 // tied to GND through momentary switches
  48. ///////////////////////////////////////////////////////////////////////////////////
  49. Lixie_II lix(DATA_PIN, NUM_DIGITS);
  50. WiFiUDP ntp_UDP;
  51. NTPClient time_client(ntp_UDP, "pool.ntp.org", 3600, 60000);
  52. #define SECONDS_PER_HOUR 3600
  53. int16_t time_zone_shift = 0;
  54. uint8_t hour_12_mode = false;
  55. bool time_found = false;
  56. uint32_t settings_last_update = 0;
  57. bool settings_changed = false;
  58. const char* settings_file = "/settings.json";
  59. uint32_t t_now = 0;
  60. uint8_t last_seconds = 0;
  61. #define STABLE 0
  62. #define RISE 1
  63. #define FALL 2
  64. bool hour_button_state = HIGH;
  65. bool hue_button_state = HIGH;
  66. bool hour_button_state_last = HIGH;
  67. bool hue_button_state_last = HIGH;
  68. uint8_t hour_button_edge = STABLE;
  69. uint8_t hue_button_edge = STABLE;
  70. uint32_t hour_button_last_hit = 0;
  71. uint32_t hue_button_last_hit = 0;
  72. uint32_t hour_button_start = 0;
  73. uint16_t hour_button_wait = 1000;
  74. bool hour_mode_started = false;
  75. uint32_t hue_button_start = 0;
  76. uint8_t button_debounce_ms = 100;
  77. uint8_t base_hue = 0;
  78. uint16_t hue_countdown = 255;
  79. uint16_t hue_push_wait = 500;
  80. uint8_t current_mode = 0;
  81. #define NUM_MODES 7
  82. #define MODE_SOLID 0
  83. #define MODE_GRADIENT 1
  84. #define MODE_DUAL 2
  85. #define MODE_NIXIE 3
  86. #define MODE_INCANDESCENT 4
  87. #define MODE_VFD 5
  88. #define MODE_WHITE 6
  89. void setup() {
  90. Serial.begin(115200);
  91. init_fs();
  92. load_settings();
  93. init_displays();
  94. init_buttons();
  95. init_wifi();
  96. init_ntp();
  97. }
  98. void loop() {
  99. run_clock();
  100. yield();
  101. }
  102. void run_clock() {
  103. t_now = millis();
  104. time_client.update();
  105. if (time_client.getSeconds() != last_seconds) {
  106. show_time();
  107. }
  108. if (t_now % 20 == 0) { // 50 FPS
  109. color_for_mode();
  110. check_buttons();
  111. lix.run();
  112. }
  113. if (t_now - settings_last_update > 5000 && settings_changed == true) {
  114. settings_changed = false;
  115. save_settings();
  116. }
  117. }
  118. void save_settings() {
  119. StaticJsonBuffer<200> jsonBuffer;
  120. JsonObject& root = jsonBuffer.createObject();
  121. root["base_hue"] = base_hue;
  122. root["mode"] = current_mode;
  123. root["offset"] = time_zone_shift;
  124. root["hour_mode"] = hour_12_mode;
  125. String settings;
  126. root.printTo(settings);
  127. Serial.println(settings);
  128. File f = SPIFFS.open(settings_file, "w+");
  129. if (!f) {
  130. Serial.println("settings file open failed");
  131. }
  132. else {
  133. //Write data to file
  134. f.print(settings);
  135. f.close(); //Close file
  136. }
  137. }
  138. void load_settings() {
  139. String input = "";
  140. //Read File data
  141. File f = SPIFFS.open(settings_file, "r");
  142. if (!f) {
  143. Serial.println("settings file open failed");
  144. }
  145. else {
  146. for (uint16_t i = 0; i < f.size(); i++) {
  147. input += (char)f.read();
  148. }
  149. f.close(); //Close file
  150. }
  151. StaticJsonBuffer<200> jsonBuffer;
  152. JsonObject& root = jsonBuffer.parseObject(input);
  153. base_hue = root["base_hue"];
  154. current_mode = root["mode"];
  155. time_zone_shift = root["offset"];
  156. hour_12_mode = root["hour_mode"];
  157. }
  158. void show_time() {
  159. uint8_t hh = time_client.getHours();
  160. uint8_t mm = time_client.getMinutes();
  161. uint8_t ss = time_client.getSeconds();
  162. if (hour_12_mode == true) {
  163. if (hh > 12) {
  164. hh -= 12;
  165. }
  166. if (hh == 0) {
  167. hh = 12;
  168. }
  169. }
  170. uint32_t t_lixie = 1000000; // "1000000" is used to get zero-padding on hours digits
  171. // This turns a time of 22:34:57 into the integer (1)223457, whose leftmost numeral will not be shown
  172. t_lixie += (hh * 10000);
  173. t_lixie += (mm * 100);
  174. t_lixie += ss;
  175. if (!SIX_DIGIT_CLOCK) {
  176. t_lixie /= 100; // Eliminate second places
  177. }
  178. lix.write(t_lixie);
  179. if (!time_found) {
  180. time_found = true;
  181. lix.fade_in();
  182. }
  183. Serial.println(t_lixie);
  184. last_seconds = ss;
  185. }
  186. void color_for_mode() {
  187. uint8_t temp_hue = base_hue + hue_countdown;
  188. if (current_mode == MODE_SOLID) {
  189. lix.color_all(ON, CHSV(temp_hue, 255, 255));
  190. lix.color_all(OFF, CRGB(0, 0, 0));
  191. }
  192. else if (current_mode == MODE_GRADIENT) {
  193. lix.gradient_rgb(ON, CHSV(temp_hue, 255, 255), CHSV(temp_hue + 90, 255, 255));
  194. lix.color_all(OFF, CRGB(0, 0, 0));
  195. }
  196. else if (current_mode == MODE_DUAL) {
  197. lix.color_all_dual(ON, CHSV(temp_hue, 255, 255), CHSV(temp_hue + 90, 255, 255));
  198. lix.color_all(OFF, CRGB(0, 0, 0));
  199. }
  200. else if (current_mode == MODE_NIXIE) {
  201. lix.color_all(ON, CRGB(255, 70, 7));
  202. CRGB col_off = CRGB(0, 100, 255);
  203. const uint8_t nixie_aura_level = 8;
  204. col_off.r *= (nixie_aura_level / 255.0);
  205. col_off.g *= (nixie_aura_level / 255.0);
  206. col_off.b *= (nixie_aura_level / 255.0);
  207. lix.color_all(OFF, col_off);
  208. }
  209. else if (current_mode == MODE_INCANDESCENT) {
  210. lix.color_all(ON, CRGB(255, 100, 25));
  211. lix.color_all(OFF, CRGB(0, 0, 0));
  212. }
  213. else if (current_mode == MODE_VFD) {
  214. lix.color_all(ON, CRGB(100, 255, 100));
  215. lix.color_all(OFF, CRGB(0, 0, 0));
  216. }
  217. else if (current_mode == MODE_WHITE) {
  218. lix.color_all(ON, CRGB(255, 255, 255));
  219. }
  220. }
  221. void check_buttons() {
  222. hour_button_state = digitalRead(HOUR_BUTTON);
  223. hue_button_state = digitalRead(HUE_BUTTON);
  224. if (hour_button_state > hour_button_state_last) {
  225. hour_button_edge = RISE;
  226. }
  227. else if (hour_button_state < hour_button_state_last) {
  228. if (t_now - hour_button_last_hit >= button_debounce_ms) {
  229. hour_button_last_hit = t_now;
  230. hour_button_edge = FALL;
  231. }
  232. }
  233. else {
  234. hour_button_edge = STABLE;
  235. }
  236. if (hue_button_state > hue_button_state_last) {
  237. hue_button_edge = RISE;
  238. }
  239. else if (hue_button_state < hue_button_state_last) {
  240. if (t_now - hue_button_last_hit >= button_debounce_ms) {
  241. hue_button_last_hit = t_now;
  242. hue_button_edge = FALL;
  243. }
  244. }
  245. else {
  246. hue_button_edge = STABLE;
  247. }
  248. parse_buttons();
  249. hour_button_state_last = hour_button_state;
  250. hue_button_state_last = hue_button_state;
  251. }
  252. void parse_buttons() {
  253. if (hour_button_edge == FALL) { // PRESS STARTED
  254. hour_button_start = t_now;
  255. }
  256. else if (hour_button_edge == RISE) { // PRESS ENDED
  257. uint16_t hour_button_duration = t_now - hour_button_start;
  258. if (hour_button_duration < hour_button_wait) { // RELEASED QUICKLY
  259. Serial.println("UP");
  260. time_zone_shift += 1;
  261. if(time_zone_shift >= 12){
  262. time_zone_shift = -12;
  263. }
  264. time_client.setTimeOffset(time_zone_shift * SECONDS_PER_HOUR);
  265. show_time();
  266. update_settings();
  267. }
  268. else { // RELEASED AFTER LONG PRESS
  269. hour_mode_started = false;
  270. }
  271. }
  272. if (hue_button_edge == FALL) { // PRESS STARTED
  273. Serial.println("HUE");
  274. hue_button_start = t_now;
  275. }
  276. else if (hue_button_edge == RISE) { // PRESS ENDED
  277. uint16_t hue_button_duration = t_now - hue_button_start;
  278. if (hue_button_duration < hue_push_wait) { // RELEASED QUICKLY
  279. Serial.println("NEXT MODE");
  280. current_mode++;
  281. if (current_mode >= NUM_MODES) {
  282. current_mode = 0;
  283. }
  284. hue_countdown = 127;
  285. update_settings();
  286. }
  287. }
  288. if (hue_button_state == LOW) { // CURRENTLY PRESSING
  289. uint16_t hue_button_duration = t_now - hue_button_start;
  290. if (hue_button_duration >= hue_push_wait) {
  291. base_hue++;
  292. Serial.print("HUE: ");
  293. Serial.println(base_hue);
  294. update_settings();
  295. }
  296. }
  297. if (hour_button_state == LOW) { // CURRENTLY PRESSING
  298. uint16_t hour_button_duration = t_now - hour_button_start;
  299. if (hour_button_duration >= hour_button_wait && hour_mode_started == false) {
  300. hour_mode_started = true;
  301. Serial.println("CHANGE HOUR MODE");
  302. hour_12_mode = !hour_12_mode;
  303. update_settings();
  304. }
  305. }
  306. if (hue_countdown < 255) {
  307. hue_countdown += 6;
  308. if (hue_countdown > 255) {
  309. hue_countdown = 255;
  310. }
  311. }
  312. }
  313. void update_settings() {
  314. settings_changed = true;
  315. settings_last_update = t_now;
  316. }
  317. void init_wifi() {
  318. WiFiManager wifiManager;
  319. if (digitalRead(HOUR_BUTTON) == LOW) {
  320. wifiManager.resetSettings();
  321. lix.write(808080);
  322. lix.color_all(ON, CRGB(64,0,255));
  323. }
  324. wifiManager.autoConnect("LIXIE CONFIG");
  325. lix.sweep_color(CRGB(0, 255, 127), 20, 3, false);
  326. lix.clear(); // Removes "8"s before fading in the time
  327. color_for_mode();
  328. }
  329. void init_ntp() {
  330. time_client.begin();
  331. time_client.setTimeOffset(time_zone_shift * SECONDS_PER_HOUR);
  332. }
  333. void init_fs() {
  334. Serial.print("SPIFFS Initialize....");
  335. if (SPIFFS.begin()) {
  336. Serial.println("ok");
  337. }
  338. else {
  339. Serial.println("failed");
  340. }
  341. }
  342. void init_buttons() {
  343. pinMode(HOUR_BUTTON, INPUT_PULLUP);
  344. pinMode(HUE_BUTTON, INPUT_PULLUP);
  345. }
  346. void init_displays() {
  347. lix.begin();
  348. lix.max_power(5, 1000);
  349. lix.write(888888);
  350. }