Browse Source

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.
main
Matteo Benedetto 8 months ago
commit
dbc81db288
  1. 5
      .gitignore
  2. 10
      .vscode/extensions.json
  3. 37
      include/README
  4. 46
      lib/README
  5. 14
      platformio.ini
  6. 13
      src/credentials.h
  7. 233
      src/display/text_display.cpp
  8. 25
      src/display/text_display.h
  9. 129
      src/effects/matrix_rain.cpp
  10. 35
      src/effects/matrix_rain.h
  11. 36
      src/fonts/font3x5_digits.h
  12. 70
      src/fonts/font5x7.h
  13. 98
      src/main.cpp
  14. 30
      src/utils/time_utils.cpp
  15. 12
      src/utils/time_utils.h
  16. 54
      src/utils/wifi_manager.cpp
  17. 16
      src/utils/wifi_manager.h
  18. 11
      test/README

5
.gitignore vendored

@ -0,0 +1,5 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

10
.vscode/extensions.json vendored

@ -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"
]
}

37
include/README

@ -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
lib/README

@ -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

14
platformio.ini

@ -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

13
src/credentials.h

@ -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

233
src/display/text_display.cpp

@ -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);
}

25
src/display/text_display.h

@ -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

129
src/effects/matrix_rain.cpp

@ -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;
}

35
src/effects/matrix_rain.h

@ -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

36
src/fonts/font3x5_digits.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

70
src/fonts/font5x7.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

98
src/main.cpp

@ -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
}

30
src/utils/time_utils.cpp

@ -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);
}

12
src/utils/time_utils.h

@ -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

54
src/utils/wifi_manager.cpp

@ -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");
}

16
src/utils/wifi_manager.h

@ -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
test/README

@ -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…
Cancel
Save