adafruit / Adafruit_Protomatter

RGB matrix library for Arduino
57 stars 20 forks source link

MatrixPortal hangs on network I/O using Protomatter v.1.5.2 and later #56

Closed jonbo372 closed 1 year ago

jonbo372 commented 1 year ago

CircuitPython version

Adafruit CircuitPython 8.0.0-beta.6
Adafruit Matrix Portal M4
Nina FW 1.7.4
Adafruit Protomatter v1.5.2

Code/REPL

#include <Arduino.h>

#include <WiFiNINA.h>

// It is enough to include the Protomatter library for things to
// go south.
#include <Adafruit_Protomatter.h>

char ssid[] = "INSERT_YOUR_SSID_HERE";
char pass[] = "INSERT_YOUR_PASSWORD_HERE";

WiFiServer* server;

int clientCount = 0;

void attachToWifi() {
    uint8_t status = WiFi.status();
    while (status != WL_CONNECTED) {
        Serial.println("Waiting for network");
        status = WiFi.begin(ssid, pass);

        if (status == WL_CONNECTED) {
            Serial.println("We are connected");
            break;
        }

        delay(10000);
    }
}

/**
 * Copied/adapted from WifiNINA examples.
 */
void process() {
    WiFiClient client = server->available();

    if (client) {
        ++clientCount;

        boolean currentLineIsBlank = true;
        while (client.connected()) {
            if (client.available()) {
                char c = client.read();
                if (c == '\n' && currentLineIsBlank) {
                    client.println("HTTP/1.1 200 OK");
                    client.println("Content-Type: text/plain");
                    client.println("Connection: close");  // the connection will be closed after completion of the response
                    client.println();
                    client.print(clientCount);
                    client.println(" Hello World");
                    break;
                }
                if (c == '\n') {
                    currentLineIsBlank = true;
                } else if (c != '\r') {
                    currentLineIsBlank = false;
                }
            }
        }
        delay(1);
        client.stop();
    }
}

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
    Serial.begin(9600);
    attachToWifi();
    server = new WiFiServer(80);
    server->begin();
}

void loop() {
    process();
    delay(50);
}

Behavior

Setup:

  1. Using WifiNINA as a simple web server
  2. Fetch content in a loop from the web server while [ true ]; do curl http://<insert_ip_here>; sleep 1; done
  3. After a short number of loops, the board will no longer process network traffic (usually less than 20)

Description

The observed behavior is the exact same one as reported in https://github.com/adafruit/circuitpython/issues/6205 (except no USB disconnect). And as reported in that issue, it is enough to include the Adafruit_Protomatter.h file to observe this behavior. Downgrading to v1.5.1 seems to do the trick and there has been DMA related updates in the later versions (which seems to have been the root cause of the bug https://github.com/adafruit/samd-peripherals/pull/42).

Additional information

Running platform.io with the following settings:

[env:adafruit_matrix_portal_m4]
platform = atmelsam
board = adafruit_matrix_portal_m4
framework = arduino

upload_port = /dev/ttyACM0

lib_deps = adafruit/Adafruit Protomatter@^1.5.2
           WiFiNINA=https://github.com/adafruit/WiFiNINA/archive/master.zip
PaintYourDragon commented 1 year ago

Using the Arduino example, I’m unable to reproduce this problem; has been running successfully for several hundred cycles. Latest WiFiNINA and Adafruit_Protomatter libraries. NINA firmware 1.7.1 and 1.7.4.

Protomatter changes in 1.5.2 should be irrelevant to M4, as the affected lines are conditionally compiled on ESP32-S3 only.

Just speculating here that the Protomatter update might be a red herring and the failure at that point is coincidental, in which case I’d suspect power issues. Try fully disconnecting the MatrixPortal board from the matrix — no data, no power, just MatrixPortal->USB->computer — and run the polling script again. Any change?

jonbo372 commented 1 year ago

Hey, thanks for taking a look at yeah, the Protomatter update is probably a red herring. I've been able to reproduce the behavior on any version and I'm starting to think it is just related to wifi being dropped. I've must have gotten extremely unlucky initially where the behavior only seemed to manifest itself on the later versions of Protomatter. Anyway, I added some defensive code around wifi connectivity and when there is a drop, it restarts wifi + the http server and I've had it running for over a million HTTP GETs on either versions now.

Anyway, I will close this one out as a no bug and rather user error. Just in case someone else wants to test, including my new sketch with the wifi reconnection logic.

jonbo372 commented 1 year ago
#include <Arduino.h>

#include <WiFiNINA.h>

// It is enough to include the Protomatter library for things to
// go south.
#include <Adafruit_Protomatter.h>

char ssid[] = "insert_your_ssid_here";
char pass[] = "insert_your_passsword_here";

WiFiServer* server;

int clientCount = 0;

int progressCount = 0;
int wifiLostCount = 0;
int lastTsWifiChecked;
const int wifiCheckDelay = 2000;

void log(const char* msg) {
    Serial.println(msg);
}

void log(const Printable& p) {
    Serial.println(p);
}

bool connectWifi(bool reStart) {
    if (reStart) {
        log("Killing WiFi");
        server->begin();
        WiFi.end();
    }

    log("Connecting to WiFi");

    uint8_t status = WiFi.status();
    while (status != WL_CONNECTED) {
        log("Waiting for network");
        status = WiFi.begin(ssid, pass);

        if (status == WL_CONNECTED) {
            log("Connected with IP: ");
            log(WiFi.localIP());
            return true;
        }

        // wait 10 seconds for connection:
        delay(10000);
    }
    return true;
}

bool checkWifiStatus() {
    const unsigned long diff = millis() - lastTsWifiChecked;
    if (diff < wifiCheckDelay) {
        return true;
    }
    lastTsWifiChecked = millis();

    switch (WiFi.status()) {
        case WL_NO_SHIELD:
            log("    WL_NO_SHIELD");
            return false;
        case WL_IDLE_STATUS:
            log("    WL_IDLE_STATUS");
            return false;
        case WL_NO_SSID_AVAIL:
            log("    WL_NO_SSID_AVAIL");
            return false;
        case WL_SCAN_COMPLETED:
            log("    WL_SCAN_COMPLETED");
            return false;
        case WL_CONNECTED:
            // log("    WL_CONNECTED");
            return true;
        case WL_CONNECT_FAILED:
            log("    WL_CONNECT_FAILED");
            return false;
        case WL_CONNECTION_LOST:
            log("    WL_CONNECTION_LOST");
            return false;
        case WL_DISCONNECTED:
            log("    WL_DISCONNECTED");
            return false;
        case WL_AP_LISTENING:
            log("    WL_AP_LISTENING");
            return false;
        case WL_AP_CONNECTED:
            log("    WL_AP_CONNECTED");
            return false;
        case WL_AP_FAILED:
            log("    WL_AP_FAILED");
            return false;
        default:
            return false;
    }
}

/**
 * Copied/adapted from WifiNINA examples.
 */
void process() {
    WiFiClient client = server->available();

        if (client) {
            ++clientCount;

            boolean currentLineIsBlank = true;
            while (client.connected()) {
                if (client.available()) {
                    char c = client.read();
                if (c == '\n' && currentLineIsBlank) {
                    client.println("HTTP/1.1 200 OK");
                    client.println("Content-Type: text/plain");
                    client.println("Connection: close");  // the connection will be closed after completion of the response
                    client.println();
                    client.print(clientCount);
                    client.println(" Hello World");
                    break;
                }
                if (c == '\n') {
                    currentLineIsBlank = true;
                } else if (c != '\r') {
                    currentLineIsBlank = false;
                }
            }
        }
        delay(1);
        client.stop();
    }
}

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
    Serial.begin(9600);
    connectWifi(false);
    server = new WiFiServer(80);
    server->begin();
}

void loop() {
    if (checkWifiStatus() == false) {
        ++wifiLostCount;
        log("Lost WiFi, reconnecting. Count: ");
        log(wifiLostCount);
        if (connectWifi(true)) {
            log("WiFi reconnected. Starting HTTP Server again");
            server->begin();
        }
    }
    process();
    delay(50);
}
jonbo372 commented 1 year ago

Unable to reproduce. May be related to spotty wifi. Closing.