diff --git a/.github/workflows/build-clang-doxy.yml b/.github/workflows/build-clang-doxy.yml index 5b08dd95f..8c16aa67e 100644 --- a/.github/workflows/build-clang-doxy.yml +++ b/.github/workflows/build-clang-doxy.yml @@ -4,6 +4,7 @@ name: WipperSnapper Build CI on: + workflow_dispatch: pull_request: workflow_call: secrets: @@ -192,7 +193,7 @@ jobs: strategy: fail-fast: false matrix: - arduino-platform: ["pyportal_tinyusb", "metro_m4_airliftlite_tinyusb"] + arduino-platform: ["pyportal_tinyusb", "pyportal_titano_tinyusb", "metro_m4_airliftlite_tinyusb"] steps: - uses: actions/setup-python@v4 with: diff --git a/examples/Wippersnapper_NoFS/.pyportal_titano_tinyusb.test.skip b/examples/Wippersnapper_NoFS/.pyportal_titano_tinyusb.test.skip new file mode 100644 index 000000000..b28b04f64 --- /dev/null +++ b/examples/Wippersnapper_NoFS/.pyportal_titano_tinyusb.test.skip @@ -0,0 +1,3 @@ + + + diff --git a/examples/Wippersnapper_demo/.pyportal_titano_tinyusb.generate b/examples/Wippersnapper_demo/.pyportal_titano_tinyusb.generate new file mode 100644 index 000000000..b28b04f64 --- /dev/null +++ b/examples/Wippersnapper_demo/.pyportal_titano_tinyusb.generate @@ -0,0 +1,3 @@ + + + diff --git a/examples/wippersnapper_debug/.pyportal_titano_tinyusb.test.skip b/examples/wippersnapper_debug/.pyportal_titano_tinyusb.test.skip new file mode 100644 index 000000000..b28b04f64 --- /dev/null +++ b/examples/wippersnapper_debug/.pyportal_titano_tinyusb.test.skip @@ -0,0 +1,3 @@ + + + diff --git a/library.properties b/library.properties index 6d9894192..ffeb83110 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Adafruit WipperSnapper -version=1.0.0-beta.75 +version=1.0.0-beta.76 author=Adafruit maintainer=Adafruit sentence=Arduino application for Adafruit.io WipperSnapper diff --git a/platformio.ini b/platformio.ini index de3997db1..450086e0e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -233,6 +233,12 @@ build_flags = -DUSE_TINYUSB=1 [env:adafruit_pyportal_m4_titano] extends = common:atsamd board = adafruit_pyportal_m4_titano +; build_type = debug +; upload_protocol = jlink +; debug_tool = jlink +; monitor_port = jlink +; debug_init_break = +lib_ignore = USBHost build_flags = -DUSE_TINYUSB=1 -DADAFRUIT_PYPORTAL_M4_TITANO diff --git a/src/Wippersnapper.cpp b/src/Wippersnapper.cpp index 4a39741c9..fd6ff3292 100644 --- a/src/Wippersnapper.cpp +++ b/src/Wippersnapper.cpp @@ -2306,9 +2306,13 @@ void Wippersnapper::errorWriteHang(String error) { WS_DEBUG_PRINTLN(error); #ifdef USE_TINYUSB _fileSystem->writeToBootOut(error.c_str()); + TinyUSBDevice.attach(); + delay(500); #endif // Signal and hang forever while (1) { + WS_DEBUG_PRINTLN("ERROR: Halted execution"); + WS_DEBUG_PRINTLN(error.c_str()); WS.feedWDT(); statusLEDBlink(WS_LED_STATUS_ERROR_RUNTIME); delay(1000); diff --git a/src/Wippersnapper.h b/src/Wippersnapper.h index ec38eda75..57154957d 100644 --- a/src/Wippersnapper.h +++ b/src/Wippersnapper.h @@ -90,7 +90,7 @@ #endif #define WS_VERSION \ - "1.0.0-beta.75" ///< WipperSnapper app. version (semver-formatted) + "1.0.0-beta.76" ///< WipperSnapper app. version (semver-formatted) // Reserved Adafruit IO MQTT topics #define TOPIC_IO_THROTTLE "/throttle" ///< Adafruit IO Throttle MQTT Topic diff --git a/src/Wippersnapper_Boards.h b/src/Wippersnapper_Boards.h index 0c3273f4c..98ec0b3a9 100644 --- a/src/Wippersnapper_Boards.h +++ b/src/Wippersnapper_Boards.h @@ -22,6 +22,12 @@ #define USE_STATUS_NEOPIXEL #define STATUS_NEOPIXEL_PIN 2 #define STATUS_NEOPIXEL_NUM 1 +#elif defined(ADAFRUIT_PYPORTAL_M4_TITANO) +#define BOARD_ID "pyportal-titano-tinyusb" +#define USE_TINYUSB +#define USE_STATUS_NEOPIXEL +#define STATUS_NEOPIXEL_PIN 2 +#define STATUS_NEOPIXEL_NUM 1 #elif defined(ADAFRUIT_METRO_M4_AIRLIFT_LITE) #define BOARD_ID "metro-m4-airliftlite-tinyusb" #define USE_TINYUSB diff --git a/src/Wippersnapper_Networking.h b/src/Wippersnapper_Networking.h index 7bbe3b1df..5e401a4ff 100644 --- a/src/Wippersnapper_Networking.h +++ b/src/Wippersnapper_Networking.h @@ -16,8 +16,9 @@ #ifndef WIPPERSNAPPER_NETWORKING_H #define WIPPERSNAPPER_NETWORKING_H -#if defined(ADAFRUIT_METRO_M4_AIRLIFT_LITE) || defined(ADAFRUIT_PYPORTAL) || \ - defined(ADAFRUIT_METRO_M4_EXPRESS) || defined(USE_AIRLIFT) +#if defined(ADAFRUIT_METRO_M4_EXPRESS) || \ + defined(ADAFRUIT_METRO_M4_AIRLIFT_LITE) || defined(ADAFRUIT_PYPORTAL) || \ + defined(ADAFRUIT_PYPORTAL_M4_TITANO) || defined(USE_AIRLIFT) #include "network_interfaces/Wippersnapper_AIRLIFT.h" /** Nina-FW (adafruit fork) networking class */ typedef Wippersnapper_AIRLIFT Wippersnapper_WiFi; diff --git a/src/provisioning/littlefs/WipperSnapper_LittleFS.cpp b/src/provisioning/littlefs/WipperSnapper_LittleFS.cpp index 337f1070d..34bcd0767 100644 --- a/src/provisioning/littlefs/WipperSnapper_LittleFS.cpp +++ b/src/provisioning/littlefs/WipperSnapper_LittleFS.cpp @@ -28,10 +28,8 @@ WipperSnapper_LittleFS::WipperSnapper_LittleFS() { // Attempt to initialize filesystem if (!LittleFS.begin()) { - WS_DEBUG_PRINTLN("ERROR: Failure initializing LittleFS!"); setStatusLEDColor(RED); - while (1) - ; + fsHalt("ERROR: Failure initializing LittleFS!"); } } @@ -51,25 +49,22 @@ WipperSnapper_LittleFS::~WipperSnapper_LittleFS() { LittleFS.end(); } void WipperSnapper_LittleFS::parseSecrets() { // Check if `secrets.json` file exists on FS if (!LittleFS.exists("/secrets.json")) { - WS_DEBUG_PRINTLN("ERROR: No secrets.json found on filesystem - did you " - "upload credentials?"); - fsHalt(); + fsHalt("ERROR: No secrets.json found on filesystem - did you upload " + "credentials?"); } // Attempt to open secrets.json file for reading File secretsFile = LittleFS.open("/secrets.json", "r"); if (!secretsFile) { - WS_DEBUG_PRINTLN("ERROR: Could not open secrets.json file for reading!"); - fsHalt(); + fsHalt("ERROR: Could not open secrets.json file for reading!"); } // Attempt to deserialize the file's JSON document JsonDocument doc; DeserializationError error = deserializeJson(doc, secretsFile); if (error) { - WS_DEBUG_PRINT("ERROR: deserializeJson() failed with code "); - WS_DEBUG_PRINTLN(error.c_str()); - fsHalt(); + fsHalt(String("ERROR: deserializeJson() failed with code ") + + error.c_str()); } // Extract a config struct from the JSON document @@ -78,18 +73,16 @@ void WipperSnapper_LittleFS::parseSecrets() { // Validate the config struct is not filled with default values if (strcmp(WS._config.aio_user, "YOUR_IO_USERNAME_HERE") == 0 || strcmp(WS._config.aio_key, "YOUR_IO_KEY_HERE") == 0) { - WS_DEBUG_PRINTLN( + fsHalt( "ERROR: Invalid IO credentials in secrets.json! TO FIX: Please change " "io_username and io_key to match your Adafruit IO credentials!\n"); - fsHalt(); } if (strcmp(WS._config.network.ssid, "YOUR_WIFI_SSID_HERE") == 0 || strcmp(WS._config.network.pass, "YOUR_WIFI_PASS_HERE") == 0) { - WS_DEBUG_PRINTLN("ERROR: Invalid network credentials in secrets.json! TO " - "FIX: Please change network_ssid and network_password to " - "match your Adafruit IO credentials!\n"); - fsHalt(); + fsHalt("ERROR: Invalid network credentials in secrets.json! TO FIX: Please " + "change network_ssid and network_password to match your Adafruit IO " + "credentials!\n"); } // Close the file @@ -99,11 +92,21 @@ void WipperSnapper_LittleFS::parseSecrets() { LittleFS.end(); } -void WipperSnapper_LittleFS::fsHalt() { +/**************************************************************************/ +/*! + @brief Halts execution and blinks the status LEDs yellow. + @param msg + Error message to print to serial console. +*/ +/**************************************************************************/ +void WipperSnapper_LittleFS::fsHalt(String msg) { + statusLEDSolid(WS_LED_STATUS_FS_WRITE); while (1) { - statusLEDSolid(WS_LED_STATUS_FS_WRITE); + WS_DEBUG_PRINTLN("Fatal Error: Halted execution!"); + WS_DEBUG_PRINTLN(msg.c_str()); delay(1000); yield(); } } + #endif \ No newline at end of file diff --git a/src/provisioning/littlefs/WipperSnapper_LittleFS.h b/src/provisioning/littlefs/WipperSnapper_LittleFS.h index d96b53768..782e8569b 100644 --- a/src/provisioning/littlefs/WipperSnapper_LittleFS.h +++ b/src/provisioning/littlefs/WipperSnapper_LittleFS.h @@ -33,7 +33,7 @@ class WipperSnapper_LittleFS { WipperSnapper_LittleFS(); ~WipperSnapper_LittleFS(); void parseSecrets(); - void fsHalt(); + void fsHalt(String msg); }; extern Wippersnapper WS; diff --git a/src/provisioning/tinyusb/Wippersnapper_FS.cpp b/src/provisioning/tinyusb/Wippersnapper_FS.cpp index 1adf8de22..1aefeea14 100644 --- a/src/provisioning/tinyusb/Wippersnapper_FS.cpp +++ b/src/provisioning/tinyusb/Wippersnapper_FS.cpp @@ -13,7 +13,7 @@ * */ #if defined(ARDUINO_MAGTAG29_ESP32S2) || defined(ARDUINO_METRO_ESP32S2) || \ - defined(ARDUINO_FUNHOUSE_ESP32S2) || \ + defined(ARDUINO_FUNHOUSE_ESP32S2) || defined(ADAFRUIT_PYPORTAL_M4_TITANO) || \ defined(ADAFRUIT_METRO_M4_AIRLIFT_LITE) || defined(ADAFRUIT_PYPORTAL) || \ defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2) || \ defined(ARDUINO_ADAFRUIT_QTPY_ESP32S2) || \ @@ -95,11 +95,9 @@ Wippersnapper_FS::Wippersnapper_FS() { // If a filesystem does not already exist - attempt to initialize a new // filesystem - if (!initFilesystem()) { - WS_DEBUG_PRINTLN("ERROR Initializing Filesystem"); + if (!initFilesystem() && !initFilesystem(true)) { setStatusLEDColor(RED); - while (1) - ; + fsHalt("ERROR Initializing Filesystem"); } // Initialize USB-MSD @@ -107,7 +105,7 @@ Wippersnapper_FS::Wippersnapper_FS() { // If we created a new filesystem, halt until user RESETs device. if (_freshFS) - fsHalt(); + fsHalt("New filesystem created! Press the reset button on your board."); } /************************************************************/ @@ -120,16 +118,18 @@ Wippersnapper_FS::~Wippersnapper_FS() {} /**************************************************************************/ /*! @brief Initializes the flash filesystem. + @param force_format + If true, forces a new filesystem to be created. [DESTRUCTIVE] @return True if filesystem initialized correctly, false otherwise. */ /**************************************************************************/ -bool Wippersnapper_FS::initFilesystem() { +bool Wippersnapper_FS::initFilesystem(bool force_format) { // Init. flash library if (!flash.begin()) return false; // Check if FS exists - if (!wipperFatFs.begin(&flash)) { + if (force_format || !wipperFatFs.begin(&flash)) { // No filesystem exists - create a new FS // NOTE: THIS WILL ERASE ALL DATA ON THE FLASH if (!makeFilesystem()) @@ -325,7 +325,7 @@ void Wippersnapper_FS::createSecretsFile() { "Please edit it to reflect your Adafruit IO and network credentials. " "When you're done, press RESET on the board."); #endif - fsHalt(); + fsHalt("ERROR: Please edit the secrets.json file. Then, reset your board."); } /**************************************************************************/ @@ -337,17 +337,14 @@ void Wippersnapper_FS::parseSecrets() { // Attempt to open the secrets.json file for reading File32 secretsFile = wipperFatFs.open("/secrets.json"); if (!secretsFile) { - WS_DEBUG_PRINTLN("ERROR: Could not open secrets.json file for reading!"); - fsHalt(); + fsHalt("ERROR: Could not open secrets.json file for reading!"); } // Attempt to deserialize the file's JSON document JsonDocument doc; DeserializationError error = deserializeJson(doc, secretsFile); if (error) { - WS_DEBUG_PRINT("ERROR: deserializeJson() failed with code "); - WS_DEBUG_PRINTLN(error.c_str()); - fsHalt(); + fsHalt(String("ERROR: Unable to parse secrets.json file - deserializeJson() failed with code") + error.c_str()); } // Extract a config struct from the JSON document @@ -362,7 +359,7 @@ void Wippersnapper_FS::parseSecrets() { "The \"io_username/io_key\" fields within secrets.json are invalid, please " "change it to match your Adafruit IO credentials. Then, press RESET."); #endif - fsHalt(); + fsHalt("ERROR: Invalid IO credentials in secrets.json! TO FIX: Please change io_username and io_key to match your Adafruit IO credentials!"); } if (strcmp(WS._config.network.ssid, "YOUR_WIFI_SSID_HERE") == 0 || strcmp(WS._config.network.pass, "YOUR_WIFI_PASS_HERE") == 0) { @@ -373,7 +370,7 @@ void Wippersnapper_FS::parseSecrets() { "The \"network_ssid and network_password\" fields within secrets.json are invalid, please " "change it to match your WiFi credentials. Then, press RESET."); #endif - fsHalt(); + fsHalt("ERROR: Invalid network credentials in secrets.json! TO FIX: Please change network_ssid and network_password to match your Adafruit IO credentials!"); } // Close secrets.json file @@ -396,17 +393,25 @@ void Wippersnapper_FS::writeToBootOut(PGM_P str) { bootFile.close(); } else { WS_DEBUG_PRINTLN("ERROR: Unable to open wipper_boot_out.txt for logging!"); + // feels like we should check why, if good use-case ok, otherwise fsHalt + // as indicates fs corruption or disc access issue (maybe latter is okay) } } /**************************************************************************/ /*! @brief Halts execution and blinks the status LEDs yellow. + @param msg + Error message to print to serial console. */ /**************************************************************************/ -void Wippersnapper_FS::fsHalt() { +void Wippersnapper_FS::fsHalt(String msg) { + TinyUSBDevice.attach(); + delay(500); + statusLEDSolid(WS_LED_STATUS_FS_WRITE); while (1) { - // statusLEDSolid(WS_LED_STATUS_FS_WRITE); + WS_DEBUG_PRINTLN("Fatal Error: Halted execution!"); + WS_DEBUG_PRINTLN(msg.c_str()); delay(1000); yield(); } @@ -432,8 +437,7 @@ void Wippersnapper_FS::createDisplayConfig() { // Create and fill JSON document from displayConfig JsonDocument doc; if (!doc.set(displayConfig)) { - WS_DEBUG_PRINTLN("ERROR: Unable to set displayConfig, no space in arduinoJSON document!"); - fsHalt(); + fsHalt("ERROR: Unable to set displayConfig, no space in arduinoJSON document!"); } // Write the file out to the filesystem serializeJsonPretty(doc, displayFile); @@ -454,18 +458,14 @@ void Wippersnapper_FS::parseDisplayConfig(displayConfig &dispCfg) { // Attempt to open file for JSON parsing File32 file = wipperFatFs.open("/display_config.json", FILE_READ); if (!file) { - WS_DEBUG_PRINTLN( - "FATAL ERROR: Unable to open display_config.json for parsing"); - fsHalt(); + fsHalt("FATAL ERROR: Unable to open display_config.json for parsing"); } // Attempt to deserialize the file's json document JsonDocument doc; DeserializationError error = deserializeJson(doc, file); if (error) { - WS_DEBUG_PRINTLN("deserializeJson() of display file failed, rc:") - Serial.println(error.c_str()); - fsHalt(); + fsHalt(String("FATAL ERROR: Unable to parse display_config.json - deserializeJson() failed with code") + error.c_str()); } // Close the file, we're done with it file.close(); diff --git a/src/provisioning/tinyusb/Wippersnapper_FS.h b/src/provisioning/tinyusb/Wippersnapper_FS.h index f89d6e2ee..22fe7df27 100644 --- a/src/provisioning/tinyusb/Wippersnapper_FS.h +++ b/src/provisioning/tinyusb/Wippersnapper_FS.h @@ -44,7 +44,7 @@ class Wippersnapper_FS { Wippersnapper_FS(); ~Wippersnapper_FS(); - bool initFilesystem(); + bool initFilesystem(bool force_format = false); void initUSBMSC(); void eraseCPFS(); @@ -54,7 +54,7 @@ class Wippersnapper_FS { void createSecretsFile(); bool createBootFile(); void writeToBootOut(PGM_P str); - void fsHalt(); + void fsHalt(String msg); void parseSecrets();