mirror of https://github.com/Enne2/Led-Monitor-2
Browse Source
- Created .gitignore to exclude build and configuration files. - Added .vscode/extensions.json for recommended extensions. - Introduced README files in include and lib directories to explain usage of header files and libraries. - Configured platformio.ini for ESP32 development environment. - Implemented credentials.h for WiFi configuration. - Developed text_display.cpp and text_display.h for displaying text on the LED matrix. - Created matrix_rain.cpp and matrix_rain.h for rain effect simulation on the matrix. - Added font3x5_digits.h and font5x7.h for character representation. - Implemented main.cpp to initialize the matrix and handle WiFi connections. - Developed time_utils.cpp and time_utils.h for time formatting functions. - Created wifi_manager.cpp and wifi_manager.h for managing WiFi connections and NTP synchronization. - Added README in test directory for PlatformIO Test Runner and project tests.main
commit
dbc81db288
18 changed files with 874 additions and 0 deletions
@ -0,0 +1,5 @@ |
|||||||
|
.pio |
||||||
|
.vscode/.browse.c_cpp.db* |
||||||
|
.vscode/c_cpp_properties.json |
||||||
|
.vscode/launch.json |
||||||
|
.vscode/ipch |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
{ |
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846 |
||||||
|
// for the documentation about the extensions.json format |
||||||
|
"recommendations": [ |
||||||
|
"platformio.platformio-ide" |
||||||
|
], |
||||||
|
"unwantedRecommendations": [ |
||||||
|
"ms-vscode.cpptools-extension-pack" |
||||||
|
] |
||||||
|
} |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
|
||||||
|
This directory is intended for project header files. |
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions |
||||||
|
to be shared between several project source files. You request the use of a |
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder |
||||||
|
by including it, with the C preprocessing directive `#include'. |
||||||
|
|
||||||
|
```src/main.c |
||||||
|
|
||||||
|
#include "header.h" |
||||||
|
|
||||||
|
int main (void) |
||||||
|
{ |
||||||
|
... |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file |
||||||
|
into each source file that needs it. Such copying would be time-consuming |
||||||
|
and error-prone. With a header file, the related declarations appear |
||||||
|
in only one place. If they need to be changed, they can be changed in one |
||||||
|
place, and programs that include the header file will automatically use the |
||||||
|
new version when next recompiled. The header file eliminates the labor of |
||||||
|
finding and changing all the copies as well as the risk that a failure to |
||||||
|
find one copy will result in inconsistencies within a program. |
||||||
|
|
||||||
|
In C, the convention is to give header files names that end with `.h'. |
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation: |
||||||
|
|
||||||
|
* Include Syntax |
||||||
|
* Include Operation |
||||||
|
* Once-Only Headers |
||||||
|
* Computed Includes |
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html |
||||||
@ -0,0 +1,46 @@ |
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries. |
||||||
|
PlatformIO will compile them to static libraries and link into the executable file. |
||||||
|
|
||||||
|
The source code of each library should be placed in a separate directory |
||||||
|
("lib/your_library_name/[Code]"). |
||||||
|
|
||||||
|
For example, see the structure of the following example libraries `Foo` and `Bar`: |
||||||
|
|
||||||
|
|--lib |
||||||
|
| | |
||||||
|
| |--Bar |
||||||
|
| | |--docs |
||||||
|
| | |--examples |
||||||
|
| | |--src |
||||||
|
| | |- Bar.c |
||||||
|
| | |- Bar.h |
||||||
|
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html |
||||||
|
| | |
||||||
|
| |--Foo |
||||||
|
| | |- Foo.c |
||||||
|
| | |- Foo.h |
||||||
|
| | |
||||||
|
| |- README --> THIS FILE |
||||||
|
| |
||||||
|
|- platformio.ini |
||||||
|
|--src |
||||||
|
|- main.c |
||||||
|
|
||||||
|
Example contents of `src/main.c` using Foo and Bar: |
||||||
|
``` |
||||||
|
#include <Foo.h> |
||||||
|
#include <Bar.h> |
||||||
|
|
||||||
|
int main (void) |
||||||
|
{ |
||||||
|
... |
||||||
|
} |
||||||
|
|
||||||
|
``` |
||||||
|
|
||||||
|
The PlatformIO Library Dependency Finder will find automatically dependent |
||||||
|
libraries by scanning project source files. |
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder |
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
; PlatformIO Project Configuration File |
||||||
|
; |
||||||
|
; Build options: build flags, source filter |
||||||
|
; Upload options: custom upload port, speed and extra flags |
||||||
|
; Library options: dependencies, extra library storages |
||||||
|
; Advanced options: extra scripting |
||||||
|
; |
||||||
|
; Please visit documentation for the other options and examples |
||||||
|
; https://docs.platformio.org/page/projectconf.html |
||||||
|
|
||||||
|
[env:esp32dev] |
||||||
|
platform = espressif32 |
||||||
|
board = esp32dev |
||||||
|
framework = arduino |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
#ifndef CREDENTIALS_H |
||||||
|
#define CREDENTIALS_H |
||||||
|
|
||||||
|
// WiFi credentials
|
||||||
|
#define WIFI_SSID "MatCaverna" // Replace with your WiFi SSID
|
||||||
|
#define WIFI_PASSWORD "nonlasai?" // Replace with your WiFi password
|
||||||
|
|
||||||
|
// NTP Server settings
|
||||||
|
#define NTP_SERVER "pool.ntp.org" |
||||||
|
#define GMT_OFFSET_SEC 3600 // Adjust based on your timezone (seconds)
|
||||||
|
#define DAYLIGHT_OFFSET_SEC 3600 // Adjust based on daylight savings (seconds)
|
||||||
|
|
||||||
|
#endif // CREDENTIALS_H
|
||||||
@ -0,0 +1,233 @@ |
|||||||
|
#include "text_display.h" |
||||||
|
#include "../fonts/font5x7.h" |
||||||
|
#include "../effects/matrix_rain.h" |
||||||
|
#include "../utils/time_utils.h" |
||||||
|
|
||||||
|
// Display text on the matrix
|
||||||
|
void displayText(MD_MAX72XX& mx, int maxDevices, const char* text, int scrollSpeed) { |
||||||
|
int textLen = strlen(text); |
||||||
|
if (textLen == 0) return; |
||||||
|
|
||||||
|
// Calculate total width of the message (5 pixels per character plus 1 pixel space between characters)
|
||||||
|
int totalWidth = textLen * 6 - 1; // 6 pixels per char (5 + 1 space), minus the last space
|
||||||
|
|
||||||
|
// Scroll the text from right to left
|
||||||
|
for (int curPosition = maxDevices * 8; curPosition > -totalWidth; curPosition--) { |
||||||
|
mx.clear(); |
||||||
|
|
||||||
|
// Draw each character
|
||||||
|
for (int charIndex = 0; charIndex < textLen; charIndex++) { |
||||||
|
int charPosition = curPosition + (charIndex * 6); // 6 pixels per character (5 + 1 space)
|
||||||
|
|
||||||
|
// Skip if the character is completely out of view
|
||||||
|
if (charPosition < -5 || charPosition >= maxDevices * 8) continue; |
||||||
|
|
||||||
|
// Get the character to display
|
||||||
|
char c = text[charIndex]; |
||||||
|
// Only handle printable ASCII characters
|
||||||
|
if (c < ' ' || c > 'Z') c = ' '; |
||||||
|
|
||||||
|
// Calculate the index in the font array
|
||||||
|
uint16_t fontIndex = (c - ' ') * 5; // Each character uses 5 bytes
|
||||||
|
|
||||||
|
// Draw the character column by column
|
||||||
|
for (int col = 0; col < 5; col++) { |
||||||
|
// Skip if this column is out of view
|
||||||
|
if (charPosition + col < 0 || charPosition + col >= maxDevices * 8) continue; |
||||||
|
|
||||||
|
// Get the font data for this column
|
||||||
|
uint8_t colData = pgm_read_byte(&font5x7[fontIndex + col]); |
||||||
|
|
||||||
|
// Draw each pixel in the column
|
||||||
|
for (int row = 0; row < 8; row++) { |
||||||
|
if (colData & (1 << row)) { |
||||||
|
// Flip the horizontal coordinate
|
||||||
|
int flippedCol = (maxDevices * 8 - 1) - (charPosition + col); |
||||||
|
mx.setPoint(row, flippedCol, true); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
mx.update(); |
||||||
|
delay(scrollSpeed); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Example function to display a message then return to rain effect
|
||||||
|
void showMessage(MD_MAX72XX& mx, int maxDevices, const char* message, int scrollSpeed) { |
||||||
|
// Clear the current display
|
||||||
|
mx.clear(); |
||||||
|
mx.update(); |
||||||
|
delay(50); |
||||||
|
|
||||||
|
// Show the message
|
||||||
|
displayText(mx, maxDevices, message, scrollSpeed); |
||||||
|
|
||||||
|
// Clear again before returning to rain effect
|
||||||
|
mx.clear(); |
||||||
|
mx.update(); |
||||||
|
delay(50); |
||||||
|
|
||||||
|
// Reset rain drops so they start fresh
|
||||||
|
initRain(); |
||||||
|
} |
||||||
|
|
||||||
|
// Display centered text on the matrix for a specified duration
|
||||||
|
void showCenteredText(MD_MAX72XX& mx, int maxDevices, const char* text, unsigned long duration, int intensity) { |
||||||
|
int textLen = strlen(text); |
||||||
|
if (textLen == 0) return; |
||||||
|
|
||||||
|
// Calculate total width of the text (5 pixels per character plus 1 pixel space between characters)
|
||||||
|
int textWidth = (textLen * 6) - 1; // 6 pixels per char (5 + 1 space), minus the last space
|
||||||
|
|
||||||
|
// Calculate starting position to center the text
|
||||||
|
int startPos = ((maxDevices * 8) - textWidth) / 2; |
||||||
|
if (startPos < 0) startPos = 0; // If text is too wide, start from the left edge
|
||||||
|
|
||||||
|
// Clear the display
|
||||||
|
mx.clear(); |
||||||
|
mx.control(MD_MAX72XX::INTENSITY, intensity); |
||||||
|
|
||||||
|
// Draw each character
|
||||||
|
for (int charIndex = 0; charIndex < textLen; charIndex++) { |
||||||
|
int charPosition = startPos + (charIndex * 6); // 6 pixels per character (5 + 1 space)
|
||||||
|
|
||||||
|
// Skip if the character would be completely out of view
|
||||||
|
if (charPosition >= maxDevices * 8) break; |
||||||
|
|
||||||
|
char c = text[charIndex]; |
||||||
|
// Only handle printable ASCII characters
|
||||||
|
if (c < ' ' || c > 'Z') c = ' '; |
||||||
|
|
||||||
|
// Calculate the index in the font array
|
||||||
|
uint16_t fontIndex = (c - ' ') * 5; |
||||||
|
|
||||||
|
// Draw the character
|
||||||
|
for (int col = 0; col < 5; col++) { |
||||||
|
// Skip if this column would be out of bounds
|
||||||
|
if (charPosition + col >= maxDevices * 8) break; |
||||||
|
|
||||||
|
// Get the column data
|
||||||
|
uint8_t colData = pgm_read_byte(&font5x7[fontIndex + col]); |
||||||
|
|
||||||
|
// Draw each pixel in the column
|
||||||
|
for (int row = 0; row < 8; row++) { |
||||||
|
if (colData & (1 << row)) { |
||||||
|
// Flip the horizontal coordinate to match the rest of the display
|
||||||
|
int flippedCol = (maxDevices * 8 - 1) - (charPosition + col); |
||||||
|
mx.setPoint(row, flippedCol, true); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Update the display
|
||||||
|
mx.update(); |
||||||
|
|
||||||
|
// Hold for the specified duration
|
||||||
|
delay(duration); |
||||||
|
} |
||||||
|
|
||||||
|
// Show centered text for a specified duration, then return to rain effect
|
||||||
|
void showCenteredMessage(MD_MAX72XX& mx, int maxDevices, const char* message, unsigned long duration, int intensity) { |
||||||
|
// Show the centered text
|
||||||
|
showCenteredText(mx, maxDevices, message, duration, intensity); |
||||||
|
} |
||||||
|
|
||||||
|
// Display centered text on the matrix with seconds on the bottom row
|
||||||
|
void showTimeWithSeconds(MD_MAX72XX& mx, int maxDevices, unsigned long duration, int intensity) { |
||||||
|
struct tm timeinfo; |
||||||
|
if (!getLocalTime(&timeinfo)) { |
||||||
|
Serial.println("Failed to obtain time"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Format time as HH:MM
|
||||||
|
char timeString[6]; // HH:MM + null terminator
|
||||||
|
strftime(timeString, sizeof(timeString), "%H:%M", &timeinfo); |
||||||
|
|
||||||
|
// Clear the display and set intensity
|
||||||
|
mx.clear(); |
||||||
|
mx.control(MD_MAX72XX::INTENSITY, intensity); |
||||||
|
|
||||||
|
// Draw the HH:MM text centered
|
||||||
|
int textLen = strlen(timeString); |
||||||
|
int textWidth = (textLen * 6) - 1; // 6 pixels per char (5 + 1 space), minus the last space
|
||||||
|
|
||||||
|
// Calculate starting position to center the text
|
||||||
|
int startPos = ((maxDevices * 8) - textWidth) / 2; |
||||||
|
if (startPos < 0) startPos = 0; // If text is too wide, start from the left edge
|
||||||
|
|
||||||
|
// Draw each character of HH:MM
|
||||||
|
for (int charIndex = 0; charIndex < textLen; charIndex++) { |
||||||
|
int charPosition = startPos + (charIndex * 6); // 6 pixels per character (5 + 1 space)
|
||||||
|
|
||||||
|
// Skip if the character would be completely out of view
|
||||||
|
if (charPosition >= maxDevices * 8) break; |
||||||
|
|
||||||
|
char c = timeString[charIndex]; |
||||||
|
|
||||||
|
// Calculate the index in the font array
|
||||||
|
uint16_t fontIndex = (c - ' ') * 5; |
||||||
|
|
||||||
|
// Draw the character
|
||||||
|
for (int col = 0; col < 5; col++) { |
||||||
|
// Skip if this column would be out of bounds
|
||||||
|
if (charPosition + col >= maxDevices * 8) break; |
||||||
|
|
||||||
|
// Get the column data
|
||||||
|
uint8_t colData = pgm_read_byte(&font5x7[fontIndex + col]); |
||||||
|
|
||||||
|
// Draw each pixel in the column (only rows 0-6, leaving row 7 for seconds)
|
||||||
|
for (int row = 0; row < 7; row++) { |
||||||
|
if (colData & (1 << row)) { |
||||||
|
// Flip the horizontal coordinate to match the rest of the display
|
||||||
|
int flippedCol = (maxDevices * 8 - 1) - (charPosition + col); |
||||||
|
mx.setPoint(row, flippedCol, true); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Draw seconds on the bottom row (row 7)
|
||||||
|
int seconds = timeinfo.tm_sec; |
||||||
|
int totalPixels = maxDevices * 8; // 32 pixels total
|
||||||
|
|
||||||
|
if (seconds < 30) { |
||||||
|
// First half minute (0-29 seconds): Fill proportionally up to 30 LEDs
|
||||||
|
int pixelsToLight = seconds; |
||||||
|
|
||||||
|
// Light up pixels from left to right
|
||||||
|
for (int i = 0; i < pixelsToLight; i++) { |
||||||
|
// Flip the horizontal coordinate to match the rest of the display
|
||||||
|
int flippedCol = (totalPixels - 1) - i; |
||||||
|
mx.setPoint(7, flippedCol, true); |
||||||
|
} |
||||||
|
} else { |
||||||
|
// Second half minute (30-59 seconds):
|
||||||
|
int pixelsToLight = seconds - 30; // 0-29 LEDs
|
||||||
|
// All 30 progress LEDs are lit
|
||||||
|
for (int i = 0; i < pixelsToLight; i++) { |
||||||
|
// Flip the horizontal coordinate to match the rest of the display
|
||||||
|
int flippedCol = (totalPixels - 1) - i; |
||||||
|
mx.setPoint(7, flippedCol, true); |
||||||
|
} |
||||||
|
|
||||||
|
// Additionally light the last LED (far right) to indicate >30 seconds
|
||||||
|
mx.setPoint(7, 0, true); |
||||||
|
} |
||||||
|
|
||||||
|
// Update the display
|
||||||
|
mx.update(); |
||||||
|
|
||||||
|
if (duration > 0) { |
||||||
|
delay(duration); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Show time with seconds for a specified duration, then return to rain effect
|
||||||
|
void showTimeWithSecondsMessage(MD_MAX72XX& mx, int maxDevices, unsigned long duration, int intensity) { |
||||||
|
// Show the time with seconds
|
||||||
|
showTimeWithSeconds(mx, maxDevices, duration, intensity); |
||||||
|
} |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
#ifndef TEXT_DISPLAY_H |
||||||
|
#define TEXT_DISPLAY_H |
||||||
|
|
||||||
|
#include <Arduino.h> |
||||||
|
#include <MD_MAX72xx.h> |
||||||
|
|
||||||
|
// Display text on the matrix
|
||||||
|
void displayText(MD_MAX72XX& mx, int maxDevices, const char* text, int scrollSpeed = 75); |
||||||
|
|
||||||
|
// Example function to display a message then return to rain effect
|
||||||
|
void showMessage(MD_MAX72XX& mx, int maxDevices, const char* message, int scrollSpeed = 75); |
||||||
|
|
||||||
|
// Display centered text on the matrix for a specified duration
|
||||||
|
void showCenteredText(MD_MAX72XX& mx, int maxDevices, const char* text, unsigned long duration = 2000, int intensity = 8); |
||||||
|
|
||||||
|
// Show centered text for a specified duration, then return to rain effect
|
||||||
|
void showCenteredMessage(MD_MAX72XX& mx, int maxDevices, const char* message, unsigned long duration = 2000, int intensity = 8); |
||||||
|
|
||||||
|
// Display centered text on the matrix with seconds on the bottom row
|
||||||
|
void showTimeWithSeconds(MD_MAX72XX& mx, int maxDevices, unsigned long duration = 2000, int intensity = 8); |
||||||
|
|
||||||
|
// Show time with seconds for a specified duration, then return to rain effect
|
||||||
|
void showTimeWithSecondsMessage(MD_MAX72XX& mx, int maxDevices, unsigned long duration = 2000, int intensity = 8); |
||||||
|
|
||||||
|
#endif // TEXT_DISPLAY_H
|
||||||
@ -0,0 +1,129 @@ |
|||||||
|
#include "matrix_rain.h" |
||||||
|
|
||||||
|
// Static global variables for this module
|
||||||
|
static Drop drops[MAX_DROPS]; |
||||||
|
static bool brightBuffer[32][8]; // [absolute_col][row] assuming MAX_DEVICES=4
|
||||||
|
static bool dimBuffer[32][8]; // [absolute_col][row] assuming MAX_DEVICES=4
|
||||||
|
|
||||||
|
// Initialize raindrops
|
||||||
|
void initRain() { |
||||||
|
for (int i = 0; i < MAX_DROPS; i++) { |
||||||
|
// Set inactive state
|
||||||
|
drops[i].row = -1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Create a new raindrop
|
||||||
|
void newDrop() { |
||||||
|
// Find an inactive drop slot
|
||||||
|
for (int i = 0; i < MAX_DROPS; i++) { |
||||||
|
if (drops[i].row == -1) { |
||||||
|
// Assign random values to the new drop
|
||||||
|
drops[i].device = random(4); // Assuming MAX_DEVICES=4
|
||||||
|
drops[i].col = random(8); |
||||||
|
drops[i].row = 0; // Start at top
|
||||||
|
drops[i].length = random(2, 6); // Trail length 2-5
|
||||||
|
drops[i].speed = random(1, 4); // Speed 1-3
|
||||||
|
drops[i].wait = 0; |
||||||
|
drops[i].brightness = random(2, 4); // Random brightness (2-3)
|
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Update the matrix display with brightness simulation
|
||||||
|
void updateDisplay(MD_MAX72XX& mx, int maxDevices) { |
||||||
|
// Clear display buffers
|
||||||
|
memset(brightBuffer, 0, sizeof(brightBuffer)); |
||||||
|
memset(dimBuffer, 0, sizeof(dimBuffer)); |
||||||
|
|
||||||
|
// Draw each active drop
|
||||||
|
for (int i = 0; i < MAX_DROPS; i++) { |
||||||
|
if (drops[i].row >= 0) { |
||||||
|
// Calculate absolute column (device * 8 + col)
|
||||||
|
int absCol = drops[i].device * 8 + drops[i].col; |
||||||
|
|
||||||
|
// Draw the head of the drop (brightest)
|
||||||
|
if (drops[i].row < 8) { |
||||||
|
brightBuffer[absCol][drops[i].row] = true; |
||||||
|
} |
||||||
|
|
||||||
|
// Draw the trail with diminishing brightness
|
||||||
|
for (int t = 1; t < drops[i].length; t++) { |
||||||
|
int trailRow = drops[i].row - t; |
||||||
|
if (trailRow >= 0 && trailRow < 8) { |
||||||
|
// First trail segment has higher chance to be in bright buffer
|
||||||
|
if (t == 1 && random(4) > 0) { |
||||||
|
brightBuffer[absCol][trailRow] = true; |
||||||
|
} |
||||||
|
// Other trail segments go to dim buffer with diminishing probability
|
||||||
|
else if (random(t + 1) == 0) { |
||||||
|
dimBuffer[absCol][trailRow] = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Update drop position based on speed
|
||||||
|
drops[i].wait++; |
||||||
|
if (drops[i].wait >= drops[i].speed) { |
||||||
|
drops[i].wait = 0; |
||||||
|
drops[i].row++; |
||||||
|
|
||||||
|
// If the drop has moved off the display, deactivate it
|
||||||
|
if (drops[i].row >= 8 + drops[i].length) { |
||||||
|
drops[i].row = -1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Clear the entire display
|
||||||
|
mx.control(MD_MAX72XX::INTENSITY, 8); // Medium intensity
|
||||||
|
mx.clear(); |
||||||
|
|
||||||
|
// Frame counter for simulating different brightness levels
|
||||||
|
static unsigned long frame = 0; |
||||||
|
frame++; |
||||||
|
|
||||||
|
// Apply the brightness simulation using the library
|
||||||
|
for (int col = 0; col < maxDevices * 8; col++) { |
||||||
|
for (int row = 0; row < 8; row++) { |
||||||
|
bool showPixel = false; |
||||||
|
|
||||||
|
// For every 4 frames, we simulate different brightness levels:
|
||||||
|
if (frame % 4 == 3) { |
||||||
|
// Dimmest frame - only show dim pixels
|
||||||
|
showPixel = dimBuffer[col][row]; |
||||||
|
} else if (frame % 4 == 1) { |
||||||
|
// Medium frame - show both with some randomization on dim pixels
|
||||||
|
showPixel = brightBuffer[col][row] || (dimBuffer[col][row] && random(2) == 0); |
||||||
|
} else { |
||||||
|
// Brightest frame - show only bright pixels
|
||||||
|
showPixel = brightBuffer[col][row]; |
||||||
|
} |
||||||
|
|
||||||
|
if (showPixel) { |
||||||
|
// Flip the horizontal coordinate
|
||||||
|
int flippedCol = (maxDevices * 8 - 1) - col; |
||||||
|
mx.setPoint(row, flippedCol, true); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Update the display
|
||||||
|
mx.update(); |
||||||
|
} |
||||||
|
|
||||||
|
// Get pointer to the drops array
|
||||||
|
Drop* getDrops() { |
||||||
|
return drops; |
||||||
|
} |
||||||
|
|
||||||
|
// Get pointers to the brightness buffers
|
||||||
|
bool (*getBrightBuffer())[8] { |
||||||
|
return brightBuffer; |
||||||
|
} |
||||||
|
|
||||||
|
bool (*getDimBuffer())[8] { |
||||||
|
return dimBuffer; |
||||||
|
} |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
#ifndef MATRIX_RAIN_H |
||||||
|
#define MATRIX_RAIN_H |
||||||
|
|
||||||
|
#include <Arduino.h> |
||||||
|
#include <MD_MAX72xx.h> |
||||||
|
|
||||||
|
// Rain drop data structure
|
||||||
|
#define MAX_DROPS 128 |
||||||
|
struct Drop { |
||||||
|
int device; // Which display (0-3)
|
||||||
|
int col; // Column position (0-7)
|
||||||
|
int row; // Row position (0-7)
|
||||||
|
int length; // Length of the trail (1-5)
|
||||||
|
int speed; // Update speed (1-3 frames)
|
||||||
|
int wait; // Counter for speed
|
||||||
|
int brightness; // Brightness level of the head (0-3)
|
||||||
|
}; |
||||||
|
|
||||||
|
// Initialize the matrix rain effect
|
||||||
|
void initRain(); |
||||||
|
|
||||||
|
// Create a new raindrop
|
||||||
|
void newDrop(); |
||||||
|
|
||||||
|
// Update the matrix display with brightness simulation
|
||||||
|
void updateDisplay(MD_MAX72XX& mx, int maxDevices); |
||||||
|
|
||||||
|
// Get pointer to the drops array
|
||||||
|
Drop* getDrops(); |
||||||
|
|
||||||
|
// Get pointers to the brightness buffers
|
||||||
|
bool (*getBrightBuffer())[8]; |
||||||
|
bool (*getDimBuffer())[8]; |
||||||
|
|
||||||
|
#endif // MATRIX_RAIN_H
|
||||||
@ -0,0 +1,36 @@ |
|||||||
|
#ifndef FONT3X5_DIGITS_H |
||||||
|
#define FONT3X5_DIGITS_H |
||||||
|
|
||||||
|
#include <Arduino.h> |
||||||
|
|
||||||
|
// A compact 3x5 font, specifically for digits and colons to display time
|
||||||
|
// Each digit/character is 3 pixels wide to fit "00:00:00" on a 32 pixel display
|
||||||
|
|
||||||
|
const uint8_t font3x5_digits[] PROGMEM = { |
||||||
|
0x7, 0x5, 0x7, // 0
|
||||||
|
0x2, 0x7, 0x0, // 1
|
||||||
|
0x5, 0x5, 0x2, // 2
|
||||||
|
0x5, 0x5, 0x7, // 3
|
||||||
|
0x2, 0x2, 0x7, // 4
|
||||||
|
0x6, 0x5, 0x5, // 5
|
||||||
|
0x7, 0x5, 0x5, // 6
|
||||||
|
0x1, 0x1, 0x7, // 7
|
||||||
|
0x7, 0x5, 0x7, // 8
|
||||||
|
0x2, 0x2, 0x7, // 9
|
||||||
|
0x0, 0x2, 0x0, // : (colon)
|
||||||
|
}; |
||||||
|
|
||||||
|
// Character index lookup (only digits 0-9 and colon)
|
||||||
|
#define DIGIT_0 0 |
||||||
|
#define DIGIT_1 3 |
||||||
|
#define DIGIT_2 6 |
||||||
|
#define DIGIT_3 9 |
||||||
|
#define DIGIT_4 12 |
||||||
|
#define DIGIT_5 15 |
||||||
|
#define DIGIT_6 18 |
||||||
|
#define DIGIT_7 21 |
||||||
|
#define DIGIT_8 24 |
||||||
|
#define DIGIT_9 27 |
||||||
|
#define DIGIT_COLON 30 |
||||||
|
|
||||||
|
#endif // FONT3X5_DIGITS_H
|
||||||
@ -0,0 +1,70 @@ |
|||||||
|
#ifndef FONT5X7_H |
||||||
|
#define FONT5X7_H |
||||||
|
|
||||||
|
#include <Arduino.h> |
||||||
|
|
||||||
|
// Font definition - basic 5x7 font for common ASCII characters
|
||||||
|
const uint8_t font5x7[] PROGMEM = { |
||||||
|
// First 32 characters (0x00-0x19) are non-printable
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, // Space (char 32)
|
||||||
|
0x00, 0x00, 0x5F, 0x00, 0x00, // !
|
||||||
|
0x00, 0x07, 0x00, 0x07, 0x00, // "
|
||||||
|
0x14, 0x7F, 0x14, 0x7F, 0x14, // #
|
||||||
|
0x24, 0x2A, 0x7F, 0x2A, 0x12, // $
|
||||||
|
0x23, 0x13, 0x08, 0x64, 0x62, // %
|
||||||
|
0x36, 0x49, 0x55, 0x22, 0x50, // &
|
||||||
|
0x00, 0x05, 0x03, 0x00, 0x00, // '
|
||||||
|
0x00, 0x1C, 0x22, 0x41, 0x00, // (
|
||||||
|
0x00, 0x41, 0x22, 0x1C, 0x00, // )
|
||||||
|
0x08, 0x2A, 0x1C, 0x2A, 0x08, // *
|
||||||
|
0x08, 0x08, 0x3E, 0x08, 0x08, // +
|
||||||
|
0x00, 0x50, 0x30, 0x00, 0x00, // ,
|
||||||
|
0x08, 0x08, 0x08, 0x08, 0x08, // -
|
||||||
|
0x00, 0x60, 0x60, 0x00, 0x00, // .
|
||||||
|
0x20, 0x10, 0x08, 0x04, 0x02, // /
|
||||||
|
0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
|
||||||
|
0x00, 0x42, 0x7F, 0x40, 0x00, // 1
|
||||||
|
0x42, 0x61, 0x51, 0x49, 0x46, // 2
|
||||||
|
0x21, 0x41, 0x45, 0x4B, 0x31, // 3
|
||||||
|
0x18, 0x14, 0x12, 0x7F, 0x10, // 4
|
||||||
|
0x27, 0x45, 0x45, 0x45, 0x39, // 5
|
||||||
|
0x3C, 0x4A, 0x49, 0x49, 0x30, // 6
|
||||||
|
0x01, 0x71, 0x09, 0x05, 0x03, // 7
|
||||||
|
0x36, 0x49, 0x49, 0x49, 0x36, // 8
|
||||||
|
0x06, 0x49, 0x49, 0x29, 0x1E, // 9
|
||||||
|
0x00, 0x36, 0x36, 0x00, 0x00, // :
|
||||||
|
0x00, 0x56, 0x36, 0x00, 0x00, // ;
|
||||||
|
0x00, 0x08, 0x14, 0x22, 0x41, // <
|
||||||
|
0x14, 0x14, 0x14, 0x14, 0x14, // =
|
||||||
|
0x41, 0x22, 0x14, 0x08, 0x00, // >
|
||||||
|
0x02, 0x01, 0x51, 0x09, 0x06, // ?
|
||||||
|
0x32, 0x49, 0x79, 0x41, 0x3E, // @
|
||||||
|
0x7E, 0x11, 0x11, 0x11, 0x7E, // A
|
||||||
|
0x7F, 0x49, 0x49, 0x49, 0x36, // B
|
||||||
|
0x3E, 0x41, 0x41, 0x41, 0x22, // C
|
||||||
|
0x7F, 0x41, 0x41, 0x22, 0x1C, // D
|
||||||
|
0x7F, 0x49, 0x49, 0x49, 0x41, // E
|
||||||
|
0x7F, 0x09, 0x09, 0x09, 0x01, // F
|
||||||
|
0x3E, 0x41, 0x49, 0x49, 0x7A, // G
|
||||||
|
0x7F, 0x08, 0x08, 0x08, 0x7F, // H
|
||||||
|
0x00, 0x41, 0x7F, 0x41, 0x00, // I
|
||||||
|
0x20, 0x40, 0x41, 0x3F, 0x01, // J
|
||||||
|
0x7F, 0x08, 0x14, 0x22, 0x41, // K
|
||||||
|
0x7F, 0x40, 0x40, 0x40, 0x40, // L
|
||||||
|
0x7F, 0x02, 0x0C, 0x02, 0x7F, // M
|
||||||
|
0x7F, 0x04, 0x08, 0x10, 0x7F, // N
|
||||||
|
0x3E, 0x41, 0x41, 0x41, 0x3E, // O
|
||||||
|
0x7F, 0x09, 0x09, 0x09, 0x06, // P
|
||||||
|
0x3E, 0x41, 0x51, 0x21, 0x5E, // Q
|
||||||
|
0x7F, 0x09, 0x19, 0x29, 0x46, // R
|
||||||
|
0x46, 0x49, 0x49, 0x49, 0x31, // S
|
||||||
|
0x01, 0x01, 0x7F, 0x01, 0x01, // T
|
||||||
|
0x3F, 0x40, 0x40, 0x40, 0x3F, // U
|
||||||
|
0x1F, 0x20, 0x40, 0x20, 0x1F, // V
|
||||||
|
0x3F, 0x40, 0x30, 0x40, 0x3F, // W
|
||||||
|
0x63, 0x14, 0x08, 0x14, 0x63, // X
|
||||||
|
0x07, 0x08, 0x70, 0x08, 0x07, // Y
|
||||||
|
0x61, 0x51, 0x49, 0x45, 0x43, // Z
|
||||||
|
}; |
||||||
|
|
||||||
|
#endif // FONT5X7_H
|
||||||
@ -0,0 +1,98 @@ |
|||||||
|
// This project uses the MD_MAX72XX library for controlling MAX7219 LED matrix displays using an ESP-WROOM-32 board
|
||||||
|
#include <Arduino.h> |
||||||
|
#include <MD_MAX72xx.h> |
||||||
|
#include <WiFi.h> |
||||||
|
#include <time.h> |
||||||
|
#include "credentials.h" |
||||||
|
|
||||||
|
// Include our modular components
|
||||||
|
#include "effects/matrix_rain.h" |
||||||
|
#include "display/text_display.h" |
||||||
|
#include "utils/wifi_manager.h" |
||||||
|
#include "utils/time_utils.h" |
||||||
|
|
||||||
|
// Define hardware type, size, and output pins
|
||||||
|
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW |
||||||
|
#define MAX_DEVICES 4 |
||||||
|
#define CS_PIN 5 |
||||||
|
#define DIN_PIN 23 |
||||||
|
#define CLK_PIN 18 |
||||||
|
|
||||||
|
// Display settings
|
||||||
|
#define MAX_BRIGHTNESS 3 // Maximum brightness level (0-15)
|
||||||
|
|
||||||
|
// Create a new instance of the MD_MAX72XX class
|
||||||
|
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DIN_PIN, CLK_PIN, CS_PIN, MAX_DEVICES); |
||||||
|
|
||||||
|
String lastTime; |
||||||
|
String lastSeconds; |
||||||
|
|
||||||
|
void setup() { |
||||||
|
Serial.begin(9600); |
||||||
|
Serial.println("Starting MAX7219 Matrix Rain Effect with MD_MAX72XX Library..."); |
||||||
|
|
||||||
|
// Initialize the library
|
||||||
|
mx.begin(); |
||||||
|
mx.control(MD_MAX72XX::INTENSITY, MAX_BRIGHTNESS); // Set to configured brightness
|
||||||
|
mx.clear(); |
||||||
|
|
||||||
|
// Initialize random seed using noise from an unconnected analog pin
|
||||||
|
randomSeed(analogRead(34)); |
||||||
|
|
||||||
|
// Initialize the rain effect
|
||||||
|
initRain(); |
||||||
|
|
||||||
|
Serial.println("All Displays Initialized."); |
||||||
|
Serial.println("Matrix Effect Initialized."); |
||||||
|
|
||||||
|
// Connect to WiFi
|
||||||
|
if (connectToWiFi()) { |
||||||
|
// Synchronize time using NTP
|
||||||
|
syncTimeWithNTP(); |
||||||
|
|
||||||
|
// Wait 10 seconds
|
||||||
|
Serial.println("Waiting 10 seconds before disconnecting WiFi..."); |
||||||
|
delay(10000); |
||||||
|
|
||||||
|
// Disconnect WiFi to save power
|
||||||
|
disconnectWiFi(); |
||||||
|
} |
||||||
|
|
||||||
|
// Show a welcome message
|
||||||
|
showMessage(mx, MAX_DEVICES, "MATRIX"); |
||||||
|
} |
||||||
|
|
||||||
|
void loop() { |
||||||
|
// Create new drops occasionally
|
||||||
|
if (random(20) == 0) { |
||||||
|
newDrop(); |
||||||
|
} |
||||||
|
|
||||||
|
// Handle special triggers for text, if any
|
||||||
|
struct tm timeinfo; |
||||||
|
if (getLocalTime(&timeinfo)) { |
||||||
|
// Format current time
|
||||||
|
char timeString[6]; // HH:MM + null terminator
|
||||||
|
strftime(timeString, sizeof(timeString), "%H:%M", &timeinfo); |
||||||
|
String timeStr = String(timeString); |
||||||
|
|
||||||
|
// Format seconds
|
||||||
|
char secondsString[3]; // SS + null terminator
|
||||||
|
strftime(secondsString, sizeof(secondsString), "%S", &timeinfo); |
||||||
|
String secondsStr = String(secondsString); |
||||||
|
|
||||||
|
// Update display if time or seconds changed
|
||||||
|
if (timeStr != lastTime || secondsStr != lastSeconds) { |
||||||
|
Serial.println("Current Time: " + timeStr + ":" + secondsStr); |
||||||
|
// Show the time with seconds progress bar
|
||||||
|
showTimeWithSeconds(mx, MAX_DEVICES, 100, 1); |
||||||
|
lastTime = timeStr; |
||||||
|
lastSeconds = secondsStr; |
||||||
|
return; // Skip rain effect this cycle
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// // Update the matrix display with the rain effect
|
||||||
|
// updateDisplay(mx, MAX_DEVICES, MAX_BRIGHTNESS);
|
||||||
|
// delay(30); // Speed control for the rain effect
|
||||||
|
} |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
#include "time_utils.h" |
||||||
|
#include <time.h> |
||||||
|
|
||||||
|
// Format the current time as HH:MM
|
||||||
|
String getCurrentTime() { |
||||||
|
struct tm timeinfo; |
||||||
|
if(!getLocalTime(&timeinfo)) { |
||||||
|
Serial.println("Failed to obtain time"); |
||||||
|
return "ERROR"; |
||||||
|
} |
||||||
|
|
||||||
|
char timeString[6]; // HH:MM + null terminator
|
||||||
|
strftime(timeString, sizeof(timeString), "%H:%M", &timeinfo); |
||||||
|
|
||||||
|
return String(timeString); |
||||||
|
} |
||||||
|
|
||||||
|
// Format the current date as YYYY-MM-DD
|
||||||
|
String getCurrentDate() { |
||||||
|
struct tm timeinfo; |
||||||
|
if(!getLocalTime(&timeinfo)) { |
||||||
|
Serial.println("Failed to obtain time"); |
||||||
|
return "ERROR"; |
||||||
|
} |
||||||
|
|
||||||
|
char dateString[11]; // YYYY-MM-DD + null terminator
|
||||||
|
strftime(dateString, sizeof(dateString), "%Y-%m-%d", &timeinfo); |
||||||
|
|
||||||
|
return String(dateString); |
||||||
|
} |
||||||
@ -0,0 +1,12 @@ |
|||||||
|
#ifndef TIME_UTILS_H |
||||||
|
#define TIME_UTILS_H |
||||||
|
|
||||||
|
#include <Arduino.h> |
||||||
|
|
||||||
|
// Format the current time as HH:MM
|
||||||
|
String getCurrentTime(); |
||||||
|
|
||||||
|
// Format the current date as YYYY-MM-DD
|
||||||
|
String getCurrentDate(); |
||||||
|
|
||||||
|
#endif // TIME_UTILS_H
|
||||||
@ -0,0 +1,54 @@ |
|||||||
|
#include "wifi_manager.h" |
||||||
|
#include <time.h> |
||||||
|
#include "credentials.h" // Include the credentials header file |
||||||
|
|
||||||
|
// Connect to WiFi network
|
||||||
|
bool connectToWiFi() { |
||||||
|
Serial.println("Connecting to WiFi..."); |
||||||
|
|
||||||
|
WiFi.begin(WIFI_SSID, WIFI_PASSWORD); |
||||||
|
|
||||||
|
// Wait for connection with timeout
|
||||||
|
int timeout = 0; |
||||||
|
while (WiFi.status() != WL_CONNECTED && timeout < 20) { |
||||||
|
delay(500); |
||||||
|
Serial.print("."); |
||||||
|
timeout++; |
||||||
|
} |
||||||
|
|
||||||
|
if (WiFi.status() == WL_CONNECTED) { |
||||||
|
Serial.println("\nWiFi connected"); |
||||||
|
Serial.println("IP address: "); |
||||||
|
Serial.println(WiFi.localIP()); |
||||||
|
return true; |
||||||
|
} else { |
||||||
|
Serial.println("\nFailed to connect to WiFi"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Set the RTC using NTP
|
||||||
|
bool syncTimeWithNTP() { |
||||||
|
Serial.println("Syncing time with NTP..."); |
||||||
|
|
||||||
|
// Initialize and get the time
|
||||||
|
configTime(GMT_OFFSET_SEC, DAYLIGHT_OFFSET_SEC, NTP_SERVER); |
||||||
|
|
||||||
|
struct tm timeinfo; |
||||||
|
if(!getLocalTime(&timeinfo)) { |
||||||
|
Serial.println("Failed to obtain time"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
Serial.println("Time synchronized successfully"); |
||||||
|
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S"); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// Disconnect from WiFi to save power
|
||||||
|
void disconnectWiFi() { |
||||||
|
Serial.println("Disconnecting WiFi to save power..."); |
||||||
|
WiFi.disconnect(true); |
||||||
|
WiFi.mode(WIFI_OFF); |
||||||
|
Serial.println("WiFi disconnected"); |
||||||
|
} |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
#ifndef WIFI_MANAGER_H |
||||||
|
#define WIFI_MANAGER_H |
||||||
|
|
||||||
|
#include <Arduino.h> |
||||||
|
#include <WiFi.h> |
||||||
|
|
||||||
|
// Connect to WiFi network
|
||||||
|
bool connectToWiFi(); |
||||||
|
|
||||||
|
// Set the RTC using NTP
|
||||||
|
bool syncTimeWithNTP(); |
||||||
|
|
||||||
|
// Disconnect from WiFi to save power
|
||||||
|
void disconnectWiFi(); |
||||||
|
|
||||||
|
#endif // WIFI_MANAGER_H
|
||||||
@ -0,0 +1,11 @@ |
|||||||
|
|
||||||
|
This directory is intended for PlatformIO Test Runner and project tests. |
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of |
||||||
|
source code, sets of one or more MCU program modules together with associated |
||||||
|
control data, usage procedures, and operating procedures, are tested to |
||||||
|
determine whether they are fit for use. Unit testing finds problems early |
||||||
|
in the development cycle. |
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing: |
||||||
|
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html |
||||||
Loading…
Reference in new issue