diff --git a/platformio.ini b/platformio.ini index 2f363325..dda89778 100644 --- a/platformio.ini +++ b/platformio.ini @@ -201,12 +201,12 @@ build_flags = -D FT_MOONLIGHT=1 -D FT_MONITOR=1 -D EFFECTS_STACK_SIZE=3072 ; psramFound() ? 4 * 1024 : 3 * 1024 - -D DRIVERS_STACK_SIZE=4096 ; psramFound() ? 4 * 1024 : 3 * 1024, 4096 is sufficient for now + -D DRIVERS_STACK_SIZE=6144 ; psramFound() ? 4 * 1024 : 3 * 1024, 4096 is sufficient for now. Update: due to FastLED audio I had to increase to 6144 (might want to move audio to a separate task) ; -D FASTLED_TESTING ; causes duplicate definition of initSpiHardware(); - workaround: removed implementation in spi_hw_manager_esp32.cpp.hpp - -D FASTLED_BUILD=\"20260221\" + -D FASTLED_BUILD=\"20260223\" lib_deps = - https://github.com/FastLED/FastLED#9d0b0eb9b5e59e4093982e0c2bdcfdff72ca80cb ; master 20260221 + https://github.com/FastLED/FastLED#d03ffd69c68f1a00f883243f78b2a0e9bfb66298 ; master 20260223 https://github.com/ewowi/WLED-sync#25f280b5e8e47e49a95282d0b78a5ce5301af4fe ; sourceIP + fftUdp.clear() if arduino >=3 (20251104) ; 💫 currently only enabled on s3 as esp32dev runs over 100% diff --git a/src/MoonBase/Nodes.h b/src/MoonBase/Nodes.h index f13272bd..6525fe96 100644 --- a/src/MoonBase/Nodes.h +++ b/src/MoonBase/Nodes.h @@ -268,7 +268,7 @@ class LiveScriptNode : public Node { // layout void onLayout() override; // call map in LiveScript - ~LiveScriptNode(); + ~LiveScriptNode() override; // LiveScript functions void compileAndRun(); @@ -346,6 +346,16 @@ static struct SharedData { size_t clientListSize; Coord3D gravity; + + // FastLED Audio + bool vocalsActive = false; + float vocalConfidence = 0.0f; + float bassLevel = 0.0f; + float midLevel = 0.0f; + float trebleLevel = 0.0f; + bool beat = false; + uint8_t percussionType = UINT8_MAX; + } sharedData; /** @@ -359,10 +369,11 @@ static struct SharedData { #include "MoonLight/Nodes/Drivers/D_ArtnetIn.h" #include "MoonLight/Nodes/Drivers/D_ArtnetOut.h" #include "MoonLight/Nodes/Drivers/D_AudioSync.h" - #include "MoonLight/Nodes/Drivers/D_FastLED.h" + #include "MoonLight/Nodes/Drivers/D_FastLEDAudio.h" + #include "MoonLight/Nodes/Drivers/D_FastLEDDriver.h" #include "MoonLight/Nodes/Drivers/D_Hub75.h" - #include "MoonLight/Nodes/Drivers/D_Infrared.h" #include "MoonLight/Nodes/Drivers/D_IMU.h" + #include "MoonLight/Nodes/Drivers/D_Infrared.h" #include "MoonLight/Nodes/Drivers/D_ParallelLEDDriver.h" #include "MoonLight/Nodes/Drivers/D__Sandbox.h" #include "MoonLight/Nodes/Effects/E_FastLED.h" diff --git a/src/MoonLight/Modules/ModuleDrivers.h b/src/MoonLight/Modules/ModuleDrivers.h index 234533d3..d4ae53d1 100644 --- a/src/MoonLight/Modules/ModuleDrivers.h +++ b/src/MoonLight/Modules/ModuleDrivers.h @@ -97,10 +97,12 @@ class ModuleDrivers : public NodeManager { addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); // Drivers, Most used first addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); addControlValue(control, getNameAndTags()); @@ -135,10 +137,12 @@ class ModuleDrivers : public NodeManager { if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); // Drivers most used first if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); if (!node) node = checkAndAlloc(name); diff --git a/src/MoonLight/Modules/ModuleEffects.h b/src/MoonLight/Modules/ModuleEffects.h index e21cbba9..dddb666b 100644 --- a/src/MoonLight/Modules/ModuleEffects.h +++ b/src/MoonLight/Modules/ModuleEffects.h @@ -170,6 +170,7 @@ class ModuleEffects : public NodeManager { // FastLED effects addControlValue(control, getNameAndTags()); + addControlValue(control, getNameAndTags()); // Moving head effects, alphabetically addControlValue(control, getNameAndTags()); @@ -291,6 +292,7 @@ class ModuleEffects : public NodeManager { // FastLED if (!node) node = checkAndAlloc(name); + if (!node) node = checkAndAlloc(name); // Moving head effects, alphabetically diff --git a/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h b/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h new file mode 100644 index 00000000..b2b7ef0b --- /dev/null +++ b/src/MoonLight/Nodes/Drivers/D_FastLEDAudio.h @@ -0,0 +1,205 @@ +/** + @title MoonLight + @file D_FastLEDAudio.h + @repo https://github.com/MoonModules/MoonLight, submit changes to this file as PRs + @Authors https://github.com/MoonModules/MoonLight/commits/main + @Doc https://moonmodules.org/MoonLight/moonlight/overview/ + @Copyright © 2026 Github MoonLight Commit Authors + @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 + @license For non GPL-v3 usage, commercial licenses must be purchased. Contact us for more information. +**/ + +#pragma once + +#if FT_MOONLIGHT + + #include "fl/audio.h" + #include "fl/audio/audio_processor.h" + #include "fl/audio_input.h" + #include "fl/time_alpha.h" + +// https://github.com/FastLED/FastLED/blob/master/src/fl/audio/README.md + +class FastLEDAudioDriver : public Node { + private: + // Member variables for audio configuration + fl::AudioConfigI2S* i2sConfig = nullptr; + fl::AudioConfig* config = nullptr; + fl::shared_ptr audioInput; + + public: + static const char* name() { return "FastLED Audio"; } + static uint8_t dim() { return _NoD; } + static const char* tags() { return "☸️"; } + + fl::AudioProcessor audioProcessor; + + update_handler_id_t ioUpdateHandler; + + bool signalConditioning = true; + bool autoGain = false; + bool noiseFloorTracking = false; + uint8_t channel = fl::Left; + + void setup() override { + addControl(signalConditioning, "signalConditioning", "checkbox"); + addControl(autoGain, "autoGain", "checkbox"); + addControl(noiseFloorTracking, "noiseFloorTracking", "checkbox"); + addControl(channel, "channel", "select"); + addControlValue("Left"); + addControlValue("Right"); + addControlValue("Both"); + + ioUpdateHandler = moduleIO->addUpdateHandler([this](const String& originId) { readPins(); }, true); + readPins(); // Node added at runtime so initial IO update not received so run explicitly + + audioProcessor.onBeat([]() { + sharedData.beat = true; + // EXT_LOGD(ML_TAG, "onBeat"); + }); + + audioProcessor.onVocalStart([]() { + sharedData.vocalsActive = true; + // EXT_LOGD(ML_TAG, "onVocalStart"); + }); + + audioProcessor.onVocalEnd([]() { + sharedData.vocalsActive = false; + // EXT_LOGD(ML_TAG, "onVocalEnd"); + }); + + audioProcessor.onVocalConfidence([](float confidence) { + sharedData.vocalConfidence = sharedData.vocalsActive ? confidence : 0.0; + // EXT_LOGD(ML_TAG, "onVocalConfidence %d", confidence); + }); + + audioProcessor.onBass([](float level) { + if (level > 0.01f) { + sharedData.bassLevel = level; + // EXT_LOGD(ML_TAG, "onBass: %f", level); + } + }); + + audioProcessor.onMid([](float level) { + if (level > 0.01f) { + sharedData.midLevel = level; + // EXT_LOGD(ML_TAG, "onMid: %f", level); + } + }); + + audioProcessor.onTreble([](float level) { + if (level > 0.01f) { + sharedData.trebleLevel = level; + // EXT_LOGD(ML_TAG, "onTreble: %f", level); + } + }); + audioProcessor.onPercussion([](fl::PercussionType type) { + // EXT_LOGD(ML_TAG, "onPercussion: %d", type); + sharedData.percussionType = (uint8_t)type; + }); + } + + void onUpdate(const Char<20>& oldValue, const JsonObject& control) override { + if (control["name"] == "signalConditioning") { + audioProcessor.setSignalConditioningEnabled(signalConditioning); + } + if (control["name"] == "autoGain") { + audioProcessor.setAutoGainEnabled(autoGain); + } + if (control["name"] == "noiseFloorTracking") { + audioProcessor.setNoiseFloorTrackingEnabled(noiseFloorTracking); + } + if (control["name"] == "channel" && oldValue != "") { // not on boot as readPins will do it then + // recreate with the new channel + stopAudio(); + startAudio(); + } + } + + uint8_t pinI2SSD = UINT8_MAX; + uint8_t pinI2SWS = UINT8_MAX; + uint8_t pinI2SSCK = UINT8_MAX; + + bool updatePin(uint8_t& pin, const uint8_t pinUsage) { + bool i2sPinsChanged = false; + moduleIO->read( + [&](ModuleState& state) { + for (JsonObject pinObject : state.data["pins"].as()) { + if (pinObject["usage"] == pinUsage && pin != pinObject["GPIO"]) { + pin = pinObject["GPIO"]; + i2sPinsChanged = true; + } + } + }, + name()); + return i2sPinsChanged; // an empty pin means it is not allocated anymore + } + + void readPins() { + if (safeModeMB) { + EXT_LOGW(ML_TAG, "Safe mode enabled, not adding pins"); + return; + } + + bool changed = updatePin(pinI2SWS, pin_I2S_WS); + changed = updatePin(pinI2SSD, pin_I2S_SD) || changed; + changed = updatePin(pinI2SSCK, pin_I2S_SCK) || changed; + + if (changed) { + EXT_LOGI(ML_TAG, "(re)creating audioInput %d %d %d", pinI2SWS, pinI2SSD, pinI2SSCK); + stopAudio(); + if (pinI2SWS != UINT8_MAX && pinI2SSD != UINT8_MAX && pinI2SSCK != UINT8_MAX) startAudio(); + } + } + + void loop() override { + if (!audioInput) return; + + sharedData.beat = false; + sharedData.percussionType = UINT8_MAX; + + while (fl::AudioSample sample = audioInput->read()) { + audioProcessor.update(sample); + } + } + + void startAudio() { + // Create configuration objects + i2sConfig = new fl::AudioConfigI2S(pinI2SWS, pinI2SSD, pinI2SSCK, 0, channel == 1 ? fl::Right : channel == 2 ? fl::Both : fl::Left, 44100, 16, fl::Philips); + + config = new fl::AudioConfig(*i2sConfig); + + fl::string errorMsg; + audioInput = fl::IAudioInput::create(*config, &errorMsg); + if (!audioInput) { + EXT_LOGE(ML_TAG, "Failed to create audio input: %s", errorMsg.c_str()); + return; + } + audioInput->start(); + } + + void stopAudio() { + if (audioInput) { + audioInput->stop(); + audioInput.reset(); // Explicitly release shared_ptr, even makes it a nullptr... + } + + // Clean up raw pointers + if (config) { + delete config; + config = nullptr; + } + + if (i2sConfig) { + delete i2sConfig; + i2sConfig = nullptr; + } + } + + ~FastLEDAudioDriver() override { + stopAudio(); + moduleIO->removeUpdateHandler(ioUpdateHandler); + } +}; + +#endif \ No newline at end of file diff --git a/src/MoonLight/Nodes/Drivers/D_FastLED.h b/src/MoonLight/Nodes/Drivers/D_FastLEDDriver.h similarity index 96% rename from src/MoonLight/Nodes/Drivers/D_FastLED.h rename to src/MoonLight/Nodes/Drivers/D_FastLEDDriver.h index c1066209..2d50af89 100644 --- a/src/MoonLight/Nodes/Drivers/D_FastLED.h +++ b/src/MoonLight/Nodes/Drivers/D_FastLEDDriver.h @@ -1,6 +1,6 @@ /** @title MoonLight - @file FastLED.h + @file D_FastLEDDriver.h @repo https://github.com/MoonModules/MoonLight, submit changes to this file as PRs @Authors https://github.com/MoonModules/MoonLight/commits/main @Doc https://moonmodules.org/MoonLight/moonlight/overview/ @@ -22,6 +22,9 @@ class FastLEDDriver : public DriverNode { static uint8_t dim() { return _NoD; } static const char* tags() { return "☸️"; } + update_handler_id_t ioUpdateHandler; + update_handler_id_t controlUpdateHandler; + Char<32> version = FASTLED_BUILD; Char<32> status = "NoInit"; Char<32> engine = "Auto"; @@ -59,7 +62,7 @@ class FastLEDDriver : public DriverNode { addControl(version, "version", "text", 0, 20, true); addControl(status, "status", "text", 0, 32, true); - moduleIO->addUpdateHandler( + ioUpdateHandler = moduleIO->addUpdateHandler( [this](const String& originId) { uint8_t nrOfPins = MIN(layerP.nrOfLedPins, layerP.nrOfAssignedPins); @@ -70,7 +73,7 @@ class FastLEDDriver : public DriverNode { // should we check here for maxPower changes? }, false); - moduleControl->addUpdateHandler([this](const String& originId) { + controlUpdateHandler = moduleControl->addUpdateHandler([this](const String& originId) { // brightness changes here? }); @@ -302,7 +305,7 @@ class FastLEDDriver : public DriverNode { CRGB* leds = (CRGB*)layerP.lights.channelsD; uint16_t startLed = 0; - FastLED.reset(ResetFlags::CHANNELS); + FastLED.clear(ClearFlags::CHANNELS); for (uint8_t pinIndex = 0; pinIndex < nrOfPins; pinIndex++) { EXT_LOGD(ML_TAG, "ledPin p:%d #:%d rgb:%d aff:%s", pins[pinIndex], layerP.ledsPerPin[pinIndex], rgbOrder, options.mAffinity.c_str()); @@ -349,7 +352,10 @@ class FastLEDDriver : public DriverNode { auto& events = FastLED.channelEvents(); events.onChannelCreated.clear(); events.onChannelEnqueued.clear(); - FastLED.reset(ResetFlags::CHANNELS); + FastLED.clear(ClearFlags::CHANNELS); + + moduleIO->removeUpdateHandler(ioUpdateHandler); + moduleControl->removeUpdateHandler(controlUpdateHandler); } }; diff --git a/src/MoonLight/Nodes/Drivers/D_IMU.h b/src/MoonLight/Nodes/Drivers/D_IMU.h index 02bbf396..a204c2fa 100644 --- a/src/MoonLight/Nodes/Drivers/D_IMU.h +++ b/src/MoonLight/Nodes/Drivers/D_IMU.h @@ -20,6 +20,8 @@ class IMUDriver : public Node { static uint8_t dim() { return _NoD; } static const char* tags() { return "☸️"; } + update_handler_id_t ioUpdateHandler; + bool motionTrackingReady = false; // set true if DMP init was successful Coord3D gyro; // in degrees (not radians) @@ -36,7 +38,7 @@ class IMUDriver : public Node { addControlValue("BMI160"); // not supported yet // Subscribe to IO updates to detect when I2C becomes ready - moduleIO->addUpdateHandler([this](const String& originId) { moduleIO->read([&](ModuleState& state) { i2cActuallyReady = state.data["I2CReady"]; }, name()); }, false); + ioUpdateHandler = moduleIO->addUpdateHandler([this](const String& originId) { moduleIO->read([&](ModuleState& state) { i2cActuallyReady = state.data["I2CReady"]; }, name()); }, false); // Read current I2C state in case boot-time handler dispatch already occurred moduleIO->read([this](ModuleState& state) { i2cActuallyReady = state.data["I2CReady"]; }, name()); requestInitBoard = true; @@ -207,7 +209,10 @@ class IMUDriver : public Node { } }; - ~IMUDriver() override { stopBoard(); } + ~IMUDriver() override { + stopBoard(); + moduleIO->removeUpdateHandler(ioUpdateHandler); + } private: MPU6050 mpu; diff --git a/src/MoonLight/Nodes/Drivers/D_Infrared.h b/src/MoonLight/Nodes/Drivers/D_Infrared.h index 8ca4f2fc..5b05e553 100644 --- a/src/MoonLight/Nodes/Drivers/D_Infrared.h +++ b/src/MoonLight/Nodes/Drivers/D_Infrared.h @@ -97,13 +97,15 @@ class IRDriver : public Node { IR_DRIVER_TAG); } + update_handler_id_t ioUpdateHandler; + void setup() override { addControl(irPreset, "irPreset", "select"); addControlValue("Swiss remote"); addControlValue("Athom"); // see https://www.athom.tech/blank-1/wled-esp32-music-addressable-led-strip-controller addControlValue("Luxceo"); - moduleIO->addUpdateHandler([this](const String& originId) { readPins(); }, false); + ioUpdateHandler = moduleIO->addUpdateHandler([this](const String& originId) { readPins(); }, false); readPins(); // Node added at runtime so initial IO update not received so run explicitly } @@ -428,6 +430,8 @@ class IRDriver : public Node { } } }; + + ~IRDriver() { moduleIO->removeUpdateHandler(ioUpdateHandler); } }; #endif \ No newline at end of file diff --git a/src/MoonLight/Nodes/Effects/E_FastLED.h b/src/MoonLight/Nodes/Effects/E_FastLED.h index a48cf96e..50d37608 100644 --- a/src/MoonLight/Nodes/Effects/E_FastLED.h +++ b/src/MoonLight/Nodes/Effects/E_FastLED.h @@ -37,4 +37,51 @@ class RainbowEffect : public Node { } }; +class FLAudioEffect : public Node { + public: + static const char* name() { return "FLAudio"; } + static uint8_t dim() { return _2D; } + static const char* tags() { return "⚡️🎵"; } + + uint8_t fade = 70; + + void setup() { addControl(fade, "fade", "slider"); } + + uint8_t beatLevel = 0; + float maxBass = 255; + float maxMid = 255; + float maxTreble = 255; + float maxVocal = 255; + + void loop() override { + layer->fadeToBlackBy(fade); + + if (sharedData.beat) beatLevel = 255; + + // EXT_LOGD(ML_TAG, "%f %f %d %f", sharedData.bassLevel, sharedData.trebleLevel, sharedData.beat, beatLevel, sharedData.vocalsActive ? sharedData.vocalConfidence : 0); + + uint8_t columnNr = 0; + if (maxBass > 0.0f) layer->drawLine(columnNr, layer->size.y - 1, columnNr, layer->size.y - 1 - layer->size.y * sharedData.bassLevel / maxBass, CRGB::Red); + columnNr++; + if (maxMid > 0.0f) layer->drawLine(columnNr, layer->size.y - 1, columnNr, layer->size.y - 1 - layer->size.y * sharedData.midLevel / maxMid, CRGB::Orange); + columnNr++; + if (maxTreble > 0.0f) layer->drawLine(columnNr, layer->size.y - 1, columnNr, layer->size.y - 1 - layer->size.y * sharedData.trebleLevel / maxTreble, CRGB::Green); + columnNr++; + if (maxVocal > 0.0f) layer->drawLine(columnNr, layer->size.y - 1, columnNr, layer->size.y - 1 - layer->size.y * sharedData.vocalConfidence / maxVocal, CRGB::Blue); + columnNr++; + + // beat + layer->drawLine(columnNr, layer->size.y - 1, columnNr++, layer->size.y - 1 - layer->size.y * beatLevel / 255, CRGB::Purple); + if (sharedData.beat) layer->setRGB(Coord3D(columnNr, layer->size.y - 1), CRGB::Purple); + columnNr++; + + // percussion + if (sharedData.percussionType != UINT8_MAX) layer->setRGB(Coord3D(columnNr + sharedData.percussionType, layer->size.y - 1), CRGB::Cyan); + columnNr+=3; + + // beat decay + if (beatLevel && layer->size.y > 0) beatLevel -= MIN(255 / layer->size.y, beatLevel); + } +}; + #endif \ No newline at end of file diff --git a/src/MoonLight/Nodes/Effects/E_WLED.h b/src/MoonLight/Nodes/Effects/E_WLED.h index 5bd37b19..ce8175a3 100644 --- a/src/MoonLight/Nodes/Effects/E_WLED.h +++ b/src/MoonLight/Nodes/Effects/E_WLED.h @@ -3560,14 +3560,18 @@ class WaterfallEffect : public Node { uint8_t pixCol = (log10f(myMajorPeak) - 2.26f) * 150; if (myMajorPeak < 182.0f) pixCol = 0; - for (int i = 0; i < layer->nrOfLights - 1; i++) layer->setRGB(i, layer->getRGB(i + 1)); + for (int x = 0; x < layer->size.x; x++) + for (int z = 0; z < layer->size.z; z++) + for (int y = 0; y < layer->size.y - 1; y++) layer->setRGB(Coord3D(x, y, z), layer->getRGB(Coord3D(x, y + 1, z))); bool peak = sharedData.volume > 128.0f; if (peak) { - layer->setRGB(layer->nrOfLights - 1, CHSV(92, 92, 92)); + for (int x = 0; x < layer->size.x; x++) + for (int z = 0; z < layer->size.z; z++) layer->setRGB(Coord3D(x, layer->size.y - 1, z), CHSV(92, 92, 92)); } else { CRGB color = ColorFromPalette(layerP.palette, pixCol + intensity, 127 + myMagnitude / 2.0); - layer->setRGB(layer->nrOfLights - 1, color); // blend(layerP.color2, color, (int)myMagnitude)); + for (int x = 0; x < layer->size.x; x++) + for (int z = 0; z < layer->size.z; z++) layer->setRGB(Coord3D(x, layer->size.y - 1, z), color); // blend(layerP.color2, color, (int)myMagnitude)); } } } diff --git a/src/MoonLight/Nodes/Layouts/L_MoonLight.h b/src/MoonLight/Nodes/Layouts/L_MoonLight.h index 0f9bef5f..2e89e452 100644 --- a/src/MoonLight/Nodes/Layouts/L_MoonLight.h +++ b/src/MoonLight/Nodes/Layouts/L_MoonLight.h @@ -9,6 +9,8 @@ @license For non GPL-v3 usage, commercial licenses must be purchased. Contact us for more information. **/ +#pragma once + #if FT_MOONLIGHT class HumanSizedCubeLayout : public Node { @@ -437,6 +439,36 @@ class SingleColumnLayout : public Node { } }; +class TubesLayout : public Node { + public: + static const char* name() { return "Tubes"; } + static uint8_t dim() { return _2D; } + static const char* tags() { return "🚥"; } + + uint8_t nrOfTubes = 4; + uint8_t ledsPerTube = 54; + uint8_t tubeDistance = 10; + bool reversed_order = false; + + void setup() override { + addControl(nrOfTubes, "nrOfTubes", "slider"); + addControl(ledsPerTube, "ledsPerTube", "slider"); + addControl(tubeDistance, "tubeDistance", "slider"); + addControl(reversed_order, "reversed order", "checkbox"); + } + + bool hasOnLayout() const override { return true; } + void onLayout() override { + for (int tube = 0; tube < nrOfTubes; tube++) { + SingleColumnLayout tubeLayout; + tubeLayout.height = ledsPerTube; + tubeLayout.reversed_order = reversed_order; + tubeLayout.xposition = tube * tubeDistance; + tubeLayout.onLayout(); + } + } +}; + class RingLayout : public Node { public: static const char* name() { return "Ring"; } diff --git a/src/main.cpp b/src/main.cpp index 5615c019..2e195bd6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -258,9 +258,10 @@ void setup() { if (Serial) Serial.printf("Serial init wait %d\n", i * 300); } - if (Serial) Serial.flush(); - - Serial.setDebugOutput(true); + if (Serial) { + Serial.flush(); + // Serial.setDebugOutput(true); //causes all EXT_LOG to dissappear + } Serial.printf("C++ Standard: %ld\n", __cplusplus); // 202002L