Files
wordclock/src/wordclock_esp8266.cpp
2023-08-21 20:26:38 +02:00

1065 lines
35 KiB
C++

/**
* 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 <Arduino.h>
#include "wordclock_esp8266.h"
#include <Adafruit_GFX.h> // https://github.com/adafruit/Adafruit-GFX-Library
#include <Adafruit_NeoMatrix.h> // https://github.com/adafruit/Adafruit_NeoMatrix
#include <Adafruit_NeoPixel.h> // NeoPixel library used to run the NeoPixel LEDs: https://github.com/adafruit/Adafruit_NeoPixel
#include <EEPROM.h> //from ESP8266 Arduino Core (automatically installed when ESP8266 was installed via Boardmanager)
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager WiFi Configuration Magic
#include <WiFiUdp.h>
// 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;
}