ESP32 and 8x64 MAX7219 matrix display with MD_Parola library

Hi,

It's one of my first projects in Arduino environment but I'm learning quite fast so until now I've got soldered together 8x64 led matrix and connected it to ESP32 board. Everything is working fine, the text is displaying fine. I've ended up with one problem.

My program connects to API of my woocommerce shop and checks for some values. I'm checking every 5 minutes for new orders and printing it in the console works and that perfectly fine.

Problems I've experienced:

MD_Parola's command:
displayText(textToDisplay, PA_CENTER, 5, 6000000, PA_SLICE, PA_SLICE);

doesn't wait enough time, I can put there some crazy numbers and stil I'm getting animations every ~30 seconds.

my loop at the moment is pretty simple:

void loop() {

  unsigned long currentMillis = millis();  // Get the current time in milliseconds

  if (matrixDisplay.displayAnimate()) {
    matrixDisplay.displayReset();
  }
  

  if (currentMillis - previousOrdersCountCheck >=ordersCountCheckInterval) {
    getOrdersCount();
    previousOrdersCountCheck = currentMillis;
    snprintf(textToDisplay, sizeof(textToDisplay), "\xFF %s", ordersCount);

  }

}

What I'm trying to acheive is update display only when ordersCount changes but I've no idea how to do it. I could set crazy high pause time and use setPause() to shorten pause time in displayText() when update occurs then set it high again but as I said earlier I can't force it to don't animate for ~10 minutes :confused:

Any ideas what I'm doing wrong?

first of all you should post your complete sketch. The space your code needs on the screen will be same because it is a code-scetion.
Anyone that is willing to analyse your code can copy it with one click into clipboard and then paste the code into their favorite editor for analysing.

And don't have to ask back what does this or that or these (unvisible functions ) do

Yeah, you're right, sorry :slight_smile:

#include <WiFi.h>              //ESP8266 WiFi connection
#include <WiFiClientSecure.h>  // Include WiFiClientSecure for SSL support
#include <HTTPClient.h>        //HTTP Client
#include <base64.h>            // Base64 encoding for authentication
#include <ArduinoJson.h>       // Parsing JSON
#include <time.h>


// MAX7219 DISPLAY LIBRARIES
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>


// Define the number of display devices (8 modules in one row)
#define MAX_DEVICES 8

// Define the hardware type and pins which display connects
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define CS_PIN 5     // LOAD/CS pin
#define DATA_PIN 23  // MOSI
#define CLK_PIN 18   // SCK/CLK


#define SHA256_SIZE 32


// Wi-Fi credentials
const char* ssid = "";
const char* password = "";


// Network configuration
IPAddress static_IP(192, 168, 0, 250);  // Static IP
IPAddress gateway(192, 168, 0, 1);      // Gateway
IPAddress subnet(255, 255, 255, 0);     // Subnet Mask
IPAddress dns1(8, 8, 8, 8);             // DNS #1
IPAddress dns2(192, 168, 0, 1);         // DNS #1


// NTP Server config
#define myNTPServer "time.windows.com"
#define myTimezone "CET-1CEST,M3.5.0,M10.5.0/3"  // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
struct tm timeinfo;


// WooCommerce API endpoint 
const String wooCommerceApiUrl = "";

// WooCommerce REST API credentials
const String consumerKey = "";
const String consumerSecret = "";


unsigned long previousOrdersCountCheck = 0;
long ordersCountCheckInterval = 300000;  // 5 minute
int onHoldOrders = 0;
int processingOrders = 0;
int completedOrders = 0;

// Create a Parola object
MD_Parola matrixDisplay = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
char textToDisplay[50];

void setup() {
  // Start serial communication
  Serial.begin(115200);
  delay(5000);

  // Initialize the display
  matrixDisplay.begin();
  matrixDisplay.setIntensity(0);  // Set brightness to 0 (minimum)
  matrixDisplay.displayClear();


  // Config NTP Time
  configTime(0, 0, myNTPServer);  // Set up NTP
  setenv("TZ", myTimezone, 1);
  tzset();

  //Connect to WiFi
  connectWiFi();


  delay(5000);

  // 1st update of counters
  getOrdersCount();

  // Set up scrolling parameters and display text
  snprintf(textToDisplay, sizeof(textToDisplay), "\xFF %d", onHoldOrders);
  matrixDisplay.displayText(textToDisplay, PA_CENTER, 1, 6000000, PA_SLICE, PA_SLICE);
}


void loop() {

  unsigned long currentMillis = millis();  // Get the current time in milliseconds

  if (matrixDisplay.displayAnimate()) {
    matrixDisplay.displayReset();
  }

  if (currentMillis - previousOrdersCountCheck >= ordersCountCheckInterval) {
    getOrdersCount();
    snprintf(textToDisplay, sizeof(textToDisplay), "\xFF %d", onHoldOrders);
    previousOrdersCountCheck = currentMillis;
  }
}


void connectWiFi() {
  // Configure Wi-Fi with a static IP
  if (!WiFi.config(static_IP, gateway, subnet, dns1, dns2)) {
    Serial.println("Failed to configure static IP ");
  }

  // Connect to Wi-Fi
  Serial.printf("Connecting to %s\n", ssid);
  WiFi.begin(ssid, password);

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  // Connected
  Serial.printf("\nConnected to %s\n", ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.print("Subnet mask: ");
  Serial.println(WiFi.subnetMask());
  Serial.print("Gateway: ");
  Serial.println(WiFi.gatewayIP());
  Serial.print("DNS #1: ");
  Serial.println(WiFi.dnsIP(0));
  Serial.print("DNS #2: ");
  Serial.println(WiFi.dnsIP(1));

  // Print the Wi-Fi auto-connect status
  bool autoConnectStatus = WiFi.getAutoReconnect();
  Serial.print("Auto-connect status: ");
  Serial.println(autoConnectStatus ? "Enabled" : "Disabled");
}


//Update WooCommerce Order stats strings

// Base64 encode consumer key and secret for HTTP Basic Auth - storeOrderInfoUpdate
String getAuthHeader() {
  String auth = consumerKey + ":" + consumerSecret;
  return "Basic " + base64::encode(auth);
}

void getOrdersCount() {
  Serial.println("Run getOrdersCount");
  if (WiFi.status() == WL_CONNECTED) {

    WiFiClientSecure client;  // Use WiFiClientSecure for HTTPS
    client.setInsecure();     // Temporarily disable SSL verification for debugging

    HTTPClient http;

    http.setTimeout(5000);                  // Increase timeout to 30 seconds
    http.begin(client, wooCommerceApiUrl);  // Pass the secure client and URL

    // Add Authorization header
    String authHeader = getAuthHeader();
    http.addHeader("Authorization", authHeader);
    http.addHeader("Content-Type", "application/json");  // Set content type
    http.addHeader("Accept", "application/json");        // Set accepted response type

    int httpCode = http.GET();  // Send GET request
    Serial.print("HTTP response code: ");
    Serial.println(httpCode);


    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();

      // Parse JSON response
      DynamicJsonDocument doc(1024);
      deserializeJson(doc, payload);
      JsonArray orders = doc.as<JsonArray>();

      Serial.println("Orders stats updated: ");
      // Loop through the order status array and filter for 'on-hold' and 'processing'
      for (JsonObject order : orders) {
        String slug = order["slug"];
       int total = order["total"];

        if (slug == "on-hold" || slug == "processing" || slug == "completed") {
          Serial.print(slug);
          Serial.print(": ");
          Serial.println(total);
        }


        if (slug == "on-hold") {
          onHoldOrders = total;
        } else if (slug == "processing") {
          processingOrders = total;
        } else if (slug == "completed") {
          completedOrders = total;
        }
      }


    } else {
      // Log additional error information
      Serial.print("Error on HTTP request: ");
      Serial.println(http.errorToString(httpCode));
      //Serial.println("Response: ");
      //String response = http.getString();
      //Serial.println(response);
    }
    http.end();  // Close connection
  } else {
    Serial.println("WiFi not connected");
  }
}

From looking at your code I did not understand what exactly is the content that you receive inside function getOrdersCount()

Sounds like a part of that content are numbers.
So cast this part of the content into integers and then compare

if (lastNumber != actualNumber) { // check if actualNumber is different from lastNumber
  //updateDisplay
  lastNumber = actualNumber;
}

I receive a int number,

onHoldOrders 

is amount of new orders and that's what I want to print at this moment on my led matrix.

I check API for new orders every 5 minutes. That part works well, it updates the value of variable and shows the same value as on the website.

The problem is I don't fully understand how MD_Parola library works with text.

  matrixDisplay.displayText(textToDisplay, PA_CENTER, 1, 6000000, PA_SLICE, PA_SLICE);

requires 6 arguments:

  • text which should be displayed
  • text aligment
  • animation speed
  • time between in and out animations (pause)
  • in animation type
  • out animation type

No matter what I use in the pause argument the screen does cycle every ~30 seconds doing in animation, displaying text and out animation. Setting it to 600 000 should update the screen every 10 minutes not 30 seconds.

I wanted to do these steps:
Show in animation
Display onHoldOrders value with additional texts (thats what snprintf does adding some chars to onHoldOrders value)
In meantime every 5 minute check API and update the onHoldOrders value if needed
When value is different show out animation then in animation and new value.
Repeat until end of the world :slight_smile:

The reason is the parameter for the pausingtime takes only a 16bit integer-value

inside file MD_Parola.h

  inline void displayText(const char *pText, textPosition_t align, uint16_t speed, uint16_t pause, textEffect_t effectIn, textEffect_t effectOut = PA_NO_EFFECT)
    { displayZoneText(0, pText, align, speed, pause, effectIn, effectOut); }

inline void displayText(const char *pText, textPosition_t align, uint16_t speed, uint16_t pause , textEffect_t effectIn, textEffect_t effectOut = PA_NO_EFFECT)
{ displayZoneText(0, pText, align, speed, pause, effectIn, effectOut); }

and this means any bigger number is reduced to its lower 16 bits.
2^16 - 1 = 65535 dn't know why this number is reduced to 30 seconds

I microcontroller can't read your mind.
other users can't read your mind.

Your description

is still not precise enough to really understand what you really want

especcially
these two lines

Repeat until end of the world
do you want to repeat the animation (until end of the world)?
or
do you want to NOT repeat the animation?

Do you mean
When value is different than the value given from last check API

  1. show out animation
  2. show in animation with new value.
  3. keep text on the display without any kind of animation until

actual value given from the API-check is different than the value given from last API-check ?

My question is:
If this is a display used in a room that can only be seen by the employers or by you
why does it have to have a fancy animation?

I would use an accoustical signal that you can hear in case the number of variable onHoldOrders has changed and then just display the new number without any animation.

If you want to keep the animation you should describe each and every step of what shall be seen on your display

0:00
seen on the display Show in animation
with onHoldOrders value with additional texts

0:01 black display or onHoldOrders standing still and beeing visible?
0:02 black display or onHoldOrders standing still and beeing visible ?
0:03 black display or onHoldOrders standing still and beeing visible ?
0:04 black display or onHoldOrders standing still and beeing visible ?

0:05 new API-Check onHoldOrders stays the same
0:05 black display or onHoldOrders standing still and beeing visible ?
0:06 black display or onHoldOrders standing still and beeing visible ?
0:07 black display or onHoldOrders standing still and beeing visible ?
0:08 black display or onHoldOrders standing still and beeing visible ?
0:09 black display or onHoldOrders standing still and beeing visible ?

0:10 new API-Check onHoldOrders changes
0:10 show out animation
0:10 show in animation with new value
0:11 black display or onHoldOrders standing still and beeing visible ?
0:12 black display or onHoldOrders standing still and beeing visible ?
0:13 black display or onHoldOrders standing still and beeing visible ?
0:14 black display or onHoldOrders standing still and beeing visible ?

0:15 new API-Check onHoldOrders stays the same

Now what exactly do you want?
Without such a precise description of what will be seen on your display
it is not possible to describe what to change in the code

Ok, you're right, I wasn't precise enough.

It is a birthday gift to my wife (after finishing with software part I'll be making some kind of case to make it look pretty). She's running a small store with things like cups, mugs etc. handmade by herself.

I wanted to use visual animation to draw attention - if it plays animation you know the value changed. I wouldn't use any kind of buzzer because it could be annoying sometimes. Also she's kind of visual geek so she likes fancy stuff so that's why.

Here's how I wanted it to work:

0:00 seen on the display Show in animation with onHoldOrders value with additional texts

0:01 onHoldOrders value with additional texts standing still and beeing visible
0:02 onHoldOrders value with additional texts standing still and beeing visible
0:03 onHoldOrders value with additional texts standing still and beeing visible
0:04 onHoldOrders value with additional texts standing still and beeing visible

0:05 new API-Check onHoldOrders stays the same
0:06 onHoldOrders value with additional texts standing still and beeing visible
0:07 onHoldOrders value with additional texts standing still and beeing visible
0:08 onHoldOrders value with additional textsstanding still and beeing visible
0:09 onHoldOrders value with additional texts standing still and beeing visible

0:10 new API-Check onHoldOrders changes
0:10 show out animation
0:10 show in animation with new value of onHoldOrders value with additional texts
0:11 onHoldOrders value with additional texts standing still and beeing visible
0:12 onHoldOrders value with additional texts standing still and beeing visible
0:13 onHoldOrders value with additional texts standing still and beeing visible
0:14 onHoldOrders value with additional texts standing still and beeing visible

0:15 new API-Check onHoldOrders stays the same

Are you sure that this code exact copy from post #3

#include <WiFi.h>              //ESP8266 WiFi connection
#include <WiFiClientSecure.h>  // Include WiFiClientSecure for SSL support
#include <HTTPClient.h>        //HTTP Client
#include <base64.h>            // Base64 encoding for authentication
#include <ArduinoJson.h>       // Parsing JSON
#include <time.h>


// MAX7219 DISPLAY LIBRARIES
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>


// Define the number of display devices (8 modules in one row)
#define MAX_DEVICES 8

// Define the hardware type and pins which display connects
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define CS_PIN 5     // LOAD/CS pin
#define DATA_PIN 23  // MOSI
#define CLK_PIN 18   // SCK/CLK


#define SHA256_SIZE 32


// Wi-Fi credentials
const char* ssid = "";
const char* password = "";


// Network configuration
IPAddress static_IP(192, 168, 0, 250);  // Static IP
IPAddress gateway(192, 168, 0, 1);      // Gateway
IPAddress subnet(255, 255, 255, 0);     // Subnet Mask
IPAddress dns1(8, 8, 8, 8);             // DNS #1
IPAddress dns2(192, 168, 0, 1);         // DNS #1


// NTP Server config
#define myNTPServer "time.windows.com"
#define myTimezone "CET-1CEST,M3.5.0,M10.5.0/3"  // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
struct tm timeinfo;


// WooCommerce API endpoint 
const String wooCommerceApiUrl = "";

// WooCommerce REST API credentials
const String consumerKey = "";
const String consumerSecret = "";


unsigned long previousOrdersCountCheck = 0;
long ordersCountCheckInterval = 300000;  // 5 minute
int onHoldOrders = 0;
int processingOrders = 0;
int completedOrders = 0;

// Create a Parola object
MD_Parola matrixDisplay = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
char textToDisplay[50];

void setup() {
  // Start serial communication
  Serial.begin(115200);
  delay(5000);

  // Initialize the display
  matrixDisplay.begin();
  matrixDisplay.setIntensity(0);  // Set brightness to 0 (minimum)
  matrixDisplay.displayClear();


  // Config NTP Time
  configTime(0, 0, myNTPServer);  // Set up NTP
  setenv("TZ", myTimezone, 1);
  tzset();

  //Connect to WiFi
  connectWiFi();


  delay(5000);

  // 1st update of counters
  getOrdersCount();

  // Set up scrolling parameters and display text
  snprintf(textToDisplay, sizeof(textToDisplay), "\xFF %d", onHoldOrders);
  matrixDisplay.displayText(textToDisplay, PA_CENTER, 1, 6000000, PA_SLICE, PA_SLICE);
}


void loop() {

  unsigned long currentMillis = millis();  // Get the current time in milliseconds

  if (matrixDisplay.displayAnimate()) {
    matrixDisplay.displayReset();
  }

  if (currentMillis - previousOrdersCountCheck >= ordersCountCheckInterval) {
    getOrdersCount();
    snprintf(textToDisplay, sizeof(textToDisplay), "\xFF %d", onHoldOrders);
    previousOrdersCountCheck = currentMillis;
  }
}


void connectWiFi() {
  // Configure Wi-Fi with a static IP
  if (!WiFi.config(static_IP, gateway, subnet, dns1, dns2)) {
    Serial.println("Failed to configure static IP ");
  }

  // Connect to Wi-Fi
  Serial.printf("Connecting to %s\n", ssid);
  WiFi.begin(ssid, password);

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  // Connected
  Serial.printf("\nConnected to %s\n", ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.print("Subnet mask: ");
  Serial.println(WiFi.subnetMask());
  Serial.print("Gateway: ");
  Serial.println(WiFi.gatewayIP());
  Serial.print("DNS #1: ");
  Serial.println(WiFi.dnsIP(0));
  Serial.print("DNS #2: ");
  Serial.println(WiFi.dnsIP(1));

  // Print the Wi-Fi auto-connect status
  bool autoConnectStatus = WiFi.getAutoReconnect();
  Serial.print("Auto-connect status: ");
  Serial.println(autoConnectStatus ? "Enabled" : "Disabled");
}


//Update WooCommerce Order stats strings

// Base64 encode consumer key and secret for HTTP Basic Auth - storeOrderInfoUpdate
String getAuthHeader() {
  String auth = consumerKey + ":" + consumerSecret;
  return "Basic " + base64::encode(auth);
}

void getOrdersCount() {
  Serial.println("Run getOrdersCount");
  if (WiFi.status() == WL_CONNECTED) {

    WiFiClientSecure client;  // Use WiFiClientSecure for HTTPS
    client.setInsecure();     // Temporarily disable SSL verification for debugging

    HTTPClient http;

    http.setTimeout(5000);                  // Increase timeout to 30 seconds
    http.begin(client, wooCommerceApiUrl);  // Pass the secure client and URL

    // Add Authorization header
    String authHeader = getAuthHeader();
    http.addHeader("Authorization", authHeader);
    http.addHeader("Content-Type", "application/json");  // Set content type
    http.addHeader("Accept", "application/json");        // Set accepted response type

    int httpCode = http.GET();  // Send GET request
    Serial.print("HTTP response code: ");
    Serial.println(httpCode);


    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();

      // Parse JSON response
      DynamicJsonDocument doc(1024);
      deserializeJson(doc, payload);
      JsonArray orders = doc.as<JsonArray>();

      Serial.println("Orders stats updated: ");
      // Loop through the order status array and filter for 'on-hold' and 'processing'
      for (JsonObject order : orders) {
        String slug = order["slug"];
       int total = order["total"];

        if (slug == "on-hold" || slug == "processing" || slug == "completed") {
          Serial.print(slug);
          Serial.print(": ");
          Serial.println(total);
        }


        if (slug == "on-hold") {
          onHoldOrders = total;
        } else if (slug == "processing") {
          processingOrders = total;
        } else if (slug == "completed") {
          completedOrders = total;
        }
      }


    } else {
      // Log additional error information
      Serial.print("Error on HTTP request: ");
      Serial.println(http.errorToString(httpCode));
      //Serial.println("Response: ");
      //String response = http.getString();
      //Serial.println(response);
    }
    http.end();  // Close connection
  } else {
    Serial.println("WiFi not connected");
  }
}

does show any animation at all?
I tried to strip down to just the animation in a WOKWI-Simulation but nothing happends

From calling

no text appears on the dotmatrix

@myousic

From what I can deduce, you are wanting a conditional method to control the animation in and out of the display. I have hacked up a wokwi which I hope you can pick the bones out of and fit to your sketch.

I took the WOKWI-Simulation from user @indev2 and added serial printing to make visible how the code works and what the code is doing

1 Like

@StefanL38

Yes, code works on my matrix, shows in animation, waits max 65 secs (its max value as you suggested, I tested what value is saved in pause with getPause()) and shows out animation then the cycle repeats.

I watched some youtube video about Wokwi simulation of that display and the code needed some changes to make it work on Wokwi.

@indev2 @StefanL38
Thanks guys, I'm gonna do some testing and come back with my conclusion :slight_smile:

Seems to me that you just want to display the last read data rather than create an animation. You can suppress the exit animation by specifying PA_NO_EFFECT. This will make the display stay on the same message until you send it the next one.

displayText(textToDisplay, PA_CENTER, 5, 0, PA_SLICE, PA_NO_EFFECT);

Please refer to the documentation for the library in the docs section of the library folder.

@indev2 @StefanL38

I've tested it and it works great and exactly as expected so dividing it into two separate animations was the way. Thank you very much for help :slight_smile:

I did some testing and based on your code I've also added some welcome screen and simple http server to change brightness, animation speed and API check interval. I left the display turned on for half day and worked well :slight_smile:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.