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