/** * 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 "animationfunctions.h" #include "pong.h" #include "snake.h" #include "tetris.h" #include "ledmatrix.h" #include "littlefs_wrapper.h" #include "base64_wrapper.h" // copied from https://github.com/Xander-Electronics/Base64 #include "ntp_client_plus.h" #include "otafunctions.h" #include "udp_logger.h" #include "wordclock_constants.h" #include "wordclock_functions.h" // DEBUG uint32_t dbg_counter = 0; // TODO RM const String state_names[] = {"Clock", "DiClock", "Spiral", "Tetris", "Snake", "PingPong"}; // 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_TIME_VISU_UPDATE, // stateAutoChange = 1 PERIOD_TIME_VISU_UPDATE, PERIOD_ANIMATION, PERIOD_ANIMATION, PERIOD_ANIMATION, PERIOD_PONG}}; // ---------------------------------------------------------------------------------- // 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 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); // Timestamp variables static unsigned long button_press_start = 0; // time of push button press start static unsigned long last_led_direct = 0; // time of last direct LED command (=> fall back to normal mode after timeout) static unsigned long last_animation_step = millis(); // time of last Matrix update static unsigned long last_heartbeat = millis(); // time of last heartbeat sending static unsigned long last_nightmode_check = millis(); // time of last nightmode check static unsigned long last_ntp_update = millis() - PERIOD_NTP_UPDATE - 5000; // time of last NTP update static unsigned long last_state_change = millis(); // time of last state change static unsigned long last_step = millis(); // time of last animation step 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 maincolor_clock = colors_24bit[2]; // color of the clock and digital clock static uint32_t maincolor_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 // here "wordclockAP" // 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 datarequests 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 (int r = 0; r < MATRIX_HEIGHT; r++) { for (int c = 0; c < MATRIX_WIDTH; c++) { matrix.fillScreen(0); matrix.drawPixel(c, r, 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', maincolor_clock); led_matrix.print_char(5, 0, 'P', maincolor_clock); led_matrix.print_number(0, 6, (address / 100), maincolor_clock); led_matrix.print_number(4, 6, (address / 10) % 10, maincolor_clock); led_matrix.print_number(8, 6, address % 10, maincolor_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, maincolor_clock); draw_minute_indicator(minutes, maincolor_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() { handleOTA(); // handle OTA logger.log_string("After handleOTA"); webserver.handleClient(); // handle webserver logger.log_string("After handleClient"); send_heartbeat(); // send heartbeat update logger.log_string("After send_heartbeat"); handle_current_state(); // handle current state - main process logger.log_string("After handle_current_state"); update_matrix(); // update matrix logger.log_string("After update_matrix"); handle_button(); // handle button press logger.log_string("After handle_button"); update_state_machine(); // handle state changes logger.log_string("After update_state_machine"); ntp_time_update(); // ntp time update logger.log_string("After ntp_time_update"); check_night_mode(); // check night mode logger.log_string("After check_night_mode"); } // ---------------------------------------------------------------------------------- // OTHER FUNCTIONS // ---------------------------------------------------------------------------------- void update_state_machine() { if (state_auto_change && (millis() - last_state_change > PERIOD_STATE_CHANGE) && !night_mode) { // increment state variable and trigger state change state_change((current_state + 1) % NUM_STATES); // save last automatic state change last_state_change = millis(); } } void handle_current_state() { // handle mode behaviours (trigger loopCycles of different modes depending on current mode) if (!night_mode && (millis() - last_step > PERIODS[state_auto_change][current_state]) && (millis() - last_led_direct > TIMEOUT_LEDDIRECT)) { 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), maincolor_clock); draw_minute_indicator((uint8_t)minutes, maincolor_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, maincolor_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, maincolor_snake, -1)) { // init snake for next run random_snake(true, 8, maincolor_snake, -1); } } else { snake.loopCycle(); } break; } case ST_PINGPONG: // state ping pong { pong.loopCycle(); break; } default: { break; } } last_step = millis(); } } /** * @brief Update matrix colors, should be called in loop() * * @param None */ void update_matrix() { // periodically write colors to matrix if (millis() - last_animation_step > PERIOD_MATRIX_UPDATE) { led_matrix.draw_on_matrix_smooth(filter_factor); last_animation_step = millis(); } } /** * @brief Send heartbeat, should be called in loop() * * @param None */ void send_heartbeat() { // send regularly heartbeat messages via UDP multicast if (millis() - last_heartbeat > PERIOD_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 last_heartbeat = millis(); // Check wifi status (only if no apmode) 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 if (millis() - last_nightmode_check > PERIOD_NIGHTMODE_CHECK) { 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); } last_nightmode_check = millis(); } } /** * @brief NTP time update, should be called in loop() * * @param None */ void ntp_time_update() { // NTP time update if (millis() - last_ntp_update > PERIOD_NTP_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 = millis(); 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) { // deactivate Nightmode set_night_mode(false); } // first clear matrix led_matrix.flush(); // set new state current_state = newState; 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 = millis(); } webserver.send(200, "text/plain", message); } } /** * @brief Check button commands * */ void handle_button() { static bool lastButtonState = false; bool buttonPressed = !digitalRead(BUTTON_PIN); // check rising edge if (buttonPressed == true && lastButtonState == false) { // button press start logger.log_string("Button press started"); button_press_start = millis(); } // check falling edge if (buttonPressed == false && lastButtonState == true) { // button press ended if ((millis() - button_press_start) > LONG_PRESS_MS) { // longpress -> nightmode logger.log_string("Button press ended - long press"); set_night_mode(true); } else if ((millis() - 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) % NUM_STATES); } } } lastButtonState = buttonPressed; } /** * @brief Set main color * */ void set_main_color(uint8_t red, uint8_t green, uint8_t blue) { maincolor_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 maincolor 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) { maincolor_clock = colors_24bit[2]; } else { maincolor_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 (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 Splits a string at given character and return specified element * * @param s string to split * @param parser separating character * @param index index of the element to return * @return String */ String split(String s, char parser, int index) { String rs = ""; int parser_cnt = 0; int r_from_index = 0, r_to_index = -1; while (index >= parser_cnt) { r_from_index = r_to_index + 1; r_to_index = s.indexOf(parser, r_from_index); if (index == parser_cnt) { if (r_to_index == 0 || r_to_index == -1) { return ""; } return s.substring(r_from_index, r_to_index); } else parser_cnt++; } return rs; } /** * @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; }