/** * Wordclock 2.0 - Wordclock with ESP8266 and NTP time update * * created by techniccontroller 04.12.2021 * * components: * - ESP8266 * - Neopixelstrip * * Board settings: * - Board: NodeMCU 1.0 (ESP-12E Module) * - Flash Size: 4MB (FS:2MB OTA:~1019KB) * - Upload Speed: 115200 * * * with code parts from: * - Adafruit NeoPixel strandtest.ino, https://github.com/adafruit/Adafruit_NeoPixel/blob/master/examples/strandtest/strandtest.ino * - Esp8266 und Esp32 webserver https://fipsok.de/ * - https://github.com/pmerlin/PMR-LED-Table/blob/master/tetrisGame.ino * - https://randomnerdtutorials.com/wifimanager-with-esp8266-autoconnect-custom-parameter-and-manage-your-ssid-and-password/ * */ #include #include "wordclock_esp8266.h" #include // https://github.com/adafruit/Adafruit-GFX-Library #include // https://github.com/adafruit/Adafruit_NeoMatrix #include // NeoPixel library used to run the NeoPixel LEDs: https://github.com/adafruit/Adafruit_NeoPixel #include //from ESP8266 Arduino Core (automatically installed when ESP8266 was installed via Boardmanager) #include #include #include //https://github.com/tzapu/WiFiManager WiFi Configuration Magic #include // own libraries #include "animation_functions.h" #include "pong.h" #include "snake.h" #include "tetris.h" #include "led_matrix.h" #include "littlefs_wrapper.h" #include "base64_wrapper.h" // copied from https://github.com/Xander-Electronics/Base64 #include "ntp_client_plus.h" #include "ota_functions.h" #include "udp_logger.h" #include "wordclock_constants.h" #include "render_functions.h" // DEBUG uint32_t dbg_counter = 0; // TODO RM const String state_names[] = {"Clock", "DiClock", "Spiral", "Tetris", "Snake", "PingPong", "Hearts"}; // PERIODS for each state (different for stateAutoChange or Manual mode) const uint16_t PERIODS[2][NUM_STATES] = {{PERIOD_TIME_VISU_UPDATE, // stateAutoChange = 0 PERIOD_TIME_VISU_UPDATE, PERIOD_ANIMATION, PERIOD_TETRIS, PERIOD_SNAKE, PERIOD_PONG, PERIOD_ANIMATION}, {PERIOD_TIME_VISU_UPDATE, // stateAutoChange = 1 PERIOD_TIME_VISU_UPDATE, PERIOD_ANIMATION, PERIOD_ANIMATION, PERIOD_ANIMATION, PERIOD_PONG, PERIOD_ANIMATION}}; // ---------------------------------------------------------------------------------- // GLOBAL VARIABLES // ---------------------------------------------------------------------------------- // Webserver ESP8266WebServer webserver(HTTP_PORT); // When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals. // Note that for older NeoPixel strips you might need to change the third parameter--see the strandtest // example for more information on possible values. Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(MATRIX_WIDTH, MATRIX_HEIGHT + 1, NEOPIXEL_PIN, NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_ROWS + NEO_MATRIX_ZIGZAG, NEO_GRB + NEO_KHZ800); // Globals uint8_t brightness = 40; // current brightness of LEDs LEDMatrix led_matrix = LEDMatrix(&matrix, brightness, &logger); UDPLogger logger; // Statics static bool spiral_direction = false; static unsigned long last_led_direct = 0; // time of last direct LED command (=> fall back to normal mode after timeout) static WiFiUDP wifi_udp; static NTPClientPlus ntp_client = NTPClientPlus(wifi_udp, NTP_SERVER_URL, 1, true); static Pong pong = Pong(&led_matrix, &logger); static Snake snake = Snake(&led_matrix, &logger); static Tetris tetris = Tetris(&led_matrix, &logger); static bool night_mode = false; // stores state of nightmode static bool state_auto_change = false; // stores state of automatic state change static float filter_factor = DEFAULT_SMOOTHING_FACTOR; // stores smoothing factor for led transition static uint32_t main_color_clock = colors_24bit[2]; // color of the clock and digital clock static uint32_t main_color_snake = colors_24bit[1]; // color of the random snake animation static uint8_t current_state = (uint8_t)ST_CLOCK; // stores current state static int watchdog_counter = 30; // Watchdog counter to trigger restart if NTP update was not possible 30 times in a row (5min) // Nightmode time settings NightModeTimes_st night_mode_times = { NIGHTMODE_START_HR, NIGHTMODE_START_MIN, NIGHTMODE_END_HR, NIGHTMODE_END_MIN}; // ---------------------------------------------------------------------------------- // SETUP // ---------------------------------------------------------------------------------- void setup() { // put your setup code here, to run once: Serial.begin(115200); delay(100); Serial.println(); Serial.printf("\nSketchname: %s\nBuild: %s\n", (__FILE__), (__TIMESTAMP__)); Serial.println(); // Reset info rst_info *resetInfo = ESP.getResetInfoPtr(); Serial.printf("Reset reason: %u\n", resetInfo->reason); Serial.printf("Reset cause: %u\n", resetInfo->exccause); Serial.printf("Reset address: %u\n", resetInfo->excvaddr); Serial.println(); // Init EEPROM EEPROM.begin(EEPROM_SIZE); // Load color for clock from EEPROM load_main_color(); // configure button pin as input pinMode(BUTTON_PIN, INPUT_PULLUP); // setup Matrix LED functions led_matrix.setup_matrix(); led_matrix.set_current_limit(CURRENT_LIMIT_LED); // Turn on minutes leds (blue) led_matrix.set_min_indicator(15, colors_24bit[6]); led_matrix.draw_on_matrix_instant(); /** Use WiFiMaanger for handling initial Wifi setup **/ // Local intialization. Once its business is done, there is no need to keep it around WiFiManager wifiManager; /* fetches ssid and pass from eeprom and tries to connect. if it does not connect it starts an access point with * the specified name and goes into a blocking loop awaiting configuration. */ wifiManager.autoConnect(AP_SSID); // if you get here you have connected to the WiFi Serial.printf("Connected, IP address: "); Serial.println(WiFi.localIP()); // Turn off minutes leds led_matrix.set_min_indicator(15, 0); led_matrix.draw_on_matrix_instant(); // init ESP8266 File manager (LittleFS) setup_filesystem(); // setup OTA setupOTA(HOSTNAME); webserver.on("/cmd", handle_command); // process commands webserver.on("/data", handle_data_request); // process data requests webserver.on("/leddirect", HTTP_POST, handle_led_direct); // Call the 'handle_led_direct' function when a POST request is made to URI "/leddirect" webserver.begin(); // create UDP Logger to send logging messages via UDP multicast logger = UDPLogger(WiFi.localIP(), LOGGER_MULTICAST_IP, LOGGER_MULTICAST_PORT, "Wordclock 2.0"); logger.log_string("Start program\n"); delay(10); logger.log_string("Sketchname: " + String(__FILE__)); delay(10); logger.log_string("Build: " + String(__TIMESTAMP__)); delay(10); logger.log_string("IP: " + WiFi.localIP().toString()); delay(10); logger.log_string("Reset Reason: " + ESP.getResetReason()); if (resetInfo->reason != REASON_SOFT_RESTART) { // test quickly each LED for (int16_t row = 0; row < MATRIX_HEIGHT; row++) { for (int16_t col = 0; col < MATRIX_WIDTH; col++) { matrix.fillScreen(0); matrix.drawPixel(col, row, LEDMatrix::color_24_to_16bit(colors_24bit[2])); matrix.show(); delay(10); } } // clear Matrix matrix.fillScreen(0); matrix.show(); delay(200); // display IP uint8_t address = WiFi.localIP()[3]; led_matrix.print_char(1, 0, 'I', main_color_clock); led_matrix.print_char(5, 0, 'P', main_color_clock); led_matrix.print_number(0, 6, (address / 100), main_color_clock); led_matrix.print_number(4, 6, (address / 10) % 10, main_color_clock); led_matrix.print_number(8, 6, address % 10, main_color_clock); led_matrix.draw_on_matrix_instant(); delay(2000); // clear matrix led_matrix.flush(); led_matrix.draw_on_matrix_instant(); } // setup NTP ntp_client.setupNTPClient(); logger.log_string("NTP running"); logger.log_string("Time: " + ntp_client.getFormattedTime()); logger.log_string("TimeOffset (seconds): " + String(ntp_client.getTimeOffset())); // show the current time for short time in words int hours = ntp_client.getHours24(); int minutes = ntp_client.getMinutes(); String timeMessage = time_to_string(hours, minutes); show_string_on_clock(timeMessage, main_color_clock); draw_minute_indicator(minutes, main_color_clock); led_matrix.draw_on_matrix_smooth(filter_factor); // init all animation modes // init snake random_snake(true, 8, colors_24bit[1], -1); // init spiral draw_spiral(true, spiral_direction, MATRIX_WIDTH - 6); // init random tetris random_tetris(true); // Read nightmode setting from EEPROM night_mode_times.nightmode_start_hour = EEPROM_read_address(ADR_NM_START_H); night_mode_times.nightmode_start_min = EEPROM_read_address(ADR_NM_START_M); night_mode_times.nightmode_end_hour = EEPROM_read_address(ADR_NM_END_H); night_mode_times.nightmode_end_min = EEPROM_read_address(ADR_NM_END_M); if (night_mode_times.nightmode_start_hour < 0 || night_mode_times.nightmode_start_hour > 23) { night_mode_times.nightmode_start_hour = NIGHTMODE_START_HR; } if (night_mode_times.nightmode_start_min < 0 || night_mode_times.nightmode_start_min > 59) { night_mode_times.nightmode_start_min = NIGHTMODE_START_MIN; } if (night_mode_times.nightmode_end_hour < 0 || night_mode_times.nightmode_end_hour > 23) { night_mode_times.nightmode_end_hour = NIGHTMODE_END_HR; } if (night_mode_times.nightmode_end_min < 0 || night_mode_times.nightmode_end_min > 59) { night_mode_times.nightmode_end_min = NIGHTMODE_END_MIN; } logger.log_string("Nightmode starts at: " + String(night_mode_times.nightmode_start_hour) + ":" + String(night_mode_times.nightmode_start_min)); logger.log_string("Nightmode ends at: " + String(night_mode_times.nightmode_end_hour) + ":" + String(night_mode_times.nightmode_end_min)); // Read brightness setting from EEPROM, lower limit is 10 so that the LEDs are not completely off brightness = EEPROM_read_address(ADR_BRIGHTNESS); if (brightness < 10) { brightness = 10; } logger.log_string("Brightness: " + String(brightness)); led_matrix.set_brightness(brightness); } // ---------------------------------------------------------------------------------- // LOOP // ---------------------------------------------------------------------------------- void loop() { unsigned long current_time_ms = CURRENT_TIME_MS; // Timestamp variables static unsigned long last_animation_step = 0; // time of last animation step static unsigned long last_matrix_update = 0; // time of last Matrix update static unsigned long last_heartbeat = 0; // time of last heartbeat sending static unsigned long last_nightmode_check = 0; // time of last nightmode check static unsigned long last_ntp_update = 0; // time of last NTP update static unsigned long last_state_change = 0; // time of last state change handleOTA(); // handle OTA yield(); webserver.handleClient(); // handle webserver yield(); // send regularly heartbeat messages via UDP multicast if ((current_time_ms - last_heartbeat) > PERIOD_HEARTBEAT) { Serial.printf("a"); Serial.println(); send_heartbeat(); // send heartbeat update last_heartbeat = CURRENT_TIME_MS; yield(); Serial.printf("A"); } if (!night_mode && ((current_time_ms - last_animation_step) > PERIODS[state_auto_change][current_state]) && ((current_time_ms - last_led_direct) > TIMEOUT_LEDDIRECT)) { Serial.printf("b"); handle_current_state(); // handle current state last_animation_step = CURRENT_TIME_MS; yield(); Serial.printf("B"); } if ((current_time_ms - last_matrix_update) > PERIOD_MATRIX_UPDATE) { Serial.printf("c"); update_matrix(); // update matrix last_matrix_update = CURRENT_TIME_MS; yield(); Serial.printf("C"); } handle_button(); // handle button press if (!night_mode && state_auto_change && (current_time_ms - last_state_change > PERIOD_STATE_CHANGE)) { Serial.printf("d"); update_state_machine(); // handle state changes last_state_change = CURRENT_TIME_MS; yield(); Serial.printf("D"); } if ((current_time_ms - last_ntp_update) > PERIOD_NTP_UPDATE) { Serial.printf("e"); ntp_time_update(&last_ntp_update); // ntp time update yield(); Serial.printf("E"); } if ((current_time_ms - last_nightmode_check) > PERIOD_NIGHTMODE_CHECK) { Serial.printf("f"); check_night_mode(); // check night mode last_nightmode_check = CURRENT_TIME_MS; Serial.printf("F"); } } // ---------------------------------------------------------------------------------- // OTHER FUNCTIONS // ---------------------------------------------------------------------------------- void update_state_machine() { // increment state variable and trigger state change state_change((current_state + 1) % (uint8_t)NUM_STATES); } void handle_current_state() { switch (current_state) { case ST_CLOCK: // state clock { int hours = ntp_client.getHours24(); int minutes = ntp_client.getMinutes(); (void)show_string_on_clock(time_to_string((uint8_t)hours, (uint8_t)minutes), main_color_clock); draw_minute_indicator((uint8_t)minutes, main_color_clock); break; } case ST_DICLOCK: // state diclock { int hours = ntp_client.getHours24(); int minutes = ntp_client.getMinutes(); show_digital_clock((uint8_t)hours, (uint8_t)minutes, main_color_clock); break; } case ST_SPIRAL: // state spiral { int res = draw_spiral(false, spiral_direction, MATRIX_WIDTH - 6); if (res && spiral_direction == 0) { // change spiral direction to closing (draw empty leds) spiral_direction = 1; // init spiral with new spiral direction draw_spiral(true, spiral_direction, MATRIX_WIDTH - 6); } else if (res && spiral_direction == 1) { // reset spiral direction to normal drawing leds spiral_direction = 0; // init spiral with new spiral direction draw_spiral(true, spiral_direction, MATRIX_WIDTH - 6); } break; } case ST_TETRIS: // state tetris { if (state_auto_change) { random_tetris(false); } else { tetris.loopCycle(); } break; } case ST_SNAKE: // state snake { if (state_auto_change) { led_matrix.flush(); if (random_snake(false, 8, main_color_snake, -1)) { // init snake for next run random_snake(true, 8, main_color_snake, -1); } } else { snake.loopCycle(); } break; } case ST_PINGPONG: // state ping pong { pong.loopCycle(); break; } case ST_HEARTS: { draw_heart_animation(); break; } default: { break; } } } /** * @brief Update matrix colors, should be called in loop() * * @param None */ void update_matrix() { // periodically write colors to matrix led_matrix.draw_on_matrix_smooth(filter_factor); } /** * @brief Send heartbeat, should be called in loop() * * @param None */ void send_heartbeat() { logger.log_string("Heartbeat, state: " + state_names[current_state] + ", FreeHeap: " + ESP.getFreeHeap() + ", HeapFrag: " + ESP.getHeapFragmentation() + ", MaxFreeBlock: " + ESP.getMaxFreeBlockSize() + "\nCounter: " + dbg_counter + " , Hours: " + (float)(dbg_counter) / 3600.0 + "\n"); // TODO CHANGE // Check wifi status if (WiFi.status() != WL_CONNECTED) { Serial.println("WiFi connection lost!"); led_matrix.grid_add_pixel(0, 5, colors_24bit[1]); led_matrix.draw_on_matrix_instant(); } dbg_counter++; // TODO RM } /** * @brief Night mode check, should be called in loop() * * @param None */ void check_night_mode() { // check if nightmode need to be activated int hours = ntp_client.getHours24(); int minutes = ntp_client.getMinutes(); if (hours == night_mode_times.nightmode_start_hour && minutes == night_mode_times.nightmode_start_min) { set_night_mode(true); } else if (hours == night_mode_times.nightmode_end_hour && minutes == night_mode_times.nightmode_end_min) { set_night_mode(false); } } /** * @brief NTP time update, should be called in loop() * * @param None */ void ntp_time_update(unsigned long *last_ntp_update) { // NTP time update int ntp_retval = ntp_client.updateNTP(); switch (ntp_retval) { case NTP_UPDATE_SUCCESS: { ntp_client.calcDate(); logger.log_string("NTP-Update successful"); logger.log_string("Time: " + ntp_client.getFormattedTime()); logger.log_string("Date: " + ntp_client.getFormattedDate()); logger.log_string("TimeOffset (seconds): " + String(ntp_client.getTimeOffset())); logger.log_string("Summertime: " + String(ntp_client.updateSWChange())); *last_ntp_update = CURRENT_TIME_MS; watchdog_counter = 30; break; } case NTP_UPDATE_TIMEOUT: { logger.log_string("NTP-Update not successful. Reason: Timeout"); *last_ntp_update += 10000; watchdog_counter--; break; } case NTP_UPDATE_DIFFTOOHIGH: { logger.log_string("NTP-Update not successful. Reason: Too large time difference"); logger.log_string("Time: " + ntp_client.getFormattedTime()); logger.log_string("Date: " + ntp_client.getFormattedDate()); logger.log_string("TimeOffset (seconds): " + String(ntp_client.getTimeOffset())); logger.log_string("Summertime: " + String(ntp_client.updateSWChange())); *last_ntp_update += 10000; watchdog_counter--; break; } case NTP_UPDATE_TIME_INVALID: default: { logger.log_string("NTP-Update not successful. Reason: NTP time not valid (<1970)"); *last_ntp_update += 10000; watchdog_counter--; break; } } logger.log_string("Watchdog Counter: " + String(watchdog_counter)); if (watchdog_counter <= 0) { logger.log_string("Trigger restart due to watchdog..."); delay(100); ESP.restart(); } } /** * @brief call entry action of given state * * @param state */ void on_state_entry(uint8_t state) { filter_factor = 0.5; switch (state) { case ST_SPIRAL: { // Init spiral with normal drawing mode spiral_direction = 0; draw_spiral(true, spiral_direction, MATRIX_WIDTH - 6); break; } case ST_TETRIS: { filter_factor = 1.0; // no smoothing if (state_auto_change) { random_tetris(true); } else { tetris.ctrlStart(); } break; } case ST_SNAKE: { if (state_auto_change) { random_snake(true, 8, colors_24bit[1], -1); } else { filter_factor = 1.0; // no smoothing snake.initGame(); } break; } case ST_PINGPONG: { if (state_auto_change) { pong.initGame(2); } else { filter_factor = 1.0; // no smoothing pong.initGame(1); } break; } default: { break; } } } /** * @brief execute a state change to given newState * * @param newState the new state to be changed to */ void state_change(uint8_t newState) { if (night_mode) { set_night_mode(false); // deactivate Nightmode } led_matrix.flush(); // first clear matrix current_state = newState; // set new state on_state_entry(current_state); logger.log_string("State change to: " + state_names[current_state]); delay(5); logger.log_string("FreeMemory=" + String(ESP.getFreeHeap())); } /** * @brief Handler for POST requests to /leddirect. * * Allows the control of all LEDs from external source. * It will overwrite the normal program for 5 seconds. * A 11x11 picture can be sent as base64 encoded string to be displayed on matrix. * */ void handle_led_direct() { if (webserver.method() != HTTP_POST) { webserver.send(405, "text/plain", "Method Not Allowed"); } else { String message = "POST data was:\n"; if (webserver.args() == 1) { String data = String(webserver.arg(0)); unsigned int dataLength = data.length(); // base64 decoding char base64data[dataLength]; data.toCharArray(base64data, dataLength); int base64dataLen = (int)dataLength; int decodedLength = Base64.decodedLength(base64data, base64dataLen); char byteArray[decodedLength]; Base64.decode(byteArray, base64data, base64dataLen); for (unsigned int i = 0; i < dataLength; i += 4) { uint8_t red = byteArray[i]; // red uint8_t green = byteArray[i + 1]; // green uint8_t blue = byteArray[i + 2]; // blue led_matrix.grid_add_pixel((i / 4) % MATRIX_WIDTH, (i / 4) / MATRIX_HEIGHT, LEDMatrix::color_24bit(red, green, blue)); } led_matrix.draw_on_matrix_instant(); last_led_direct = CURRENT_TIME_MS; } webserver.send(200, "text/plain", message); } } /** * @brief Check button commands * */ void handle_button() { bool button_pressed = !digitalRead(BUTTON_PIN); static bool last_button_state = false; static unsigned long button_press_start = 0; // time of push button press start // check rising edge if (button_pressed == true && last_button_state == false) { // button press start logger.log_string("Button press started"); button_press_start = CURRENT_TIME_MS; } // check falling edge if (button_pressed == false && last_button_state == true) { // button press ended if ((CURRENT_TIME_MS - button_press_start) > LONG_PRESS_MS) { // longpress -> nightmode logger.log_string("Button press ended - long press"); set_night_mode(true); } else if ((CURRENT_TIME_MS - button_press_start) > SHORT_PRESS_MS) { // shortpress -> state change logger.log_string("Button press ended - short press"); if (night_mode) { set_night_mode(false); } else { state_change((current_state + 1) % (uint8_t)NUM_STATES); } } } last_button_state = button_pressed; } /** * @brief Set main color * */ void set_main_color(uint8_t red, uint8_t green, uint8_t blue) { main_color_clock = LEDMatrix::color_24bit(red, green, blue); EEPROM.put(ADR_MC_RED, red); EEPROM.put(ADR_MC_GREEN, green); EEPROM.put(ADR_MC_BLUE, blue); EEPROM.commit(); } /** * @brief Load main_color from EEPROM * */ void load_main_color() { uint8_t red = EEPROM.read(ADR_MC_RED); uint8_t green = EEPROM.read(ADR_MC_GREEN); uint8_t blue = EEPROM.read(ADR_MC_BLUE); if ((int(red) + int(green) + int(blue)) < 50) { main_color_clock = colors_24bit[2]; } else { main_color_clock = LEDMatrix::color_24bit(red, green, blue); } } /** * @brief Handler for handling commands sent to "/cmd" url * */ void handle_command() { // receive command and handle accordingly for (uint8_t i = 0; i < webserver.args(); i++) { Serial.print(webserver.argName(i)); Serial.print(F(": ")); Serial.println(webserver.arg(i)); } if (webserver.argName(0).equals("led")) // the parameter which was sent to this server is led color { String color_str = webserver.arg(0) + "-"; String red_str = split(color_str, '-', 0); String green_str = split(color_str, '-', 1); String blue_str = split(color_str, '-', 2); logger.log_string(color_str); logger.log_string("r: " + String(red_str.toInt())); logger.log_string("g: " + String(green_str.toInt())); logger.log_string("b: " + String(blue_str.toInt())); // set new main color set_main_color(red_str.toInt(), green_str.toInt(), blue_str.toInt()); } else if (webserver.argName(0).equals("mode")) // the parameter which was sent to this server is mode change { String mode_str = webserver.arg(0); logger.log_string("Mode change via Webserver to: " + mode_str); // set current mode/state accordant sent mode if (mode_str.equals("clock")) { state_change(ST_CLOCK); } else if (mode_str.equals("diclock")) { state_change(ST_DICLOCK); } else if (mode_str.equals("spiral")) { state_change(ST_SPIRAL); } else if (mode_str.equals("tetris")) { state_change(ST_TETRIS); } else if (mode_str.equals("snake")) { state_change(ST_SNAKE); } else if (mode_str.equals("pingpong")) { state_change(ST_PINGPONG); } else if (mode_str.equals("hearts")) { state_change(ST_HEARTS); } } else if (webserver.argName(0).equals("nightmode")) { String mode_str = webserver.arg(0); logger.log_string("Nightmode change via Webserver to: " + mode_str); mode_str.equals("1") ? set_night_mode(true) : set_night_mode(false); } else if (webserver.argName(0).equals("setting")) { String time_str = webserver.arg(0) + "-"; logger.log_string("Nightmode setting change via Webserver to: " + time_str); night_mode_times.nightmode_start_hour = split(time_str, '-', 0).toInt(); night_mode_times.nightmode_start_min = split(time_str, '-', 1).toInt(); night_mode_times.nightmode_end_hour = split(time_str, '-', 2).toInt(); night_mode_times.nightmode_end_min = split(time_str, '-', 3).toInt(); brightness = split(time_str, '-', 4).toInt(); if (brightness < 10) { brightness = 10; } if (night_mode_times.nightmode_start_hour < 0 || night_mode_times.nightmode_start_hour > 23) { night_mode_times.nightmode_start_hour = NIGHTMODE_START_HR; } if (night_mode_times.nightmode_start_min < 0 || night_mode_times.nightmode_start_min > 59) { night_mode_times.nightmode_start_min = NIGHTMODE_START_MIN; } if (night_mode_times.nightmode_end_hour < 0 || night_mode_times.nightmode_end_hour > 23) { night_mode_times.nightmode_end_hour = NIGHTMODE_END_HR; } if (night_mode_times.nightmode_end_min < 0 || night_mode_times.nightmode_end_min > 59) { night_mode_times.nightmode_end_min = NIGHTMODE_END_MIN; } EEPROM_write_to_address(ADR_NM_START_H, night_mode_times.nightmode_start_hour); EEPROM_write_to_address(ADR_NM_START_M, night_mode_times.nightmode_start_min); EEPROM_write_to_address(ADR_NM_END_H, night_mode_times.nightmode_end_hour); EEPROM_write_to_address(ADR_NM_END_M, night_mode_times.nightmode_end_min); EEPROM_write_to_address(ADR_BRIGHTNESS, brightness); logger.log_string("Nightmode starts at: " + String(night_mode_times.nightmode_start_hour) + ":" + String(night_mode_times.nightmode_start_min)); logger.log_string("Nightmode ends at: " + String(night_mode_times.nightmode_end_hour) + ":" + String(night_mode_times.nightmode_end_min)); logger.log_string("Brightness: " + String(brightness)); led_matrix.set_brightness(brightness); } else if (webserver.argName(0).equals("stateautochange")) { String mode_str = webserver.arg(0); logger.log_string("stateAutoChange change via Webserver to: " + mode_str); mode_str.equals("1") ? state_auto_change = true : state_auto_change = false; } else if (webserver.argName(0).equals("tetris")) { String cmd_str = webserver.arg(0); logger.log_string("Tetris cmd via Webserver to: " + cmd_str); if (cmd_str.equals("up")) { tetris.ctrlUp(); } else if (cmd_str.equals("left")) { tetris.ctrlLeft(); } else if (cmd_str.equals("right")) { tetris.ctrlRight(); } else if (cmd_str.equals("down")) { tetris.ctrlDown(); } else if (cmd_str.equals("play")) { tetris.ctrlStart(); } else if (cmd_str.equals("pause")) { tetris.ctrlPlayPause(); } } else if (webserver.argName(0).equals("snake")) { String cmd_str = webserver.arg(0); logger.log_string("Snake cmd via Webserver to: " + cmd_str); if (cmd_str.equals("up")) { snake.ctrlUp(); } else if (cmd_str.equals("left")) { snake.ctrlLeft(); } else if (cmd_str.equals("right")) { snake.ctrlRight(); } else if (cmd_str.equals("down")) { snake.ctrlDown(); } else if (cmd_str.equals("new")) { snake.initGame(); } } else if (webserver.argName(0).equals("pong")) { String cmd_str = webserver.arg(0); logger.log_string("Pong cmd via Webserver to: " + cmd_str); if (cmd_str.equals("up")) { pong.ctrlUp(1); } else if (cmd_str.equals("down")) { pong.ctrlDown(1); } else if (cmd_str.equals("new")) { pong.initGame(1); } } webserver.send(204, "text/plain", "No Content"); // this page doesn't send back content --> 204 } /** * @brief Handler for GET requests * */ void handle_data_request() { // receive data request and handle accordingly for (uint8_t i = 0; i < webserver.args(); i++) { Serial.print(webserver.argName(i)); Serial.print(F(": ")); Serial.println(webserver.arg(i)); } if (webserver.argName(0).equals("key")) // the parameter which was sent to this server is led color { String message = "{"; String keystr = webserver.arg(0); if (keystr.equals("mode")) { message += "\"mode\":\"" + state_names[current_state] + "\""; message += ","; message += "\"modeid\":\"" + String(current_state) + "\""; message += ","; message += "\"stateAutoChange\":\"" + String(state_auto_change) + "\""; message += ","; message += "\"night_mode\":\"" + String(night_mode) + "\""; message += ","; message += "\"nightModeStart\":\"" + leading_zero2digit(night_mode_times.nightmode_start_hour) + "-" + leading_zero2digit(night_mode_times.nightmode_start_min) + "\""; message += ","; message += "\"nightModeEnd\":\"" + leading_zero2digit(night_mode_times.nightmode_end_hour) + "-" + leading_zero2digit(night_mode_times.nightmode_end_min) + "\""; message += ","; message += "\"brightness\":\"" + String(brightness) + "\""; } message += "}"; webserver.send(200, "application/json", message); } } /** * @brief Set the nightmode state * * @param state true -> nightmode on */ void set_night_mode(bool state) { led_matrix.flush(); led_matrix.draw_on_matrix_smooth(0.2); night_mode = state; } /** * @brief Write value to EEPROM * * @param address address to write the value * @param value value to write */ void EEPROM_write_to_address(int address, int value) { EEPROM.put(address, value); EEPROM.commit(); } /** * @brief Read value from EEPROM * * @param address address * @return int value */ int EEPROM_read_address(int address) { int value; EEPROM.get(address, value); return value; } /** * @brief Convert Integer to String with leading zero * * @param value * @return String */ String leading_zero2digit(int value) { String msg = ""; if (value < 10) { msg = "0"; } msg += String(value); return msg; }