mirror of
https://github.com/Enne2/Led-Monitor-2.git
synced 2026-09-26 08:46:28 +00:00
Add initial project structure and functionality for LED matrix display
- 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.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
Vendored
+10
@@ -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
|
||||
+46
@@ -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
|
||||
+11
@@ -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
|
||||
Reference in New Issue
Block a user