Compare commits
21 Commits
73aa168152
...
1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 1629f24fcc | |||
| adb3677d71 | |||
| d6f06525c9 | |||
| 4d810783fc | |||
| feb26d7269 | |||
| 3dfd6c82fe | |||
| 244455909d | |||
| f53f557a6a | |||
| e1df24633a | |||
| 4543cf1e09 | |||
| 0cf2e5ca90 | |||
| ff90c610ae | |||
| 2e50dd0e3f | |||
| b584cc03bd | |||
| eb4341e05b | |||
| 73b390d8cd | |||
| 5a51707452 | |||
| f4b49dd8c4 | |||
| a4be8f1d9e | |||
| 0543b9c0c7 | |||
| ef6061fc21 |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -66,3 +66,14 @@ modules.order
|
|||||||
Module.symvers
|
Module.symvers
|
||||||
Mkfile.old
|
Mkfile.old
|
||||||
dkms.conf
|
dkms.conf
|
||||||
|
|
||||||
|
# Python
|
||||||
|
venv
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Local backup
|
||||||
|
*.*_
|
||||||
|
*.*bak*
|
||||||
|
|||||||
99
README.md
99
README.md
@@ -1,96 +1,49 @@
|
|||||||
# Important Note:
|
# Important Note:
|
||||||
This project has been unofficially forked from https://github.com/techniccontroller/wordclock_esp8266 which was initially created by techniccontroller. Copyright and licensing is respected.
|
This project has been unofficially forked from https://github.com/techniccontroller/wordclock_esp8266 which was initially created by techniccontroller. Copyright and licensing is respected. Very many thanks for the initial code!
|
||||||
|
|
||||||
|
|
||||||
# Wordclock 2.0
|
# Wordclock 2.0
|
||||||
|
|
||||||
Wordclock 2.0 with ESP8266 and NTP time
|
Wordclock 2.0 with ESP8266 and NTP time
|
||||||
|
|
||||||
More details on my website: https://techniccontroller.com/word-clock-with-wifi-and-neopixel/
|
More details on techniccontroller's website: https://techniccontroller.com/word-clock-with-wifi-and-neopixel/
|
||||||
|
|
||||||
|
|
||||||
**Languages**
|
**Languages**
|
||||||
|
|
||||||
The Wordclock is available in **German**, **English** and **Italian** language. By default the language is German.
|
The Wordclock is available in **German** language.
|
||||||
To use the English or Italian language please replace the file *wordclockfunctions.ino* with *wordclockfunctions.ino_english* or *wordclockfunctions.ino_italian*.
|
|
||||||
The code compiles only with one file named *wordclockfunctions.ino*. So please rename the file you want to use to *wordclockfunctions.ino* and replace the existing file.
|
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- 6 modes (Clock, Digital Clock, SPIRAL animation, TETRIS, SNAKE, PONG)
|
- Time update via NTP server
|
||||||
- time update via NTP server
|
- Games: Pong, Snake, Tetris, ...
|
||||||
- automatic summer/wintertime change
|
- Automatic summer/wintertime change
|
||||||
- easy WIFI setup with WifiManager
|
- Easy wifi setup with WifiManager
|
||||||
- configurable color
|
- Configurable color
|
||||||
- configurable night mode (start and end time)
|
- Configurable night mode (start and end time)
|
||||||
- configurable brightness
|
- Configurable brightness
|
||||||
- automatic mode change
|
- Automatic mode change
|
||||||
- webserver interface for configuration and control
|
- Webserver interface for configuration and control (web address: wordclock.local)
|
||||||
- physical button to change mode or enable night mode without webserver
|
- Automatic current limiting of LEDs
|
||||||
- automatic current limiting of LEDs
|
|
||||||
|
|
||||||
## Pictures of clock
|
## Pictures of clock
|
||||||

|

|
||||||
|
|
||||||
## Screenshots of webserver UI
|
|
||||||

|
|
||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
1. Clone the project into the sketch folder of the Arduino IDE,
|
1. Clone the project into the a directory.
|
||||||
2. Rename the file "example_secrets.h" to "secrets.h". You don't need to change anything in the file if you want uses the normal WiFi setup with WiFiManager (see section "Remark about the WiFi setup").
|
2. Open directory in VSCode with PlatformIO extension installed.
|
||||||
3. Install the additional libraries and flash it to the ESP8266 as usual (See section [*Upload program to ESP8266*](https://github.com/techniccontroller/wordclock_esp8266/blob/main/README.md#upload-program-to-esp8266-with-arduino-ide) below).
|
3. Current dependencies (2024-07-21):
|
||||||
4. The implemented WiFiManager helps you to set up a WiFi connection with your home WiFi -> on the first startup it will create a WiFi access point named "WordclockAP". Connect your phone to this access point and follow the steps which will be shown to you.
|
- Platform espressif8266 @ 2.6.3 (required: espressif8266 @ 2.6.3)
|
||||||
5. After a successful WiFi setup, open the browser and enter the IP address of your ESP8266 to access the interface of the webserver.
|
- Libraries
|
||||||
6. Here you can then upload all files located in the folder "data". Please make sure all icons stay in the folder "icons" also on the webserver.
|
- ├── FastLED @ 3.7.0 (required: fastled/FastLED @ 3.7.0)
|
||||||
|
- ├── FastLED NeoMatrix @ 1.2.0 (required: marcmerlin/FastLED NeoMatrix @ ^1.2)
|
||||||
|
- │⠀⠀⠀└── Framebuffer GFX @ 1.1.0 (required: Framebuffer GFX)
|
||||||
<img src="https://techniccontroller.com/wp-content/uploads/filemanager1-1.png" height="300px" /> <img src="https://techniccontroller.com/wp-content/uploads/filemanager2-1.png" height="300px" /> <img src="https://techniccontroller.com/wp-content/uploads/filemanager3-1.png" height="300px" />
|
- │⠀⠀⠀│⠀⠀⠀├── Adafruit GFX Library @ 1.11.9 (required: Adafruit GFX Library)
|
||||||
|
- │⠀⠀⠀│⠀⠀⠀│⠀⠀⠀└── Adafruit BusIO @ 1.16.1 (required: Adafruit BusIO)
|
||||||
## Install needed Libraries
|
- ├── WiFiManager @ 0.16.0 (required: tzapu/WiFiManager @ ^0.16.0)
|
||||||
|
- └── base64 @ 1.4.0 (required: densaugeo/base64 @ ^1.4.0)
|
||||||
Please download all these libraries as ZIP from GitHub, and extract them in the *libraries* folder of your Sketchbook location (see **File -> Preferences**):
|
|
||||||
|
|
||||||
- https://github.com/adafruit/Adafruit-GFX-Library
|
|
||||||
- https://github.com/adafruit/Adafruit_NeoMatrix
|
|
||||||
- https://github.com/adafruit/Adafruit_NeoPixel
|
|
||||||
- https://github.com/tzapu/WiFiManager
|
|
||||||
- https://github.com/adafruit/Adafruit_BusIO
|
|
||||||
|
|
||||||
|
|
||||||
## Remark about the WiFi setup
|
## Remark about the WiFi setup
|
||||||
|
|
||||||
Regarding the Wifi setting, I have actually implemented two variants:
|
By default the WifiManager is activated. That is, the word clock makes the first time its own WiFi (should be called "WordclockAP"). There you simply connect to the cell phone and you can perform configuration of the WiFi settings conveniently as with SmartHome devices.
|
||||||
1. By default the WifiManager is activated. That is, the word clock makes the first time its own WiFi (should be called "WordclockAP"). There you simply connect to the cell phone and you can perform configuration of the WiFi settings conveniently as with a SmartHome devices (Very elegant 😊)
|
|
||||||
2. Another (traditional) variant is to define the wifi credentials in the code (in secrets.h).
|
|
||||||
- For this you have to comment out lines 230 to 251 in the code of the file *wordclock_esp8266.ino* (/\* before and \*/ after)
|
|
||||||
- and comment out lines 257 to 305 (/\* and \*/ remove)
|
|
||||||
|
|
||||||
|
|
||||||
## Remark about Logging
|
|
||||||
|
|
||||||
The wordclock send continuously log messages to the serial port and via multicast UDP. If you want to see these messages, you have to
|
|
||||||
|
|
||||||
- open the serial monitor in the Arduino IDE (Tools -> Serial Monitor). The serial monitor must be set to 115200 baud.
|
|
||||||
|
|
||||||
OR
|
|
||||||
|
|
||||||
- run the following steps for the multicast UDP logging:
|
|
||||||
|
|
||||||
1. starting situation: wordclock is connected to WLAN, a computer with installed Python (https://www.python.org/downloads/) is in the same local area network (WLAN or LAN doesn't matter).
|
|
||||||
3. open the file **multicastUDP_receiver.py** in a text editor and in line 81 enter the IP address of the computer (not the wordclock!).
|
|
||||||
```python
|
|
||||||
# ip address of network interface
|
|
||||||
MCAST_IF_IP = '192.168.0.7'
|
|
||||||
```
|
|
||||||
4. execute the script with following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python multicastUDP_receiver_analyzer.py
|
|
||||||
```
|
|
||||||
|
|
||||||
5. now you should see the log messages of the word clock (every 5 seconds a heartbeat message and the currently displayed time).
|
|
||||||
If this is not the case, there could be a problem with the network settings of the computer, then recording is unfortunately not possible.
|
|
||||||
|
|
||||||
6. If special events (failed NTP update, reboot) occur, a section of the log is saved in a file called *log.txt*.
|
|
||||||
In principle, the events are not critical and will occur from time to time, but should not be too frequent.
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "wordclock_constants.h"
|
#include "wordclock_constants.h"
|
||||||
|
|
||||||
|
extern bool spiral_direction; // Direction of sprial animation
|
||||||
|
|
||||||
enum Direction
|
enum Direction
|
||||||
{
|
{
|
||||||
RIGHT,
|
RIGHT,
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright (C) 2016 Arturo Guadalupi. All right reserved.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BASE64_WRAPPER_H
|
|
||||||
#define BASE64_WRAPPER_H
|
|
||||||
|
|
||||||
class Base64Class{
|
|
||||||
public:
|
|
||||||
int encode(char *output, char *input, int inputLength);
|
|
||||||
int decode(char * output, char * input, int inputLength);
|
|
||||||
int encodedLength(int plainLength);
|
|
||||||
int decodedLength(char * input, int inputLength);
|
|
||||||
|
|
||||||
private:
|
|
||||||
inline void fromA3ToA4(unsigned char * A4, unsigned char * A3);
|
|
||||||
inline void fromA4ToA3(unsigned char * A3, unsigned char * A4);
|
|
||||||
inline unsigned char lookupTable(char c);
|
|
||||||
};
|
|
||||||
extern Base64Class Base64;
|
|
||||||
|
|
||||||
#endif /* BASE64_WRAPPER_H */
|
|
||||||
26
include/diagnosis.h
Normal file
26
include/diagnosis.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#ifndef DIAGNOSIS_H
|
||||||
|
#define DIAGNOSIS_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "led_matrix.h"
|
||||||
|
#include "udp_logger.h"
|
||||||
|
|
||||||
|
class Diagnosis
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Diagnosis(); // constructor
|
||||||
|
Diagnosis(UDPLogger *logger, LEDMatrix *matrix); // constructor
|
||||||
|
|
||||||
|
String handle_command(const String &command);
|
||||||
|
String print_device_info();
|
||||||
|
String print_sketch_info();
|
||||||
|
String print_last_reset_details();
|
||||||
|
String print_matrix_fps();
|
||||||
|
|
||||||
|
private:
|
||||||
|
UDPLogger *_logger;
|
||||||
|
LEDMatrix * _matrix;
|
||||||
|
void print(const String &s);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DIAGNOSIS_H
|
||||||
@@ -1,24 +1,28 @@
|
|||||||
#ifndef LEDMATRIX_H
|
#ifndef LEDMATRIX_H
|
||||||
#define LEDMATRIX_H
|
#define LEDMATRIX_H
|
||||||
|
|
||||||
|
#ifndef FASTLED_INTERNAL
|
||||||
|
#define FASTLED_INTERNAL
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Adafruit_GFX.h>
|
#include <Adafruit_GFX.h>
|
||||||
#include <Adafruit_NeoMatrix.h>
|
#include <FastLED_NeoMatrix.h>
|
||||||
#include "wordclock_constants.h"
|
#include "wordclock_constants.h"
|
||||||
#include "udp_logger.h"
|
#include "udp_logger.h"
|
||||||
|
|
||||||
#define DEFAULT_CURRENT_LIMIT 9999
|
#define DEFAULT_CURRENT_LIMIT 9999
|
||||||
|
|
||||||
extern const uint32_t colors_24bit[NUM_COLORS];
|
extern const uint32_t colors_24bit[NUM_COLORS];
|
||||||
|
|
||||||
class LEDMatrix
|
class LEDMatrix
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LEDMatrix(Adafruit_NeoMatrix * matrix, uint8_t brightness, UDPLogger * logger);
|
LEDMatrix(FastLED_NeoMatrix *matrix, uint8_t brightness, UDPLogger *logger);
|
||||||
static uint16_t color_24_to_16bit(uint32_t color24bit);
|
static uint16_t color_24_to_16bit(uint32_t color24bit);
|
||||||
static uint32_t color_24bit(uint8_t r, uint8_t g, uint8_t b);
|
static uint32_t color_24bit(uint8_t r, uint8_t g, uint8_t b);
|
||||||
static uint32_t interpolate_color_24bit(uint32_t color1, uint32_t color2, float factor);
|
static uint32_t interpolate_color_24bit(uint32_t color1, uint32_t color2, float factor);
|
||||||
static uint32_t wheel(uint8_t WheelPos);
|
static uint32_t wheel(uint8_t WheelPos);
|
||||||
|
uint16_t get_fps(void);
|
||||||
void draw_on_matrix_instant();
|
void draw_on_matrix_instant();
|
||||||
void draw_on_matrix_smooth(float factor);
|
void draw_on_matrix_smooth(float factor);
|
||||||
void flush(void);
|
void flush(void);
|
||||||
@@ -31,22 +35,22 @@ public:
|
|||||||
void setup_matrix();
|
void setup_matrix();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Adafruit_NeoMatrix * _neomatrix;
|
FastLED_NeoMatrix *_neomatrix;
|
||||||
UDPLogger * _logger;
|
UDPLogger *_logger;
|
||||||
|
|
||||||
uint8_t _brightness;
|
uint8_t _brightness;
|
||||||
uint16_t _current_limit;
|
uint16_t _current_limit;
|
||||||
|
|
||||||
// target representation of matrix as 2D array
|
// target representation of matrix as 2D array
|
||||||
uint32_t _target_grid[MATRIX_HEIGHT][MATRIX_WIDTH] = {0};
|
uint32_t _target_grid[MATRIX_HEIGHT][MATRIX_WIDTH];
|
||||||
|
|
||||||
// current representation of matrix as 2D array
|
// current representation of matrix as 2D array
|
||||||
uint32_t _current_grid[MATRIX_HEIGHT][MATRIX_WIDTH] = {0};
|
uint32_t _current_grid[MATRIX_HEIGHT][MATRIX_WIDTH];
|
||||||
|
|
||||||
// target representation of minutes indicator leds
|
// target representation of minutes indicator LEDs
|
||||||
uint32_t _target_minute_indicators[4] = {0, 0, 0, 0};
|
uint32_t _target_minute_indicators[4] = {0, 0, 0, 0};
|
||||||
|
|
||||||
// current representation of minutes indicator leds
|
// current representation of minutes indicator LEDs
|
||||||
uint32_t _current_minute_indicators[4] = {0, 0, 0, 0};
|
uint32_t _current_minute_indicators[4] = {0, 0, 0, 0};
|
||||||
|
|
||||||
void _draw_on_matrix(float factor);
|
void _draw_on_matrix(float factor);
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ private:
|
|||||||
UDPLogger *_logger;
|
UDPLogger *_logger;
|
||||||
uint8_t _gameState = 0;
|
uint8_t _gameState = 0;
|
||||||
uint8_t _numBots = 0;
|
uint8_t _numBots = 0;
|
||||||
uint8_t _playerMovement[PLAYER_AMOUNT] = {0};
|
uint8_t _playerMovement[PLAYER_AMOUNT];
|
||||||
Coords _paddles[PLAYER_AMOUNT][PADDLE_WIDTH] = {0};
|
Coords _paddles[PLAYER_AMOUNT][PADDLE_WIDTH];
|
||||||
Coords _ball = {0, 0};
|
Coords _ball = {0, 0};
|
||||||
Coords _ball_old = {0, 0};
|
Coords _ball_old = {0, 0};
|
||||||
int _ballMovement[2] = {0, 0};
|
int _ballMovement[2] = {0, 0};
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ private:
|
|||||||
uint8_t _userDirection = 0;
|
uint8_t _userDirection = 0;
|
||||||
uint8_t _gameState = 0;
|
uint8_t _gameState = 0;
|
||||||
Coords _head = {0, 0};
|
Coords _head = {0, 0};
|
||||||
Coords _tail[MAX_TAIL_LENGTH] = {0};
|
Coords _tail[MAX_TAIL_LENGTH];
|
||||||
Coords _food = {0, 0};
|
Coords _food = {0, 0};
|
||||||
unsigned long _lastDrawUpdate = 0;
|
unsigned long _lastDrawUpdate = 0;
|
||||||
unsigned long _lastButtonClick = 0;
|
unsigned long _lastButtonClick = 0;
|
||||||
|
|||||||
80
include/time_manager.h
Normal file
80
include/time_manager.h
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#ifndef TIME_MANAGER_H
|
||||||
|
#define TIME_MANAGER_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "udp_logger.h"
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
TIME_UPDATE_FAILED = 0,
|
||||||
|
TIME_UPDATE_OK = 1,
|
||||||
|
TIME_UPDATE_PENDING = 2,
|
||||||
|
} TimeUpdateState;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
TM_INIT = 0,
|
||||||
|
TM_INITIAL_SYNC = 1,
|
||||||
|
TM_NORMAL = 2,
|
||||||
|
TM_SYNC_OVERDUE = 3,
|
||||||
|
TM_SYNC_TIMEOUT = 4,
|
||||||
|
TM_SETUP_FAILED = 5,
|
||||||
|
} TimeManagerState;
|
||||||
|
|
||||||
|
class TimeManager
|
||||||
|
{
|
||||||
|
#define NTP_MAX_UPDATE_TIME_US (5 * 1000 * 1000) // 5000ms max update time
|
||||||
|
|
||||||
|
public:
|
||||||
|
// constructors
|
||||||
|
TimeManager();
|
||||||
|
TimeManager(const char *tz,
|
||||||
|
const char *ntp_server,
|
||||||
|
bool (*is_wifi_connected)(void),
|
||||||
|
uint32 ntp_max_offline_time_s,
|
||||||
|
UDPLogger *logger);
|
||||||
|
|
||||||
|
// init
|
||||||
|
void init();
|
||||||
|
|
||||||
|
// callback
|
||||||
|
void time_set_cb(void); // callback which is called when NTP time was set
|
||||||
|
|
||||||
|
// ntp methods
|
||||||
|
bool ntp_sync_successful(void) const; // was there a NTP sync once?
|
||||||
|
bool ntp_sync_overdue(void); // function to check if NTP sync is overdue
|
||||||
|
bool ntp_sync_timeout(void); // function to check if maximum time since last NTP sync has been reached
|
||||||
|
TimeUpdateState get_time(); // main time update method, called in loop
|
||||||
|
|
||||||
|
// getter for time values
|
||||||
|
bool isdst(void) const; // true if summertime (daylight saving time)
|
||||||
|
int day(void) const;
|
||||||
|
int hour(void) const;
|
||||||
|
int minute(void) const;
|
||||||
|
int month(void) const;
|
||||||
|
int year(void) const;
|
||||||
|
struct tm time_info(void) const;
|
||||||
|
|
||||||
|
// getter
|
||||||
|
TimeManagerState tm_state(void) const; // get current state
|
||||||
|
|
||||||
|
// logging
|
||||||
|
void log_time() const; // log _time_info
|
||||||
|
void log_time(struct tm time_info) const; // log argument time_info
|
||||||
|
|
||||||
|
private:
|
||||||
|
void _set_up_ntp(void); // set up NTP server
|
||||||
|
bool (*_is_wifi_connected)(void); // function to check if wifi is connected
|
||||||
|
|
||||||
|
const char *_ntp_server = "pool.ntp.org"; // ntp server address
|
||||||
|
const char *_tz; // timezone
|
||||||
|
struct tm _time_info = {0, 0, 0, 0, 0, 0, 0, 0, 0}; // structure tm holds time information
|
||||||
|
time_t _now = 0; // local time value
|
||||||
|
time_t _ntp_sync_timestamp_s = 0; // timestamp of last successful ntp sync
|
||||||
|
TimeManagerState _tm_state = TM_INIT; // Main state
|
||||||
|
UDPLogger *_logger; // logger instance
|
||||||
|
uint32 _ntp_max_offline_time_s; // maximum time in seconds which is considered ok since last NTP update
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* TIME_MANAGER_H */
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
#define HTTP_PORT (80) // Standard HTTP port
|
#define HTTP_PORT (80) // Standard HTTP port
|
||||||
|
|
||||||
// ESP8266 Pins
|
// ESP8266 Pins
|
||||||
#define NEOPIXEL_PIN (14) // pin to which the NeoPixels are attached
|
#define FASTLED_PIN (14) // pin to which the LEDs are attached
|
||||||
#define BUTTON_PIN (5) // pin to which the button is attached
|
#define BUTTON_PIN (5) // pin to which the button is attached
|
||||||
|
|
||||||
// Time limits
|
// Time limits
|
||||||
@@ -34,16 +34,17 @@
|
|||||||
|
|
||||||
// Timings in us
|
// Timings in us
|
||||||
#define PERIOD_ANIMATION_US (200 * 1000) // 200ms
|
#define PERIOD_ANIMATION_US (200 * 1000) // 200ms
|
||||||
#define PERIOD_CLOCK_UPDATE_US (1 * 1000 * 1000) // 1s
|
#define PERIOD_CLOCK_UPDATE_US (1 * 1000 * 1000) // Must be 1s! Do not change!
|
||||||
#define PERIOD_HEARTBEAT_US (1 * 1000 * 1000) // 1s
|
#define PERIOD_HEARTBEAT_US (1 * 1000 * 1000) // 1s
|
||||||
#define PERIOD_MATRIX_UPDATE_US (100 * 1000) // 100ms
|
#define PERIOD_MATRIX_UPDATE_US (33 * 1000) // 33ms
|
||||||
#define PERIOD_NIGHTMODE_CHECK_US (20 * 1000 * 1000) // 20s
|
#define PERIOD_NIGHTMODE_CHECK_US (30 * 1000 * 1000) // 30s
|
||||||
#define PERIOD_NTP_UPDATE_US (30 * 1000 * 1000) // 30s
|
#define PERIOD_TIME_UPDATE_US (1 * 1000 * 1000) // 1000ms
|
||||||
#define PERIOD_PONG_US (10 * 1000) // 10ms
|
#define PERIOD_PONG_US (10 * 1000) // 10ms
|
||||||
#define PERIOD_SNAKE_US (50 * 1000) // 50ms
|
#define PERIOD_SNAKE_US (50 * 1000) // 50ms
|
||||||
#define PERIOD_STATE_CHANGE_US (10 * 1000 * 1000) // 10s
|
#define PERIOD_STATE_CHANGE_US (10 * 1000 * 1000) // 10s
|
||||||
#define PERIOD_TETRIS_US (50 * 1000) // 50ms
|
#define PERIOD_TETRIS_US (50 * 1000) // 50ms
|
||||||
#define TIMEOUT_LEDDIRECT_US (5 * 1000 * 1000) // 5s
|
#define TIMEOUT_LEDDIRECT_US (5 * 1000 * 1000) // 5s
|
||||||
|
#define PERIOD_BRIGHTNESS_UPDATE_US (5 * 60 * 1000 * 1000) // 300s
|
||||||
|
|
||||||
#define SHORT_PRESS_US (100 * 1000) // 100ms
|
#define SHORT_PRESS_US (100 * 1000) // 100ms
|
||||||
#define LONG_PRESS_US (2 * 1000 * 1000) // 2s
|
#define LONG_PRESS_US (2 * 1000 * 1000) // 2s
|
||||||
@@ -62,9 +63,17 @@
|
|||||||
|
|
||||||
// Number of colors in colors array
|
// Number of colors in colors array
|
||||||
#define NUM_COLORS (7)
|
#define NUM_COLORS (7)
|
||||||
|
#define COLOR_ORDER GRB // WS2812B color order
|
||||||
|
|
||||||
// LED matrix size
|
// LED matrix size
|
||||||
#define MATRIX_WIDTH (11)
|
#define MATRIX_WIDTH (11)
|
||||||
#define MATRIX_HEIGHT (11)
|
#define MATRIX_HEIGHT (11)
|
||||||
|
#define NUM_MATRIX (MATRIX_WIDTH * (MATRIX_HEIGHT + 1))
|
||||||
|
|
||||||
|
// NTP macros
|
||||||
|
#define BUILD_YEAR (__DATE__ + 7) // Will expand to current year at compile time as string.
|
||||||
|
#define NTP_MINIMUM_RX_YEAR (atoi(BUILD_YEAR) - 1) // Will expand to current year minus one at compile time.
|
||||||
|
#define NTP_START_YEAR (1900) // NTP minimum year is 1900
|
||||||
|
#define NTP_MAX_OFFLINE_TIME_S (7 * 24 * 3600) // Watchdog value, maximum offline time before a restart is triggered
|
||||||
|
|
||||||
#endif /* WORDCLOCK_CONSTANTS_H */
|
#endif /* WORDCLOCK_CONSTANTS_H */
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#define WORDCLOCK_ESP8266_H
|
#define WORDCLOCK_ESP8266_H
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <ESP8266WebServer.h>
|
#include <ESP8266WebServer.h>
|
||||||
#include "led_matrix.h"
|
#include "led_matrix.h"
|
||||||
#include "udp_logger.h"
|
#include "udp_logger.h"
|
||||||
@@ -11,6 +12,9 @@
|
|||||||
|
|
||||||
#define EEPROM_SIZE (sizeof(EepromLayout_st) / sizeof(uint8_t))
|
#define EEPROM_SIZE (sizeof(EepromLayout_st) / sizeof(uint8_t))
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
// TYPEDEFS
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
int start_hour;
|
int start_hour;
|
||||||
@@ -24,7 +28,7 @@ typedef struct
|
|||||||
uint8_t red;
|
uint8_t red;
|
||||||
uint8_t green;
|
uint8_t green;
|
||||||
uint8_t blue;
|
uint8_t blue;
|
||||||
uint8_t alpha;
|
uint8_t alpha; // note: unused
|
||||||
} Color_st;
|
} Color_st;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
@@ -54,11 +58,15 @@ typedef enum
|
|||||||
NUM_STATES
|
NUM_STATES
|
||||||
} ClockState_en;
|
} ClockState_en;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
// FUNCTIONS DECLARATIONS
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
bool check_wifi_status(void);
|
||||||
String leading_zero2digit(int value);
|
String leading_zero2digit(int value);
|
||||||
uint8_t calculate_dynamic_brightness(uint8_t min_brightness, uint8_t max_brightness, int hours, int minutes, bool summertime);
|
uint8_t calculate_dynamic_brightness(uint8_t min_brightness, uint8_t max_brightness, int hours, int minutes, bool summertime);
|
||||||
uint8_t update_brightness(void);
|
uint8_t update_brightness(void);
|
||||||
void check_night_mode(void);
|
void check_night_mode(void);
|
||||||
void check_wifi_status(void);
|
void cold_start_setup(void);
|
||||||
void draw_main_color(void);
|
void draw_main_color(void);
|
||||||
void handle_button(void);
|
void handle_button(void);
|
||||||
void handle_command(void);
|
void handle_command(void);
|
||||||
@@ -66,9 +74,7 @@ void handle_current_state(void);
|
|||||||
void handle_data_request(void);
|
void handle_data_request(void);
|
||||||
void handle_led_direct(void);
|
void handle_led_direct(void);
|
||||||
void limit_value_ranges(void);
|
void limit_value_ranges(void);
|
||||||
void log_time(tm local_time);
|
void log_data(void);
|
||||||
void ntp_time_update(uint32 *last_ntp_update_us);
|
|
||||||
void ntp_time_update(uint32 *last_ntp_update_us);
|
|
||||||
void on_state_entry(uint8_t state);
|
void on_state_entry(uint8_t state);
|
||||||
void read_settings_from_EEPROM(void);
|
void read_settings_from_EEPROM(void);
|
||||||
void reset_wifi_credentials(void);
|
void reset_wifi_credentials(void);
|
||||||
@@ -76,7 +82,7 @@ void send_heartbeat(void);
|
|||||||
void set_dynamic_brightness(bool state);
|
void set_dynamic_brightness(bool state);
|
||||||
void set_main_color(uint8_t red, uint8_t green, uint8_t blue);
|
void set_main_color(uint8_t red, uint8_t green, uint8_t blue);
|
||||||
void set_night_mode(bool on);
|
void set_night_mode(bool on);
|
||||||
void state_change(uint8_t newState);
|
void state_change(ClockState_en new_state);
|
||||||
void update_matrix(void);
|
void update_matrix(void);
|
||||||
void write_settings_to_EEPROM(void);
|
void write_settings_to_EEPROM(void);
|
||||||
|
|
||||||
|
|||||||
@@ -12,14 +12,16 @@
|
|||||||
default_envs = nodemcuv2
|
default_envs = nodemcuv2
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
platform = espressif8266
|
platform = espressif8266@2.6.3
|
||||||
board = nodemcuv2
|
board = nodemcuv2
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps =
|
lib_deps =
|
||||||
adafruit/Adafruit BusIO@^1.15.0
|
densaugeo/base64@^1.4.0
|
||||||
adafruit/Adafruit NeoMatrix@^1.3.0
|
fastled/FastLED@3.7.6
|
||||||
adafruit/Adafruit NeoPixel@^1.11.0
|
marcmerlin/FastLED NeoMatrix@^1.2
|
||||||
tzapu/WiFiManager@^0.16.0
|
tzapu/WiFiManager@^2.0.17
|
||||||
|
build_flags =
|
||||||
|
-DFASTLED_ESP8266_RAW_PIN_ORDER
|
||||||
|
|
||||||
[env:nodemcuv2]
|
[env:nodemcuv2]
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
|
|||||||
13
scripts/http_diagnosis.py
Normal file
13
scripts/http_diagnosis.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
r = requests.get("http://wordclock.local/cmd?diag=reset_info")
|
||||||
|
print(r.status_code)
|
||||||
|
print(r.text)
|
||||||
|
|
||||||
|
r = requests.get("http://wordclock.local/cmd?diag=sketch_info")
|
||||||
|
print(r.status_code)
|
||||||
|
print(r.text)
|
||||||
|
|
||||||
|
r = requests.get("http://wordclock.local/cmd?diag=device_info")
|
||||||
|
print(r.status_code)
|
||||||
|
print(r.text)
|
||||||
@@ -4,13 +4,15 @@ import queue
|
|||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
LOG_PATH = Path("C:/temp/wordclock_log.txt")
|
||||||
|
|
||||||
def setup_logging():
|
def setup_logging():
|
||||||
FORMAT = "%(asctime)s %(message)s"
|
FORMAT = "%(asctime)s %(message)s"
|
||||||
logging.basicConfig(format=FORMAT, level=logging.INFO)
|
logging.basicConfig(format=FORMAT, level=logging.INFO)
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
handler = logging.StreamHandler(sys.stdout)
|
handler = logging.FileHandler(LOG_PATH)
|
||||||
handler.setLevel(logging.INFO)
|
handler.setLevel(logging.INFO)
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
return logger
|
return logger
|
||||||
@@ -42,33 +44,9 @@ mreq = struct.pack("4s4s", group, socket.inet_aton(get_ip_address()))
|
|||||||
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
||||||
|
|
||||||
logger.info("Ready")
|
logger.info("Ready")
|
||||||
saveCounter = 0
|
|
||||||
|
|
||||||
buffer = queue.Queue(20)
|
|
||||||
|
|
||||||
# Receive/respond loop
|
# Receive/respond loop
|
||||||
while True:
|
while True:
|
||||||
data, address = sock.recvfrom(1024)
|
data, address = sock.recvfrom(1024)
|
||||||
data_str = data.decode("utf-8").strip()
|
data_str = data.decode("utf-8").strip()
|
||||||
logger.info(data_str)
|
logger.info(data_str)
|
||||||
data_str = datetime.now().strftime("%b-%d-%Y_%H%M%S") + ": " + data_str
|
|
||||||
buffer.put(data_str)
|
|
||||||
if buffer.full():
|
|
||||||
buffer.get()
|
|
||||||
|
|
||||||
if "NTP-Update not successful" in data_str or "Start program" in data_str:
|
|
||||||
f = open("log.txt", "a")
|
|
||||||
while not buffer.empty():
|
|
||||||
f.write(buffer.get())
|
|
||||||
f.write("\n")
|
|
||||||
f.close()
|
|
||||||
saveCounter = 20
|
|
||||||
|
|
||||||
if saveCounter > 0:
|
|
||||||
f = open("log.txt", "a")
|
|
||||||
f.write(data_str)
|
|
||||||
f.write("\n")
|
|
||||||
if saveCounter == 1:
|
|
||||||
f.write("\n")
|
|
||||||
f.close()
|
|
||||||
saveCounter -= 1
|
|
||||||
|
|||||||
BIN
scripts/requirements.txt
Normal file
BIN
scripts/requirements.txt
Normal file
Binary file not shown.
93
src/connectivity/diagnosis.cpp
Normal file
93
src/connectivity/diagnosis.cpp
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#include "diagnosis.h"
|
||||||
|
|
||||||
|
Diagnosis::Diagnosis()
|
||||||
|
{
|
||||||
|
_logger = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Diagnosis::Diagnosis(UDPLogger *logger, LEDMatrix *matrix) // constructor
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_matrix = matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
String Diagnosis::handle_command(const String &command)
|
||||||
|
{
|
||||||
|
if (command == "device_info")
|
||||||
|
{
|
||||||
|
return print_device_info();
|
||||||
|
}
|
||||||
|
else if (command == "sketch_info")
|
||||||
|
{
|
||||||
|
return print_sketch_info();
|
||||||
|
}
|
||||||
|
else if (command == "reset_info")
|
||||||
|
{
|
||||||
|
return print_last_reset_details();
|
||||||
|
}
|
||||||
|
else if (command == "matrix_fps")
|
||||||
|
{
|
||||||
|
return print_matrix_fps();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Handle unknown command
|
||||||
|
String unknown_command = "Diagnosis: Unknown command!\n";
|
||||||
|
print(unknown_command);
|
||||||
|
return unknown_command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String Diagnosis::print_device_info()
|
||||||
|
{
|
||||||
|
// Retrieve and print device information
|
||||||
|
String device_info = "Device Information:\n";
|
||||||
|
device_info += "Chip ID: " + String(ESP.getChipId()) + "\n";
|
||||||
|
device_info += "Flash Chip ID: " + String(ESP.getFlashChipId()) + "\n";
|
||||||
|
device_info += "Flash Chip Size: " + String(ESP.getFlashChipSize()) + " bytes\n";
|
||||||
|
device_info += "Free Heap Size: " + String(ESP.getFreeHeap()) + " bytes\n";
|
||||||
|
device_info += "Free Sketch Space: " + String(ESP.getFreeSketchSpace()) + " bytes\n";
|
||||||
|
device_info += "SDK Version: " + String(ESP.getSdkVersion()) + "\n";
|
||||||
|
print(device_info);
|
||||||
|
return device_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
String Diagnosis::print_sketch_info()
|
||||||
|
{
|
||||||
|
// Retrieve and print sketch information
|
||||||
|
String sketch_info = "Sketch Information:\n";
|
||||||
|
sketch_info += "Sketch Size: " + String(ESP.getSketchSize()) + " bytes\n";
|
||||||
|
sketch_info += "Sketch MD5: " + String(ESP.getSketchMD5()) + "\n";
|
||||||
|
print(sketch_info);
|
||||||
|
return sketch_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
String Diagnosis::print_last_reset_details()
|
||||||
|
{
|
||||||
|
// Retrieve and print last reset details
|
||||||
|
String reset_info = "Last Reset Details:\n";
|
||||||
|
reset_info += "Reset Reason: " + String(ESP.getResetReason()) + "\n";
|
||||||
|
reset_info += "Reset Info: " + String(ESP.getResetInfo()) + "\n";
|
||||||
|
print(reset_info);
|
||||||
|
return reset_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
String Diagnosis::print_matrix_fps()
|
||||||
|
{
|
||||||
|
// Retrieve and print matrix FPS
|
||||||
|
String matrix_fps = "Matrix FPS: " + String(_matrix->get_fps()) + "\n";
|
||||||
|
print(matrix_fps);
|
||||||
|
return matrix_fps;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Diagnosis::print(const String &s)
|
||||||
|
{
|
||||||
|
if (_logger != nullptr)
|
||||||
|
{
|
||||||
|
_logger->log_string(s);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.println(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
214
src/connectivity/time_manager.cpp
Normal file
214
src/connectivity/time_manager.cpp
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
#include "time_manager.h"
|
||||||
|
#include "wordclock_constants.h"
|
||||||
|
#include <coredecls.h> // required for settimeofday_cb()
|
||||||
|
#include <sntp.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
extern UDPLogger logger; // logging instance
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
// Class
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
TimeManager::TimeManager(){};
|
||||||
|
TimeManager::TimeManager(const char *tz,
|
||||||
|
const char *ntp_server,
|
||||||
|
bool (*is_wifi_connected)(void),
|
||||||
|
uint32 ntp_max_offline_time_s,
|
||||||
|
UDPLogger *logger)
|
||||||
|
{
|
||||||
|
_tz = tz;
|
||||||
|
_ntp_server = ntp_server;
|
||||||
|
_is_wifi_connected = is_wifi_connected;
|
||||||
|
_ntp_max_offline_time_s = ntp_max_offline_time_s;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeManager::init()
|
||||||
|
{
|
||||||
|
// Set up NTP server once
|
||||||
|
_set_up_ntp();
|
||||||
|
|
||||||
|
if (_tm_state == TM_INITIAL_SYNC)
|
||||||
|
{
|
||||||
|
// force sntp reinit now
|
||||||
|
sntp_stop();
|
||||||
|
sntp_init();
|
||||||
|
}
|
||||||
|
settimeofday_cb([&]() { time_set_cb(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeManager::ntp_sync_successful(void) const
|
||||||
|
{
|
||||||
|
return (_now > 1716913300); // UTC timestamp in the past (28.05.2024)
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeManager::ntp_sync_overdue(void)
|
||||||
|
{
|
||||||
|
if (!ntp_sync_successful())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool retval = false;
|
||||||
|
// after the ntp sync update delay has been reached six times, the sync is considered overdue
|
||||||
|
if (_now >= (_ntp_sync_timestamp_s + (time_t)(6 * (sntp_update_delay_MS_rfc_not_less_than_15000() / 1000))))
|
||||||
|
{
|
||||||
|
_tm_state = TM_SYNC_OVERDUE;
|
||||||
|
retval = true;
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeManager::ntp_sync_timeout(void)
|
||||||
|
{
|
||||||
|
if (!ntp_sync_successful())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool retval = false;
|
||||||
|
// after the maxmimum offline time has been reached, the sync is considered timed out
|
||||||
|
if (_now >= (_ntp_sync_timestamp_s + (time_t)_ntp_max_offline_time_s))
|
||||||
|
{
|
||||||
|
_tm_state = TM_SYNC_TIMEOUT;
|
||||||
|
retval = true;
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief NTP time update, should be called in loop().
|
||||||
|
*
|
||||||
|
* @retval true if last update was successful
|
||||||
|
*/
|
||||||
|
TimeUpdateState TimeManager::get_time()
|
||||||
|
{
|
||||||
|
TimeUpdateState retval = TIME_UPDATE_PENDING; // NTP time update
|
||||||
|
uint32 timestamp_us = system_get_time(); // NTP update start time
|
||||||
|
struct tm time_info; // local NTP time info
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
_now = time(nullptr); // update time
|
||||||
|
(void)localtime_r(&_now, &time_info); // convert time
|
||||||
|
yield(); // since this loop could take up to NTP_MAX_UPDATE_TIME_US
|
||||||
|
|
||||||
|
} while (((system_get_time() - timestamp_us) <= NTP_MAX_UPDATE_TIME_US) && (time_info.tm_year < (NTP_MINIMUM_RX_YEAR - NTP_START_YEAR)));
|
||||||
|
|
||||||
|
retval = (time_info.tm_year <= (NTP_MINIMUM_RX_YEAR - NTP_START_YEAR)) ? TIME_UPDATE_FAILED : TIME_UPDATE_OK; // sanity check
|
||||||
|
|
||||||
|
if (retval == TIME_UPDATE_OK)
|
||||||
|
{
|
||||||
|
_time_info = time_info; // take over time_info to member variable
|
||||||
|
_tm_state = TM_NORMAL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.log_string("NTP-Update was not successful. Retrying in " + String(sntp_update_delay_MS_rfc_not_less_than_15000()) + "ms.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimeManager::isdst(void) const
|
||||||
|
{
|
||||||
|
return (_time_info.tm_isdst > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeManager::day(void) const
|
||||||
|
{
|
||||||
|
return (_time_info.tm_mday + 1); // add 1 to get actual day
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeManager::hour(void) const
|
||||||
|
{
|
||||||
|
return _time_info.tm_hour;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeManager::minute(void) const
|
||||||
|
{
|
||||||
|
return _time_info.tm_min;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeManager::month(void) const
|
||||||
|
{
|
||||||
|
return (_time_info.tm_mon + 1); // add 1 to get actual month
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimeManager::year(void) const
|
||||||
|
{
|
||||||
|
return _time_info.tm_year + NTP_START_YEAR; // add start year to get actual year
|
||||||
|
}
|
||||||
|
|
||||||
|
struct tm TimeManager::time_info(void) const
|
||||||
|
{
|
||||||
|
return _time_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeManagerState TimeManager::tm_state(void) const
|
||||||
|
{
|
||||||
|
return _tm_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Log time_info as string.
|
||||||
|
*
|
||||||
|
* @param local_time
|
||||||
|
*/
|
||||||
|
void TimeManager::log_time() const
|
||||||
|
{
|
||||||
|
log_time(_time_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Log time_info as string.
|
||||||
|
*
|
||||||
|
* @param local_time
|
||||||
|
*/
|
||||||
|
void TimeManager::log_time(struct tm time_info) const
|
||||||
|
{
|
||||||
|
char strftime_buf[64]; // Time string buffer
|
||||||
|
strftime(strftime_buf, sizeof(strftime_buf), "%c", &time_info);
|
||||||
|
logger.log_string(String(strftime_buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets up the NTP server config
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void TimeManager::_set_up_ntp(void)
|
||||||
|
{
|
||||||
|
if ((_tz != nullptr) && (_ntp_server != nullptr))
|
||||||
|
{
|
||||||
|
// set up NTP server and timezone at init
|
||||||
|
configTime(_tz, _ntp_server);
|
||||||
|
_tm_state = TM_INITIAL_SYNC;
|
||||||
|
logger.log_string(String("NTP configuration was set up successfully!"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_tm_state = TM_SETUP_FAILED;
|
||||||
|
logger.log_string(String("ERROR: Timezone and/or NTP-Server were not given correctly!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeManager::time_set_cb(void)
|
||||||
|
{
|
||||||
|
if ((*_is_wifi_connected)())
|
||||||
|
{
|
||||||
|
if (get_time() == TIME_UPDATE_OK)
|
||||||
|
{
|
||||||
|
_ntp_sync_timestamp_s = _now;
|
||||||
|
logger.log_string("NTP successfully synced at...");
|
||||||
|
log_time();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.log_string("NTP sync failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.log_string("NTP sync failed, WiFi is not connected!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
#include "udp_logger.h"
|
#include "udp_logger.h"
|
||||||
|
|
||||||
UDPLogger::UDPLogger()
|
UDPLogger::UDPLogger() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
UDPLogger::UDPLogger(IPAddress interface_addr, IPAddress multicast_addr, int port, String name)
|
UDPLogger::UDPLogger(IPAddress interface_addr, IPAddress multicast_addr, int port, String name)
|
||||||
{
|
{
|
||||||
@@ -26,8 +24,10 @@ void UDPLogger::log_string(String message)
|
|||||||
delay(10);
|
delay(10);
|
||||||
}
|
}
|
||||||
message = _name + ": " + message;
|
message = _name + ": " + message;
|
||||||
|
#ifdef SERIAL_DEBUG
|
||||||
Serial.println(message);
|
Serial.println(message);
|
||||||
delay(10);
|
delay(10);
|
||||||
|
#endif /* SERIAL_DEBUG */
|
||||||
_udp.beginPacketMulticast(_multicastAddr, _port, _interfaceAddr);
|
_udp.beginPacketMulticast(_multicastAddr, _port, _interfaceAddr);
|
||||||
message.toCharArray(_packetBuffer, 100);
|
message.toCharArray(_packetBuffer, 100);
|
||||||
_udp.print(_packetBuffer);
|
_udp.print(_packetBuffer);
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ extern LEDMatrix led_matrix;
|
|||||||
const int8_t dx[] = {1, -1, 0, 0};
|
const int8_t dx[] = {1, -1, 0, 0};
|
||||||
const int8_t dy[] = {0, 0, -1, 1};
|
const int8_t dy[] = {0, 0, -1, 1};
|
||||||
|
|
||||||
|
bool spiral_direction = false; // Direction of sprial animation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Function to draw a spiral step (from center)
|
* @brief Function to draw a spiral step (from center)
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const uint32_t colors_24bit[NUM_COLORS] = {
|
|||||||
* @param mybrightness the initial brightness of the leds
|
* @param mybrightness the initial brightness of the leds
|
||||||
* @param mylogger pointer to the UDPLogger object
|
* @param mylogger pointer to the UDPLogger object
|
||||||
*/
|
*/
|
||||||
LEDMatrix::LEDMatrix(Adafruit_NeoMatrix *matrix, uint8_t brightness, UDPLogger *logger)
|
LEDMatrix::LEDMatrix(FastLED_NeoMatrix *matrix, uint8_t brightness, UDPLogger *logger)
|
||||||
{
|
{
|
||||||
_neomatrix = matrix;
|
_neomatrix = matrix;
|
||||||
_brightness = brightness;
|
_brightness = brightness;
|
||||||
@@ -181,6 +181,11 @@ void LEDMatrix::flush(void)
|
|||||||
_target_minute_indicators[3] = 0;
|
_target_minute_indicators[3] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t LEDMatrix::get_fps(void)
|
||||||
|
{
|
||||||
|
return FastLED.getFPS();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Write target pixels directly to leds
|
* @brief Write target pixels directly to leds
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ String time_to_string(uint8_t hours, uint8_t minutes)
|
|||||||
switch (hours)
|
switch (hours)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
|
{
|
||||||
if (minutes >= 0 && minutes < 5)
|
if (minutes >= 0 && minutes < 5)
|
||||||
{
|
{
|
||||||
message += "MITTERNACHT ";
|
message += "MITTERNACHT ";
|
||||||
@@ -189,49 +190,69 @@ String time_to_string(uint8_t hours, uint8_t minutes)
|
|||||||
message += "ZWOLF ";
|
message += "ZWOLF ";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 1:
|
|
||||||
message += "EIN";
|
|
||||||
// EIN(S)
|
|
||||||
if (minutes > 4)
|
|
||||||
{
|
|
||||||
message += "S";
|
|
||||||
}
|
}
|
||||||
message += " ";
|
case 1:
|
||||||
|
{
|
||||||
|
message += "EIN";
|
||||||
|
message += (minutes > 4) ? "S " : " "; // add "S" if needed
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 2:
|
case 2:
|
||||||
|
{
|
||||||
message += "ZWEI ";
|
message += "ZWEI ";
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 3:
|
case 3:
|
||||||
|
{
|
||||||
message += "DREI ";
|
message += "DREI ";
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 4:
|
case 4:
|
||||||
|
{
|
||||||
message += "VIER ";
|
message += "VIER ";
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 5:
|
case 5:
|
||||||
|
{
|
||||||
message += "FUNF ";
|
message += "FUNF ";
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 6:
|
case 6:
|
||||||
|
{
|
||||||
message += "SECHS ";
|
message += "SECHS ";
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 7:
|
case 7:
|
||||||
|
{
|
||||||
message += "SIEBEN ";
|
message += "SIEBEN ";
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 8:
|
case 8:
|
||||||
|
{
|
||||||
message += "ACHT ";
|
message += "ACHT ";
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 9:
|
case 9:
|
||||||
|
{
|
||||||
message += "NEUN ";
|
message += "NEUN ";
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 10:
|
case 10:
|
||||||
|
{
|
||||||
message += "ZEHN ";
|
message += "ZEHN ";
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 11:
|
case 11:
|
||||||
|
{
|
||||||
message += "ELF ";
|
message += "ELF ";
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 12:
|
case 12:
|
||||||
|
{
|
||||||
message += "ZWOLF ";
|
message += "ZWOLF ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if ((minutes < 5) && (hours != 0))
|
if ((minutes < 5) && (hours != 0))
|
||||||
{
|
{
|
||||||
message += "UHR ";
|
message += "UHR ";
|
||||||
|
|||||||
@@ -24,17 +24,16 @@
|
|||||||
#include "wordclock_esp8266.h"
|
#include "wordclock_esp8266.h"
|
||||||
|
|
||||||
#include <Adafruit_GFX.h> // https://github.com/adafruit/Adafruit-GFX-Library
|
#include <Adafruit_GFX.h> // https://github.com/adafruit/Adafruit-GFX-Library
|
||||||
#include <Adafruit_NeoMatrix.h> // https://github.com/adafruit/Adafruit_NeoMatrix
|
#include <base64.hpp>
|
||||||
#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 <EEPROM.h> //from ESP8266 Arduino Core (automatically installed when ESP8266 was installed via Boardmanager)
|
|
||||||
#include <ESP8266WebServer.h>
|
#include <ESP8266WebServer.h>
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
#include <time.h>
|
#include <FastLED.h>
|
||||||
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager WiFi Configuration Magic
|
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager WiFi Configuration Magic
|
||||||
|
|
||||||
// own libraries
|
// own libraries
|
||||||
#include "animation_functions.h"
|
#include "animation_functions.h"
|
||||||
#include "base64_wrapper.h" // copied from https://github.com/Xander-Electronics/Base64
|
#include "diagnosis.h"
|
||||||
#include "led_matrix.h"
|
#include "led_matrix.h"
|
||||||
#include "littlefs_wrapper.h"
|
#include "littlefs_wrapper.h"
|
||||||
#include "ota_functions.h"
|
#include "ota_functions.h"
|
||||||
@@ -42,55 +41,53 @@
|
|||||||
#include "render_functions.h"
|
#include "render_functions.h"
|
||||||
#include "snake.h"
|
#include "snake.h"
|
||||||
#include "tetris.h"
|
#include "tetris.h"
|
||||||
|
#include "time_manager.h"
|
||||||
#include "udp_logger.h"
|
#include "udp_logger.h"
|
||||||
#include "wordclock_constants.h"
|
#include "wordclock_constants.h"
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
// GLOBAL VARIABLES
|
// GLOBAL VARIABLES
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
UDPLogger logger; // Logger
|
UDPLogger logger; // Global UDP logger instance
|
||||||
Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(MATRIX_WIDTH, MATRIX_HEIGHT + 1, NEOPIXEL_PIN,
|
|
||||||
NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_ROWS + NEO_MATRIX_ZIGZAG,
|
CRGB leds[NUM_MATRIX]; // LED array for FastLED
|
||||||
NEO_GRB + NEO_KHZ800); // NeoMatrix
|
FastLED_NeoMatrix matrix = FastLED_NeoMatrix(leds, MATRIX_WIDTH, (MATRIX_HEIGHT + 1),
|
||||||
char strftime_buf[64]; // Time string buffer
|
NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_ROWS + NEO_MATRIX_ZIGZAG);
|
||||||
|
LEDMatrix led_matrix = LEDMatrix(&matrix, DEFAULT_BRIGHTNESS, &logger); // FastLED_NeoMatrix wrapper
|
||||||
ESP8266WebServer webserver(HTTP_PORT); // Webserver
|
ESP8266WebServer webserver(HTTP_PORT); // Webserver
|
||||||
LEDMatrix led_matrix = LEDMatrix(&matrix, DEFAULT_BRIGHTNESS, &logger); // NeoMatrix wrapper
|
|
||||||
struct tm timeinfo; // Structure tm holds time information
|
|
||||||
time_t now; // Seconds since Epoch (1970) - UTC
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
// STATIC VARIABLES
|
// STATIC VARIABLES
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
|
// EEPROM values
|
||||||
static EepromLayout_st eeprom_buffer = {{0, 0, 0, 0}, {0U, 0U, 0U, false}, {0U, 0U, 0U, 0U}};
|
static EepromLayout_st eeprom_buffer = {{0, 0, 0, 0}, {0U, 0U, 0U, false}, {0U, 0U, 0U, 0U}};
|
||||||
static Brightness_st *brightness_ps = &eeprom_buffer.brightness_values;
|
static Brightness_st *const brightness_ps = &eeprom_buffer.brightness_values;
|
||||||
static Color_st *colors_ps = &eeprom_buffer.color_values;
|
static Color_st *const colors_ps = &eeprom_buffer.color_values;
|
||||||
static NightModeTimes_st *night_mode_times_ps = &eeprom_buffer.night_mode_times;
|
static NightModeTimes_st *const night_mode_times_ps = &eeprom_buffer.night_mode_times;
|
||||||
|
|
||||||
|
// Games
|
||||||
static Pong pong = Pong(&led_matrix, &logger);
|
static Pong pong = Pong(&led_matrix, &logger);
|
||||||
static Snake snake = Snake(&led_matrix, &logger);
|
static Snake snake = Snake(&led_matrix, &logger);
|
||||||
static Tetris tetris = Tetris(&led_matrix, &logger);
|
static Tetris tetris = Tetris(&led_matrix, &logger);
|
||||||
|
|
||||||
static uint32 last_ntp_update_us = 0; // time of last NTP update
|
// Time Manager
|
||||||
|
static TimeManager tm_mgr;
|
||||||
|
|
||||||
static bool flg_night_mode = false; // state of nightmode
|
// State variables
|
||||||
static bool flg_reset_wifi_creds = false; // used to reset stored wifi credentials
|
static bool flg_night_mode = false; // State of nightmode
|
||||||
static bool spiral_direction = false;
|
static bool flg_reset_wifi_creds = false; // Used to reset stored wifi credentials
|
||||||
static float filter_factor = DEFAULT_SMOOTHING_FACTOR; // stores smoothing factor for led transition
|
static float filter_factor = DEFAULT_SMOOTHING_FACTOR; // Stores smoothing factor for led transition, value of 1 represents no smoothing.
|
||||||
static int watchdog_counter = 30; // Watchdog counter to trigger restart if NTP update was not possible 30 times in a row (5min)
|
static uint32_t main_color_clock = colors_24bit[2]; // Color of the clock and digital clock
|
||||||
static uint32 last_led_direct_us = 0; // time of last direct LED command (=> fall back to normal mode after timeout)
|
static uint8_t current_brightness = DEFAULT_BRIGHTNESS; // Current brightness of LEDs
|
||||||
static uint32_t heartbeat_counter = 0; // for heartbeat on-time in seconds
|
static ClockState_en current_state = ST_CLOCK; // Stores current state
|
||||||
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_brightness = DEFAULT_BRIGHTNESS; // current brightness of LEDs
|
|
||||||
static uint8_t current_state = (uint8_t)ST_CLOCK; // stores current state
|
|
||||||
|
|
||||||
static const String state_names[NUM_STATES] = {"Clock", "DiClock", "Spiral", "Tetris", "Snake", "PingPong", "Hearts"};
|
// Other variables
|
||||||
static const uint32_t period_timings[NUM_STATES] = {PERIOD_CLOCK_UPDATE_US, PERIOD_CLOCK_UPDATE_US,
|
static uint32 last_led_direct_us = 0; // Time of last direct LED command (=> fall back to normal mode after timeout)
|
||||||
PERIOD_ANIMATION_US, PERIOD_TETRIS_US, PERIOD_SNAKE_US,
|
static uint32_t heartbeat_counter = 0; // Heartbeat on-time in seconds
|
||||||
PERIOD_PONG_US, PERIOD_ANIMATION_US};
|
|
||||||
|
|
||||||
// Quarterly brightness factor for dynamic brightness (4 quarters a 24 hours)
|
// Const definitions
|
||||||
static const float qtly_brightness_factor[96] = {
|
static const String state_names[NUM_STATES] = {"Clock", "DiClock", "Spiral", "Tetris", "Snake", "PingPong", "Hearts"}; // all clock states
|
||||||
|
static const float qtly_brightness_factor[96] = { // Quarterly brightness factor for dynamic brightness (4 quarters a 24 hours)
|
||||||
0.0f, 0.0f, 0.0f, 0.001f, 0.003f, 0.007f, 0.014f, 0.026f, 0.044f, 0.069f, 0.101f, 0.143f, 0.194f, 0.253f, 0.32f,
|
0.0f, 0.0f, 0.0f, 0.001f, 0.003f, 0.007f, 0.014f, 0.026f, 0.044f, 0.069f, 0.101f, 0.143f, 0.194f, 0.253f, 0.32f,
|
||||||
0.392f, 0.468f, 0.545f, 0.62f, 0.691f, 0.755f, 0.811f, 0.858f, 0.896f, 0.927f, 0.949f, 0.966f, 0.978f, 0.986f,
|
0.392f, 0.468f, 0.545f, 0.62f, 0.691f, 0.755f, 0.811f, 0.858f, 0.896f, 0.927f, 0.949f, 0.966f, 0.978f, 0.986f,
|
||||||
0.991f, 0.995f, 0.997f, 0.998f, 0.999f, 0.999f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
0.991f, 0.995f, 0.997f, 0.998f, 0.999f, 0.999f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
|
||||||
@@ -98,6 +95,9 @@ static const float qtly_brightness_factor[96] = {
|
|||||||
0.998f, 0.997f, 0.995f, 0.991f, 0.986f, 0.978f, 0.966f, 0.949f, 0.927f, 0.896f, 0.858f, 0.811f, 0.755f, 0.691f,
|
0.998f, 0.997f, 0.995f, 0.991f, 0.986f, 0.978f, 0.966f, 0.949f, 0.927f, 0.896f, 0.858f, 0.811f, 0.755f, 0.691f,
|
||||||
0.62f, 0.545f, 0.468f, 0.392f, 0.32f, 0.253f, 0.194f, 0.143f, 0.101f, 0.069f, 0.044f, 0.026f, 0.014f, 0.007f,
|
0.62f, 0.545f, 0.468f, 0.392f, 0.32f, 0.253f, 0.194f, 0.143f, 0.101f, 0.069f, 0.044f, 0.026f, 0.014f, 0.007f,
|
||||||
0.003f, 0.001f, 0.0f, 0.0f};
|
0.003f, 0.001f, 0.0f, 0.0f};
|
||||||
|
static const uint32_t period_timings[NUM_STATES] = {PERIOD_TIME_UPDATE_US, PERIOD_TIME_UPDATE_US,
|
||||||
|
PERIOD_ANIMATION_US, PERIOD_TETRIS_US, PERIOD_SNAKE_US,
|
||||||
|
PERIOD_PONG_US, PERIOD_ANIMATION_US};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------------
|
||||||
// SETUP
|
// SETUP
|
||||||
@@ -112,12 +112,15 @@ void setup()
|
|||||||
Serial.println();
|
Serial.println();
|
||||||
|
|
||||||
// Reset info
|
// Reset info
|
||||||
rst_info *resetInfo = ESP.getResetInfoPtr();
|
rst_info *reset_info = ESP.getResetInfoPtr();
|
||||||
Serial.printf("Reset reason: %u\n", resetInfo->reason);
|
Serial.printf("Reset reason: %u\n", reset_info->reason);
|
||||||
Serial.printf("Reset cause: %u\n", resetInfo->exccause);
|
Serial.printf("Reset cause: %u\n", reset_info->exccause);
|
||||||
Serial.printf("Reset address: %u\n", resetInfo->excvaddr);
|
Serial.printf("Reset address: %u\n", reset_info->excvaddr);
|
||||||
Serial.println();
|
Serial.println();
|
||||||
|
|
||||||
|
// Init FastLED
|
||||||
|
FastLED.addLeds<WS2812B, FASTLED_PIN, COLOR_ORDER>(leds, NUM_MATRIX);
|
||||||
|
|
||||||
// Init EEPROM
|
// Init EEPROM
|
||||||
EEPROM.begin(EEPROM_SIZE);
|
EEPROM.begin(EEPROM_SIZE);
|
||||||
|
|
||||||
@@ -134,8 +137,8 @@ void setup()
|
|||||||
led_matrix.setup_matrix();
|
led_matrix.setup_matrix();
|
||||||
led_matrix.set_current_limit(CURRENT_LIMIT_LED);
|
led_matrix.set_current_limit(CURRENT_LIMIT_LED);
|
||||||
|
|
||||||
// Turn on minutes leds (blue)
|
// Turn on minutes LEDs (blue)
|
||||||
led_matrix.set_min_indicator(15, colors_24bit[6]);
|
led_matrix.set_min_indicator((uint8_t)0b1111, colors_24bit[6]);
|
||||||
led_matrix.draw_on_matrix_instant();
|
led_matrix.draw_on_matrix_instant();
|
||||||
|
|
||||||
/* Use WiFiMaanger for handling initial Wifi setup */
|
/* Use WiFiMaanger for handling initial Wifi setup */
|
||||||
@@ -148,39 +151,162 @@ void setup()
|
|||||||
// If you get here you have connected to the WiFi
|
// If you get here you have connected to the WiFi
|
||||||
Serial.printf("Connected, IP address: ");
|
Serial.printf("Connected, IP address: ");
|
||||||
Serial.println(WiFi.localIP());
|
Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
// ESP8266 tries to reconnect automatically when the connection is lost
|
// ESP8266 tries to reconnect automatically when the connection is lost
|
||||||
WiFi.setAutoReconnect(true);
|
WiFi.setAutoReconnect(true);
|
||||||
WiFi.persistent(true);
|
WiFi.persistent(true);
|
||||||
|
|
||||||
// Turn off minutes leds
|
// Turn off minutes LEDs
|
||||||
led_matrix.set_min_indicator(15, 0);
|
led_matrix.set_min_indicator((uint8_t)0b1111, 0);
|
||||||
led_matrix.draw_on_matrix_instant();
|
led_matrix.draw_on_matrix_instant();
|
||||||
|
|
||||||
// init ESP8266 File manager (LittleFS)
|
// init ESP8266 File manager (LittleFS)
|
||||||
setup_filesystem();
|
setup_filesystem();
|
||||||
|
|
||||||
// setup OTA
|
// set up OTA
|
||||||
setupOTA(HOSTNAME);
|
setupOTA(HOSTNAME);
|
||||||
|
|
||||||
webserver.on("/cmd", handle_command); // process commands
|
webserver.on("/cmd", handle_command); // process commands
|
||||||
webserver.on("/data", handle_data_request); // process data requests
|
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.on("/leddirect", HTTP_POST, handle_led_direct); // call the 'handle_led_direct' function when a POST request is made to URI "/leddirect"
|
||||||
webserver.begin();
|
webserver.begin();
|
||||||
|
|
||||||
// create UDP Logger to send logging messages via UDP multicast
|
// create UDP Logger to send logging messages via UDP multicast
|
||||||
logger = UDPLogger(WiFi.localIP(), LOGGER_MULTICAST_IP, LOGGER_MULTICAST_PORT, "Wordclock 2.0");
|
logger = UDPLogger(WiFi.localIP(), LOGGER_MULTICAST_IP, LOGGER_MULTICAST_PORT, "Wordclock 2.0");
|
||||||
|
|
||||||
|
if (reset_info->reason != REASON_SOFT_RESTART) // only if there was a cold start/hard reset
|
||||||
|
{
|
||||||
|
cold_start_setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up time manager and get initial time
|
||||||
|
tm_mgr = TimeManager(MY_TZ, NTP_SERVER_URL, check_wifi_status, NTP_MAX_OFFLINE_TIME_S, &logger);
|
||||||
|
tm_mgr.init();
|
||||||
|
|
||||||
|
if (tm_mgr.get_time() == TIME_UPDATE_OK)
|
||||||
|
{
|
||||||
|
// show the current time for short time in words
|
||||||
|
String timeMessage = time_to_string(tm_mgr.hour(), tm_mgr.minute());
|
||||||
|
show_string_on_clock(timeMessage, main_color_clock);
|
||||||
|
draw_minute_indicator(tm_mgr.minute(), main_color_clock);
|
||||||
|
led_matrix.draw_on_matrix_smooth(filter_factor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.log_string("Warning: Initial time sync failed! Retrying in a bit.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// init all animation modes
|
||||||
|
random_snake(true, 8, colors_24bit[1], -1);
|
||||||
|
draw_spiral(true, spiral_direction, MATRIX_WIDTH - 6);
|
||||||
|
random_tetris(true);
|
||||||
|
|
||||||
|
// Set range limits
|
||||||
|
limit_value_ranges();
|
||||||
|
|
||||||
|
// Update brightness
|
||||||
|
current_brightness = update_brightness();
|
||||||
|
|
||||||
|
// Send logging data
|
||||||
|
log_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
// LOOP
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
uint32 current_time_us = system_get_time();
|
||||||
|
|
||||||
|
// Timestamp variables
|
||||||
|
static uint32 last_animation_step_us = 0; // timestamp of last animation step
|
||||||
|
static uint32 last_matrix_update_us = 0; // timestamp of last Matrix update
|
||||||
|
static uint32 last_time_update_us = 0; // timestamp of last time update
|
||||||
|
static uint32 last_heartbeat_us = 0; // timestamp of last heartbeat sending
|
||||||
|
static uint32 last_nightmode_check_us = 0; // timestamp of last nightmode check
|
||||||
|
static uint32 last_brightness_update_us = 0; // timestamp of last brightness update
|
||||||
|
|
||||||
|
handleOTA(); // handle OTA
|
||||||
|
|
||||||
|
webserver.handleClient(); // handle webserver
|
||||||
|
|
||||||
|
handle_button(); // handle button press
|
||||||
|
|
||||||
|
// send regularly heartbeat messages via UDP multicast
|
||||||
|
if ((current_time_us - last_heartbeat_us) >= PERIOD_HEARTBEAT_US)
|
||||||
|
{
|
||||||
|
send_heartbeat(); // send heartbeat update
|
||||||
|
last_heartbeat_us = system_get_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!flg_night_mode && ((current_time_us - last_animation_step_us) > period_timings[current_state]) &&
|
||||||
|
((current_time_us - last_led_direct_us) >= TIMEOUT_LEDDIRECT_US))
|
||||||
|
{
|
||||||
|
handle_current_state(); // handle current state
|
||||||
|
last_animation_step_us = system_get_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((current_time_us - last_brightness_update_us) >= PERIOD_BRIGHTNESS_UPDATE_US)
|
||||||
|
{
|
||||||
|
current_brightness = update_brightness(); // update brightness
|
||||||
|
logger.log_string("Brightness: " + String(((uint16_t)current_brightness * 100) / UINT8_MAX) + "%");
|
||||||
|
last_brightness_update_us = system_get_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((current_time_us - last_matrix_update_us) >= PERIOD_MATRIX_UPDATE_US)
|
||||||
|
{
|
||||||
|
update_matrix(); // update matrix
|
||||||
|
last_matrix_update_us = system_get_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((current_time_us - last_time_update_us) >= PERIOD_TIME_UPDATE_US)
|
||||||
|
{
|
||||||
|
(void)tm_mgr.get_time(); // NTP time update
|
||||||
|
|
||||||
|
if (tm_mgr.ntp_sync_timeout())
|
||||||
|
{
|
||||||
|
logger.log_string("Trigger restart due to being offline for too long...");
|
||||||
|
delay(100);
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
last_time_update_us = system_get_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((current_time_us - last_nightmode_check_us) >= PERIOD_NIGHTMODE_CHECK_US)
|
||||||
|
{
|
||||||
|
check_night_mode(); // check night mode
|
||||||
|
last_nightmode_check_us = system_get_time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
// OTHER FUNCTIONS
|
||||||
|
// ----------------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @brief Log information
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void log_data()
|
||||||
|
{
|
||||||
logger.log_string("Start program\n");
|
logger.log_string("Start program\n");
|
||||||
delay(10);
|
|
||||||
logger.log_string("Sketchname: " + String(__FILE__));
|
logger.log_string("Sketchname: " + String(__FILE__));
|
||||||
delay(10);
|
|
||||||
logger.log_string("Build: " + String(__TIMESTAMP__));
|
logger.log_string("Build: " + String(__TIMESTAMP__));
|
||||||
delay(10);
|
|
||||||
logger.log_string("IP: " + WiFi.localIP().toString());
|
logger.log_string("IP: " + WiFi.localIP().toString());
|
||||||
delay(10);
|
|
||||||
logger.log_string("Reset Reason: " + ESP.getResetReason());
|
logger.log_string("Reset Reason: " + ESP.getResetReason());
|
||||||
|
|
||||||
if (resetInfo->reason != REASON_SOFT_RESTART)
|
logger.log_string("Nightmode starts at: " + String(night_mode_times_ps->start_hour) + ":" + String(night_mode_times_ps->start_min));
|
||||||
{
|
logger.log_string("Nightmode ends at: " + String(night_mode_times_ps->end_hour) + ":" + String(night_mode_times_ps->end_min));
|
||||||
|
|
||||||
|
logger.log_string("Brightness: " + String(((uint16_t)current_brightness * 100) / UINT8_MAX) + "%\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Test all LEDs and display IP address
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void cold_start_setup()
|
||||||
|
{
|
||||||
// quickly test each LED
|
// quickly test each LED
|
||||||
for (int16_t row = 0; row < MATRIX_HEIGHT; row++)
|
for (int16_t row = 0; row < MATRIX_HEIGHT; row++)
|
||||||
{
|
{
|
||||||
@@ -211,149 +337,51 @@ void setup()
|
|||||||
// clear matrix
|
// clear matrix
|
||||||
led_matrix.flush();
|
led_matrix.flush();
|
||||||
led_matrix.draw_on_matrix_instant();
|
led_matrix.draw_on_matrix_instant();
|
||||||
}
|
|
||||||
|
|
||||||
// setup NTP
|
|
||||||
configTime(MY_TZ, NTP_SERVER_URL);
|
|
||||||
ntp_time_update(&last_ntp_update_us); // NTP time update
|
|
||||||
|
|
||||||
// show the current time for short time in words
|
|
||||||
String timeMessage = time_to_string(timeinfo.tm_hour, timeinfo.tm_min);
|
|
||||||
show_string_on_clock(timeMessage, main_color_clock);
|
|
||||||
draw_minute_indicator(timeinfo.tm_min, 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);
|
|
||||||
|
|
||||||
// Set range limits
|
|
||||||
limit_value_ranges();
|
|
||||||
|
|
||||||
logger.log_string("Nightmode starts at: " + String(night_mode_times_ps->start_hour) + ":" + String(night_mode_times_ps->start_min));
|
|
||||||
logger.log_string("Nightmode ends at: " + String(night_mode_times_ps->end_hour) + ":" + String(night_mode_times_ps->end_min));
|
|
||||||
|
|
||||||
// Update brightness
|
|
||||||
current_brightness = update_brightness();
|
|
||||||
logger.log_string("Brightness: " + String(((uint16_t)current_brightness * 100) / UINT8_MAX) + "%");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------
|
|
||||||
// LOOP
|
|
||||||
// ----------------------------------------------------------------------------------
|
|
||||||
void loop()
|
|
||||||
{
|
|
||||||
uint32 current_time_us = system_get_time();
|
|
||||||
|
|
||||||
// Timestamp variables
|
|
||||||
static uint32 last_animation_step_us = 0; // time of last animation step
|
|
||||||
static uint32 last_matrix_update_us = 0; // time of last Matrix update
|
|
||||||
static uint32 last_heartbeat_us = 0; // time of last heartbeat sending
|
|
||||||
static uint32 last_nightmode_check_us = 0; // time of last nightmode check
|
|
||||||
|
|
||||||
handleOTA(); // handle OTA
|
|
||||||
|
|
||||||
webserver.handleClient(); // handle webserver
|
|
||||||
|
|
||||||
// send regularly heartbeat messages via UDP multicast
|
|
||||||
if ((current_time_us - last_heartbeat_us) > PERIOD_HEARTBEAT_US)
|
|
||||||
{
|
|
||||||
send_heartbeat(); // send heartbeat update
|
|
||||||
last_heartbeat_us = system_get_time();
|
|
||||||
delay(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!flg_night_mode && ((current_time_us - last_animation_step_us) > period_timings[current_state]) &&
|
|
||||||
((current_time_us - last_led_direct_us) > TIMEOUT_LEDDIRECT_US))
|
|
||||||
{
|
|
||||||
handle_current_state(); // handle current state
|
|
||||||
last_animation_step_us = system_get_time();
|
|
||||||
delay(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((current_time_us - last_matrix_update_us) > PERIOD_MATRIX_UPDATE_US)
|
|
||||||
{
|
|
||||||
update_matrix(); // update matrix
|
|
||||||
last_matrix_update_us = system_get_time();
|
|
||||||
delay(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_button(); // handle button press
|
|
||||||
|
|
||||||
if ((current_time_us - last_ntp_update_us) > PERIOD_NTP_UPDATE_US)
|
|
||||||
{
|
|
||||||
check_wifi_status(); // check WiFi status before NTP update
|
|
||||||
delay(10);
|
|
||||||
|
|
||||||
ntp_time_update(&last_ntp_update_us); // NTP time update
|
|
||||||
delay(10);
|
|
||||||
|
|
||||||
current_brightness = update_brightness(); // update brightness every PERIOD_NTP_UPDATE_US
|
|
||||||
delay(10);
|
|
||||||
|
|
||||||
logger.log_string("Brightness: " + String(((uint16_t)current_brightness * 100) / UINT8_MAX) + "%");
|
|
||||||
delay(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((current_time_us - last_nightmode_check_us) > PERIOD_NIGHTMODE_CHECK_US)
|
|
||||||
{
|
|
||||||
check_night_mode(); // check night mode
|
|
||||||
last_nightmode_check_us = system_get_time();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------------
|
|
||||||
// OTHER FUNCTIONS
|
|
||||||
// ----------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Updates the NTP time
|
* @brief Update and control word clock states.
|
||||||
*
|
|
||||||
* @return boolean - true if NTP update was successful, false otherwise
|
|
||||||
*/
|
*/
|
||||||
bool get_ntp_time(uint32 sec)
|
|
||||||
{
|
|
||||||
uint32 start = system_get_time() / 1000; // ms
|
|
||||||
do
|
|
||||||
{
|
|
||||||
time(&now);
|
|
||||||
localtime_r(&now, &timeinfo);
|
|
||||||
delay(10);
|
|
||||||
} while (((system_get_time() / 1000 - start) <= (1000 * sec)) && (timeinfo.tm_year < (2023 - 1900)));
|
|
||||||
|
|
||||||
if (timeinfo.tm_year <= (2023 - 1900))
|
|
||||||
{
|
|
||||||
return false; // the NTP call was not successful
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void log_time(tm local_time)
|
|
||||||
{
|
|
||||||
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
|
|
||||||
logger.log_string(String(strftime_buf));
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_current_state()
|
void handle_current_state()
|
||||||
{
|
{
|
||||||
switch (current_state)
|
switch (current_state)
|
||||||
{
|
{
|
||||||
case ST_CLOCK: // state clock
|
case ST_CLOCK: // state clock
|
||||||
{
|
{
|
||||||
(void)show_string_on_clock(time_to_string((uint8_t)timeinfo.tm_hour, (uint8_t)timeinfo.tm_min), main_color_clock);
|
if (tm_mgr.tm_state() == TM_NORMAL)
|
||||||
draw_minute_indicator((uint8_t)timeinfo.tm_min, main_color_clock);
|
{
|
||||||
|
(void)show_string_on_clock(time_to_string((uint8_t)tm_mgr.hour(), (uint8_t)tm_mgr.minute()), main_color_clock);
|
||||||
|
draw_minute_indicator((uint8_t)tm_mgr.minute(), main_color_clock);
|
||||||
|
}
|
||||||
|
else if (tm_mgr.ntp_sync_overdue()) // if NTP sync is overdue
|
||||||
|
{
|
||||||
|
(void)show_string_on_clock(time_to_string((uint8_t)tm_mgr.hour(), (uint8_t)tm_mgr.minute()), main_color_clock);
|
||||||
|
draw_minute_indicator((uint8_t)tm_mgr.minute(), colors_24bit[6]); // in blue to indicate a network problem
|
||||||
|
}
|
||||||
|
else // if no NTP sync has been done, only show 4 blue minute indicators
|
||||||
|
{
|
||||||
|
// clear matrix
|
||||||
|
led_matrix.flush();
|
||||||
|
// Turn on minutes LEDs (blue)
|
||||||
|
led_matrix.set_min_indicator((uint8_t)0b1111, colors_24bit[6]);
|
||||||
|
led_matrix.draw_on_matrix_instant();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ST_DICLOCK: // state diclock
|
case ST_DICLOCK: // state diclock
|
||||||
{
|
{
|
||||||
show_digital_clock((uint8_t)timeinfo.tm_hour, (uint8_t)timeinfo.tm_min, main_color_clock);
|
if (tm_mgr.ntp_sync_successful())
|
||||||
|
{
|
||||||
|
show_digital_clock((uint8_t)tm_mgr.hour(), (uint8_t)tm_mgr.minute(), main_color_clock);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// clear matrix
|
||||||
|
led_matrix.flush();
|
||||||
|
// Turn on minutes LEDs (blue)
|
||||||
|
led_matrix.set_min_indicator((uint8_t)0b1111, colors_24bit[6]);
|
||||||
|
led_matrix.draw_on_matrix_instant();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ST_SPIRAL: // state spiral
|
case ST_SPIRAL: // state spiral
|
||||||
@@ -361,14 +389,14 @@ void handle_current_state()
|
|||||||
int res = draw_spiral(false, spiral_direction, MATRIX_WIDTH - 2);
|
int res = draw_spiral(false, spiral_direction, MATRIX_WIDTH - 2);
|
||||||
if ((bool)res && spiral_direction == 0)
|
if ((bool)res && spiral_direction == 0)
|
||||||
{
|
{
|
||||||
// change spiral direction to closing (draw empty leds)
|
// change spiral direction to closing (draw empty LEDs)
|
||||||
spiral_direction = true;
|
spiral_direction = true;
|
||||||
// init spiral with new spiral direction
|
// init spiral with new spiral direction
|
||||||
draw_spiral(true, spiral_direction, MATRIX_WIDTH - 1);
|
draw_spiral(true, spiral_direction, MATRIX_WIDTH - 1);
|
||||||
}
|
}
|
||||||
else if (res && spiral_direction == 1)
|
else if (res && spiral_direction == 1)
|
||||||
{
|
{
|
||||||
// reset spiral direction to normal drawing leds
|
// reset spiral direction to normal drawing LEDs
|
||||||
spiral_direction = false;
|
spiral_direction = false;
|
||||||
// init spiral with new spiral direction
|
// init spiral with new spiral direction
|
||||||
draw_spiral(true, spiral_direction, MATRIX_WIDTH - 1);
|
draw_spiral(true, spiral_direction, MATRIX_WIDTH - 1);
|
||||||
@@ -429,14 +457,18 @@ void send_heartbeat()
|
|||||||
* @brief Check WiFi status and try to reconnect if needed. Should be called in loop() before NTP update.
|
* @brief Check WiFi status and try to reconnect if needed. Should be called in loop() before NTP update.
|
||||||
*
|
*
|
||||||
* @param None
|
* @param None
|
||||||
|
*
|
||||||
|
* @retval bool - true if WiFi is connected, false otherwise
|
||||||
*/
|
*/
|
||||||
void check_wifi_status()
|
bool check_wifi_status()
|
||||||
{
|
{
|
||||||
|
bool connected = (WiFi.status() == WL_CONNECTED);
|
||||||
// Check wifi status
|
// Check wifi status
|
||||||
if (WiFi.status() != WL_CONNECTED)
|
if (!connected)
|
||||||
{
|
{
|
||||||
Serial.println("WiFi connection lost! Trying to reconnect automatically...");
|
Serial.println("WiFi connection lost! Trying to reconnect automatically...");
|
||||||
}
|
}
|
||||||
|
return connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -446,9 +478,9 @@ void check_wifi_status()
|
|||||||
*/
|
*/
|
||||||
void check_night_mode()
|
void check_night_mode()
|
||||||
{
|
{
|
||||||
// check if nightmode need to be activated
|
// Check if nightmode needs to be activated. This only toggles at the exact minute.
|
||||||
int hours = timeinfo.tm_hour;
|
int hours = tm_mgr.hour();
|
||||||
int minutes = timeinfo.tm_min;
|
int minutes = tm_mgr.minute();
|
||||||
|
|
||||||
if ((hours == night_mode_times_ps->start_hour) && (minutes == night_mode_times_ps->start_min))
|
if ((hours == night_mode_times_ps->start_hour) && (minutes == night_mode_times_ps->start_min))
|
||||||
{
|
{
|
||||||
@@ -460,38 +492,6 @@ void check_night_mode()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief NTP time update, should be called in loop().
|
|
||||||
*
|
|
||||||
* @param None
|
|
||||||
*/
|
|
||||||
void ntp_time_update(uint32 *last_ntp_update_us)
|
|
||||||
{
|
|
||||||
// NTP time update
|
|
||||||
bool ntp_retval = get_ntp_time(*last_ntp_update_us / 10000000);
|
|
||||||
|
|
||||||
if (ntp_retval == true)
|
|
||||||
{
|
|
||||||
log_time(timeinfo);
|
|
||||||
*last_ntp_update_us = system_get_time();
|
|
||||||
watchdog_counter = 30;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.log_string("NTP-Update was not successful.");
|
|
||||||
*last_ntp_update_us += 10000000;
|
|
||||||
watchdog_counter--;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
* @brief call entry action of given state
|
||||||
*
|
*
|
||||||
@@ -499,7 +499,7 @@ void ntp_time_update(uint32 *last_ntp_update_us)
|
|||||||
*/
|
*/
|
||||||
void on_state_entry(uint8_t state)
|
void on_state_entry(uint8_t state)
|
||||||
{
|
{
|
||||||
filter_factor = 0.5f;
|
filter_factor = DEFAULT_SMOOTHING_FACTOR;
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case ST_SPIRAL:
|
case ST_SPIRAL:
|
||||||
@@ -534,11 +534,11 @@ void on_state_entry(uint8_t state)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief execute a state change to given newState
|
* @brief execute a state change to given new_state
|
||||||
*
|
*
|
||||||
* @param newState the new state to be changed to
|
* @param new_state the new state to be changed to
|
||||||
*/
|
*/
|
||||||
void state_change(uint8_t newState)
|
void state_change(ClockState_en new_state)
|
||||||
{
|
{
|
||||||
if (flg_night_mode)
|
if (flg_night_mode)
|
||||||
{
|
{
|
||||||
@@ -546,9 +546,9 @@ void state_change(uint8_t newState)
|
|||||||
}
|
}
|
||||||
|
|
||||||
led_matrix.flush(); // first clear matrix
|
led_matrix.flush(); // first clear matrix
|
||||||
current_state = newState; // set new state
|
current_state = new_state; // set new state
|
||||||
on_state_entry(current_state);
|
on_state_entry((uint8_t)current_state);
|
||||||
logger.log_string("State change to: " + state_names[current_state]);
|
logger.log_string("State change to: " + state_names[(uint8_t)current_state]);
|
||||||
logger.log_string("FreeMemory=" + String(ESP.getFreeHeap()));
|
logger.log_string("FreeMemory=" + String(ESP.getFreeHeap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,10 +577,9 @@ void handle_led_direct()
|
|||||||
// base64 decoding
|
// base64 decoding
|
||||||
char base64data[dataLength];
|
char base64data[dataLength];
|
||||||
data.toCharArray(base64data, dataLength);
|
data.toCharArray(base64data, dataLength);
|
||||||
int base64dataLen = (int)dataLength;
|
unsigned int decodedLength = decode_base64_length((unsigned char *)base64data, dataLength);
|
||||||
int decodedLength = Base64.decodedLength(base64data, base64dataLen);
|
unsigned char byteArray[decodedLength];
|
||||||
char byteArray[decodedLength];
|
decode_base64((unsigned char *)base64data, dataLength, byteArray);
|
||||||
Base64.decode(byteArray, base64data, base64dataLen);
|
|
||||||
|
|
||||||
for (unsigned int i = 0; i < dataLength; i += 4)
|
for (unsigned int i = 0; i < dataLength; i += 4)
|
||||||
{
|
{
|
||||||
@@ -641,7 +640,7 @@ void handle_button()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
state_change((current_state + 1) % (uint8_t)NUM_STATES);
|
state_change((ClockState_en)(((uint8_t)current_state + 1) % (uint8_t)NUM_STATES));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -656,11 +655,11 @@ void handle_button()
|
|||||||
void set_main_color(uint8_t red, uint8_t green, uint8_t blue)
|
void set_main_color(uint8_t red, uint8_t green, uint8_t blue)
|
||||||
{
|
{
|
||||||
main_color_clock = LEDMatrix::color_24bit(red, green, blue);
|
main_color_clock = LEDMatrix::color_24bit(red, green, blue);
|
||||||
|
|
||||||
|
// Update colors and save color settings to EEPROM
|
||||||
colors_ps->blue = blue;
|
colors_ps->blue = blue;
|
||||||
colors_ps->red = red;
|
colors_ps->red = red;
|
||||||
colors_ps->green = green;
|
colors_ps->green = green;
|
||||||
|
|
||||||
// save color settings to EEPROM
|
|
||||||
write_settings_to_EEPROM();
|
write_settings_to_EEPROM();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,13 +690,16 @@ void draw_main_color()
|
|||||||
*/
|
*/
|
||||||
void handle_command()
|
void handle_command()
|
||||||
{
|
{
|
||||||
|
bool send204 = true; // flag to send 204 response
|
||||||
// receive command and handle accordingly
|
// receive command and handle accordingly
|
||||||
|
#ifdef SERIAL_DEBUG
|
||||||
for (uint8_t i = 0; i < webserver.args(); i++)
|
for (uint8_t i = 0; i < webserver.args(); i++)
|
||||||
{
|
{
|
||||||
Serial.print(webserver.argName(i));
|
Serial.print(webserver.argName(i));
|
||||||
Serial.print(F(": "));
|
Serial.print(F(": "));
|
||||||
Serial.println(webserver.arg(i));
|
Serial.println(webserver.arg(i));
|
||||||
}
|
}
|
||||||
|
#endif /* SERIAL_DEBUG */
|
||||||
|
|
||||||
if (webserver.argName(0).equals("led")) // the parameter which was sent to this server is led color
|
if (webserver.argName(0).equals("led")) // the parameter which was sent to this server is led color
|
||||||
{
|
{
|
||||||
@@ -716,6 +718,7 @@ void handle_command()
|
|||||||
{
|
{
|
||||||
String mode_str = webserver.arg(0);
|
String mode_str = webserver.arg(0);
|
||||||
logger.log_string("Mode change via Webserver to: " + mode_str);
|
logger.log_string("Mode change via Webserver to: " + mode_str);
|
||||||
|
|
||||||
// set current mode/state accordant sent mode
|
// set current mode/state accordant sent mode
|
||||||
if (mode_str.equals("clock"))
|
if (mode_str.equals("clock"))
|
||||||
{
|
{
|
||||||
@@ -797,7 +800,6 @@ void handle_command()
|
|||||||
else if (webserver.argName(0).equals("tetris"))
|
else if (webserver.argName(0).equals("tetris"))
|
||||||
{
|
{
|
||||||
String cmd_str = webserver.arg(0);
|
String cmd_str = webserver.arg(0);
|
||||||
// logger.log_string("Tetris cmd via Webserver to: " + cmd_str);
|
|
||||||
if (cmd_str.equals("up"))
|
if (cmd_str.equals("up"))
|
||||||
{
|
{
|
||||||
tetris.ctrlUp();
|
tetris.ctrlUp();
|
||||||
@@ -826,7 +828,6 @@ void handle_command()
|
|||||||
else if (webserver.argName(0).equals("snake"))
|
else if (webserver.argName(0).equals("snake"))
|
||||||
{
|
{
|
||||||
String cmd_str = webserver.arg(0);
|
String cmd_str = webserver.arg(0);
|
||||||
// logger.log_string("Snake cmd via Webserver to: " + cmd_str);
|
|
||||||
if (cmd_str.equals("up"))
|
if (cmd_str.equals("up"))
|
||||||
{
|
{
|
||||||
snake.ctrlUp();
|
snake.ctrlUp();
|
||||||
@@ -851,7 +852,6 @@ void handle_command()
|
|||||||
else if (webserver.argName(0).equals("pong"))
|
else if (webserver.argName(0).equals("pong"))
|
||||||
{
|
{
|
||||||
String cmd_str = webserver.arg(0);
|
String cmd_str = webserver.arg(0);
|
||||||
// logger.log_string("Pong cmd via Webserver to: " + cmd_str);
|
|
||||||
if (cmd_str.equals("up"))
|
if (cmd_str.equals("up"))
|
||||||
{
|
{
|
||||||
pong.ctrlUp(1);
|
pong.ctrlUp(1);
|
||||||
@@ -865,8 +865,18 @@ void handle_command()
|
|||||||
pong.initGame(1);
|
pong.initGame(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (webserver.argName(0).equals("diag"))
|
||||||
|
{
|
||||||
|
String cmd_str = webserver.arg(0);
|
||||||
|
Diagnosis diag(&logger, &led_matrix);
|
||||||
|
webserver.send(200, "text/plain", diag.handle_command(cmd_str));
|
||||||
|
send204 = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (send204)
|
||||||
|
{
|
||||||
webserver.send(204, "text/plain", "No Content"); // this page doesn't send back content --> 204
|
webserver.send(204, "text/plain", "No Content"); // this page doesn't send back content --> 204
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -875,6 +885,7 @@ void handle_command()
|
|||||||
*/
|
*/
|
||||||
void handle_data_request()
|
void handle_data_request()
|
||||||
{
|
{
|
||||||
|
#ifdef SERIAL_DEBUG
|
||||||
// receive data request and handle accordingly
|
// receive data request and handle accordingly
|
||||||
for (uint8_t i = 0; i < webserver.args(); i++)
|
for (uint8_t i = 0; i < webserver.args(); i++)
|
||||||
{
|
{
|
||||||
@@ -882,7 +893,7 @@ void handle_data_request()
|
|||||||
Serial.print(F(": "));
|
Serial.print(F(": "));
|
||||||
Serial.println(webserver.arg(i));
|
Serial.println(webserver.arg(i));
|
||||||
}
|
}
|
||||||
|
#endif /* SERIAL_DEBUG */
|
||||||
if (webserver.argName(0).equals("key"))
|
if (webserver.argName(0).equals("key"))
|
||||||
{
|
{
|
||||||
String message = "{";
|
String message = "{";
|
||||||
@@ -1030,9 +1041,9 @@ uint8_t update_brightness()
|
|||||||
{
|
{
|
||||||
new_brightness = calculate_dynamic_brightness(brightness_ps->dyn_brightness_min,
|
new_brightness = calculate_dynamic_brightness(brightness_ps->dyn_brightness_min,
|
||||||
brightness_ps->dyn_brightness_max,
|
brightness_ps->dyn_brightness_max,
|
||||||
timeinfo.tm_hour,
|
tm_mgr.hour(),
|
||||||
timeinfo.tm_min,
|
tm_mgr.minute(),
|
||||||
timeinfo.tm_isdst);
|
tm_mgr.isdst());
|
||||||
}
|
}
|
||||||
else // use static brightness
|
else // use static brightness
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright (C) 2016 Arturo Guadalupi. All right reserved.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include "base64_wrapper.h"
|
|
||||||
#if (defined(__AVR__))
|
|
||||||
#include <avr/pgmspace.h>
|
|
||||||
#else
|
|
||||||
#include <pgmspace.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const char PROGMEM _Base64AlphabetTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
"abcdefghijklmnopqrstuvwxyz"
|
|
||||||
"0123456789+/";
|
|
||||||
|
|
||||||
int Base64Class::encode(char *output, char *input, int inputLength) {
|
|
||||||
int i = 0, j = 0;
|
|
||||||
int encodedLength = 0;
|
|
||||||
unsigned char A3[3];
|
|
||||||
unsigned char A4[4];
|
|
||||||
|
|
||||||
while(inputLength--) {
|
|
||||||
A3[i++] = *(input++);
|
|
||||||
if(i == 3) {
|
|
||||||
fromA3ToA4(A4, A3);
|
|
||||||
|
|
||||||
for(i = 0; i < 4; i++) {
|
|
||||||
output[encodedLength++] = pgm_read_byte(&_Base64AlphabetTable[A4[i]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(i) {
|
|
||||||
for(j = i; j < 3; j++) {
|
|
||||||
A3[j] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
fromA3ToA4(A4, A3);
|
|
||||||
|
|
||||||
for(j = 0; j < i + 1; j++) {
|
|
||||||
output[encodedLength++] = pgm_read_byte(&_Base64AlphabetTable[A4[j]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
while((i++ < 3)) {
|
|
||||||
output[encodedLength++] = '=';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output[encodedLength] = '\0';
|
|
||||||
return encodedLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Base64Class::decode(char * output, char * input, int inputLength) {
|
|
||||||
int i = 0, j = 0;
|
|
||||||
int decodedLength = 0;
|
|
||||||
unsigned char A3[3];
|
|
||||||
unsigned char A4[4];
|
|
||||||
|
|
||||||
|
|
||||||
while (inputLength--) {
|
|
||||||
if(*input == '=') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
A4[i++] = *(input++);
|
|
||||||
if (i == 4) {
|
|
||||||
for (i = 0; i <4; i++) {
|
|
||||||
A4[i] = lookupTable(A4[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fromA4ToA3(A3,A4);
|
|
||||||
|
|
||||||
for (i = 0; i < 3; i++) {
|
|
||||||
output[decodedLength++] = A3[i];
|
|
||||||
}
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i) {
|
|
||||||
for (j = i; j < 4; j++) {
|
|
||||||
A4[j] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
for (j = 0; j <4; j++) {
|
|
||||||
A4[j] = lookupTable(A4[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
fromA4ToA3(A3,A4);
|
|
||||||
|
|
||||||
for (j = 0; j < i - 1; j++) {
|
|
||||||
output[decodedLength++] = A3[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output[decodedLength] = '\0';
|
|
||||||
return decodedLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Base64Class::encodedLength(int plainLength) {
|
|
||||||
int n = plainLength;
|
|
||||||
return (n + 2 - ((n + 2) % 3)) / 3 * 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Base64Class::decodedLength(char * input, int inputLength) {
|
|
||||||
int i = 0;
|
|
||||||
int numEq = 0;
|
|
||||||
for(i = inputLength - 1; input[i] == '='; i--) {
|
|
||||||
numEq++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ((6 * inputLength) / 8) - numEq;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Private utility functions
|
|
||||||
inline void Base64Class::fromA3ToA4(unsigned char * A4, unsigned char * A3) {
|
|
||||||
A4[0] = (A3[0] & 0xfc) >> 2;
|
|
||||||
A4[1] = ((A3[0] & 0x03) << 4) + ((A3[1] & 0xf0) >> 4);
|
|
||||||
A4[2] = ((A3[1] & 0x0f) << 2) + ((A3[2] & 0xc0) >> 6);
|
|
||||||
A4[3] = (A3[2] & 0x3f);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Base64Class::fromA4ToA3(unsigned char * A3, unsigned char * A4) {
|
|
||||||
A3[0] = (A4[0] << 2) + ((A4[1] & 0x30) >> 4);
|
|
||||||
A3[1] = ((A4[1] & 0xf) << 4) + ((A4[2] & 0x3c) >> 2);
|
|
||||||
A3[2] = ((A4[2] & 0x3) << 6) + A4[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline unsigned char Base64Class::lookupTable(char c) {
|
|
||||||
if(c >='A' && c <='Z') return c - 'A';
|
|
||||||
if(c >='a' && c <='z') return c - 71;
|
|
||||||
if(c >='0' && c <='9') return c + 4;
|
|
||||||
if(c == '+') return 62;
|
|
||||||
if(c == '/') return 63;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Base64Class Base64;
|
|
||||||
Reference in New Issue
Block a user