commit dbc81db2881d56f737a29cb21c60f1b308360f2b Author: Matteo Benedetto Date: Sat May 3 16:15:11 2025 +0200 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. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -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" + ] +} diff --git a/include/README b/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/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 diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/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 +#include + +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 diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..4b30716 --- /dev/null +++ b/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 diff --git a/src/credentials.h b/src/credentials.h new file mode 100644 index 0000000..5975803 --- /dev/null +++ b/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 diff --git a/src/display/text_display.cpp b/src/display/text_display.cpp new file mode 100644 index 0000000..d666961 --- /dev/null +++ b/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); +} diff --git a/src/display/text_display.h b/src/display/text_display.h new file mode 100644 index 0000000..15fdebe --- /dev/null +++ b/src/display/text_display.h @@ -0,0 +1,25 @@ +#ifndef TEXT_DISPLAY_H +#define TEXT_DISPLAY_H + +#include +#include + +// 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 diff --git a/src/effects/matrix_rain.cpp b/src/effects/matrix_rain.cpp new file mode 100644 index 0000000..afaba93 --- /dev/null +++ b/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; +} diff --git a/src/effects/matrix_rain.h b/src/effects/matrix_rain.h new file mode 100644 index 0000000..136f501 --- /dev/null +++ b/src/effects/matrix_rain.h @@ -0,0 +1,35 @@ +#ifndef MATRIX_RAIN_H +#define MATRIX_RAIN_H + +#include +#include + +// 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 diff --git a/src/fonts/font3x5_digits.h b/src/fonts/font3x5_digits.h new file mode 100644 index 0000000..3b61621 --- /dev/null +++ b/src/fonts/font3x5_digits.h @@ -0,0 +1,36 @@ +#ifndef FONT3X5_DIGITS_H +#define FONT3X5_DIGITS_H + +#include + +// 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 diff --git a/src/fonts/font5x7.h b/src/fonts/font5x7.h new file mode 100644 index 0000000..f7ccbe7 --- /dev/null +++ b/src/fonts/font5x7.h @@ -0,0 +1,70 @@ +#ifndef FONT5X7_H +#define FONT5X7_H + +#include + +// 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 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..81a776b --- /dev/null +++ b/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 +#include +#include +#include +#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 +} diff --git a/src/utils/time_utils.cpp b/src/utils/time_utils.cpp new file mode 100644 index 0000000..8c47b4a --- /dev/null +++ b/src/utils/time_utils.cpp @@ -0,0 +1,30 @@ +#include "time_utils.h" +#include + +// 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); +} diff --git a/src/utils/time_utils.h b/src/utils/time_utils.h new file mode 100644 index 0000000..c976b9c --- /dev/null +++ b/src/utils/time_utils.h @@ -0,0 +1,12 @@ +#ifndef TIME_UTILS_H +#define TIME_UTILS_H + +#include + +// Format the current time as HH:MM +String getCurrentTime(); + +// Format the current date as YYYY-MM-DD +String getCurrentDate(); + +#endif // TIME_UTILS_H diff --git a/src/utils/wifi_manager.cpp b/src/utils/wifi_manager.cpp new file mode 100644 index 0000000..33fe8e9 --- /dev/null +++ b/src/utils/wifi_manager.cpp @@ -0,0 +1,54 @@ +#include "wifi_manager.h" +#include +#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"); +} diff --git a/src/utils/wifi_manager.h b/src/utils/wifi_manager.h new file mode 100644 index 0000000..dffa15a --- /dev/null +++ b/src/utils/wifi_manager.h @@ -0,0 +1,16 @@ +#ifndef WIFI_MANAGER_H +#define WIFI_MANAGER_H + +#include +#include + +// 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 diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/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