From 562a2065084167f581479568e258d36e2cdf12a1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 11 Jun 2022 00:50:29 +0200 Subject: [PATCH] It compiles! Cleaned (and possibly broken) AudioSource Added: - usermod notification about update - strip.getMinShowDelay() - pin manager updates Changed: - data exchange --- usermods/audioreactive/audio_reactive.h | 134 +++--- usermods/audioreactive/audio_source.h | 573 ++++++++++++------------ wled00/FX.cpp | 2 +- wled00/FX.h | 2 + wled00/fcn_declare.h | 68 ++- wled00/pin_manager.h | 5 +- wled00/um_manager.cpp | 1 + wled00/wled_server.cpp | 2 + 8 files changed, 389 insertions(+), 398 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 7c00f34b..33cfd2ed 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -106,7 +106,6 @@ static uint16_t micDataSm; // Smoothed mic data, as it's a static float micDataReal = 0.0f; // future support - this one has the full 24bit MicIn data - lowest 8bit after decimal point static byte soundAgc = 0; // default Automagic gain control static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our multiplier -static uint16_t noiseFloor = 100; // default squelch value for FFT reactive routines static double FFT_MajorPeak = 0; static double FFT_Magnitude = 0; @@ -127,13 +126,14 @@ static float fftResultMax[16]; // A table used for test static float fftAvg[16]; // Table of linearNoise results to be multiplied by soundSquelch in order to reduce squelch across fftResult bins. -static uint16_t linearNoise[16] = { 34, 28, 26, 25, 20, 12, 9, 6, 4, 4, 3, 2, 2, 2, 2, 2 }; +static uint8_t linearNoise[16] = { 34, 28, 26, 25, 20, 12, 9, 6, 4, 4, 3, 2, 2, 2, 2, 2 }; // Table of multiplication factors so that we can even out the frequency response. static float fftResultPink[16] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f }; // Create FFT object static arduinoFFT FFT = arduinoFFT(vReal, vImag, samples, SAMPLE_RATE); +static TaskHandle_t FFT_Task; float fftAdd(int from, int to) { float result = 0.0f; @@ -156,15 +156,13 @@ void FFTcode(void * parameter) { // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. // Only run the FFT computing code if we're not in Receive mode - if (audioSyncEnabled & 0x02) - continue; + if (audioSyncEnabled & 0x02) continue; audioSource->getSamples(vReal, samplesFFT); // old code - Last sample in vReal is our current mic sample //micDataSm = (uint16_t)vReal[samples - 1]; // will do a this a bit later - - // micDataSm = ((micData * 3) + micData)/4; + //micDataSm = ((micData * 3) + micData)/4; const int halfSamplesFFT = samplesFFT / 2; // samplesFFT divided by 2 double maxSample1 = 0.0; // max sample from first half of FFT batch @@ -356,6 +354,21 @@ class AudioReactive : public Usermod { #else int8_t i2sckPin = I2S_CKPIN; #endif + #ifndef ES7243_SDAPIN + int8_t sdaPin = 18; + #else + int8_t sdaPin = ES7243_SDAPIN; + #endif + #ifndef ES7243_SDAPIN + int8_t sclPin = 23; + #else + int8_t sclPin = ES7243_SCLPIN; + #endif + #ifndef MCLK_PIN + int8_t mclkPin = 0; + #else + int8_t mclkPin = MLCK_PIN; + #endif #define UDP_SYNC_HEADER "00001" struct audioSyncPacket { @@ -389,8 +402,8 @@ class AudioReactive : public Usermod { float sampleAdj; // Gain adjusted sample value float sampleAgc = 0.0f; // Our AGC sample int16_t rawSampleAgc = 0; // Our AGC sample - raw - long timeOfPeak = 0; - long lastTime = 0; + uint32_t timeOfPeak = 0; + uint32_t lastTime = 0; float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller float sampleAvg = 0.0f; // Smoothed Average float beat = 0.0f; // beat Detection @@ -680,36 +693,6 @@ class AudioReactive : public Usermod { } // getSample() - /* - * A simple averaging multiplier to automatically adjust sound sensitivity. - */ - /* - * A simple, but hairy, averaging multiplier to automatically adjust sound sensitivity. - * not sure if not sure "sample" or "sampleAvg" is the correct input signal for AGC - */ - void agcAvg() { - - float lastMultAgc = multAgc; - float tmpAgc; - if(fabs(sampleAvg) < 2.0f) { - tmpAgc = sampleAvg; // signal below squelch -> deliver silence - multAgc = multAgc * 0.95f; // slightly decrease gain multiplier - } else { - multAgc = (sampleAvg < 1) ? targetAgc : targetAgc / sampleAvg; // Make the multiplier so that sampleAvg * multiplier = setpoint - } - - if (multAgc < 0.5f) multAgc = 0.5f; // signal higher than 2*setpoint -> don't reduce it further - multAgc = (lastMultAgc*127.0f +multAgc) / 128.0f; //apply some filtering to gain multiplier -> smoother transitions - tmpAgc = (float)sample * multAgc; // apply gain to signal - if (tmpAgc <= (soundSquelch*1.2f)) tmpAgc = sample; // check against squelch threshold - increased by 20% to avoid artefacts (ripples) - - if (tmpAgc > 255) tmpAgc = 255; - sampleAgc = tmpAgc; // ONLY update sampleAgc ONCE because it's used elsewhere asynchronously!!!! - userVar0 = sampleAvg * 4; - if (userVar0 > 255) userVar0 = 255; - } // agcAvg() - - void transmitAudioData() { if (!udpSyncConnected) return; @@ -752,22 +735,25 @@ class AudioReactive : public Usermod { // usermod exchangeable data // we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers um_data = new um_data_t; - um_data->ub8_size = 2; - um_data->ub8_data = new (*uint8_t)[um_data->ub8_size]; - um_data->ub8_data[0] = &maxVol; - um_data->ub8_data[1] = fftResult; - um_data->ui32_size = 1; - um_data->ui32_data = new (*int32_t)[um_data->ui32_size]; - um_data->ui32_data[0] = &sample; - um_data->uf4_size = 3; - um_data->uf4_data = new (*float)[um_data->uf4_size]; - um_data->uf4_data[0] = fftAvg; - um_data->uf4_data[1] = fftCalc; - um_data->uf4_data[2] = fftBin; - um_data->uf8_size = 2; - um_data->uf8_data = new (*double)[um_data->uf8_size]; - um_data->uf8_data[0] = &FFT_MajorPeak; - um_data->uf8_data[1] = &FFT_Magnitude; + um_data->u_size = 8; + um_data->u_type = new um_types_t[um_data->u_size]; + um_data->u_data = new void*[um_data->u_size]; + um_data->u_data[0] = &maxVol; + um_data->u_type[0] = UMT_BYTE; + um_data->u_data[1] = fftResult; + um_data->u_type[1] = UMT_BYTE_ARR; + um_data->u_data[2] = &sample; + um_data->u_type[2] = UMT_INT16; + um_data->u_data[3] = fftAvg; + um_data->u_type[3] = UMT_FLOAT_ARR; + um_data->u_data[4] = fftCalc; + um_data->u_type[4] = UMT_FLOAT_ARR; + um_data->u_data[5] = fftBin; + um_data->u_type[5] = UMT_FLOAT_ARR; + um_data->u_data[6] = &FFT_MajorPeak; + um_data->u_type[6] = UMT_DOUBLE; + um_data->u_data[7] = &FFT_Magnitude; + um_data->u_type[7] = UMT_DOUBLE; //... // these are values used by effects in soundreactive fork //uint8_t *fftResult = um_data->; @@ -800,22 +786,32 @@ class AudioReactive : public Usermod { case 1: DEBUGSR_PRINTLN("AS: Generic I2S Microphone."); audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); + delay(100); + audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); break; case 2: DEBUGSR_PRINTLN("AS: ES7243 Microphone."); audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); + delay(100); + audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin); break; case 3: DEBUGSR_PRINTLN("AS: SPH0645 Microphone"); audioSource = new SPH0654(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); + delay(100); + audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); break; case 4: DEBUGSR_PRINTLN("AS: Generic I2S Microphone with Master Clock"); audioSource = new I2SSourceWithMasterClock(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); + delay(100); + audioSource->initialize(mclkPin, i2swsPin, i2ssdPin, i2sckPin); break; case 5: DEBUGSR_PRINTLN("AS: I2S PDM Microphone"); audioSource = new I2SPdmSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); + delay(100); + audioSource->initialize(i2swsPin, i2ssdPin); break; case 0: default: @@ -823,18 +819,17 @@ class AudioReactive : public Usermod { // we don't do the down-shift by 16bit any more //audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE, -4, 0x0FFF); // request upscaling to 16bit - still produces too much noise audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0x0FFF); // keep at 12bit - less noise + delay(100); + audioSource->initialize(audioPin); break; } - delay(100); - - audioSource->initialize(); delay(250); - //pinMode(LED_BUILTIN, OUTPUT); - //sampling_period_us = round(1000000*(1.0/SAMPLE_RATE)); + onUpdateBegin(false); // create FFT task +/* // Define the FFT Task and lock it to core 0 xTaskCreatePinnedToCore( FFTcode, // Function to implement the task @@ -844,6 +839,7 @@ class AudioReactive : public Usermod { 1, // Priority of the task &FFT_Task, // Task handle 0); // Core where the task should run +*/ } @@ -1008,11 +1004,27 @@ class AudioReactive : public Usermod { bool getUMData(um_data_t **data) { if (!data) return false; // no pointer provided by caller -> exit - *data = &um_data; + *data = um_data; return true; } + void onUpdateBegin(bool init) { + if (init) vTaskDelete(FFT_Task); // update is about to begin, remove task to prevent crash + else { // update has failed or create task requested + // Define the FFT Task and lock it to core 0 + xTaskCreatePinnedToCore( + FFTcode, // Function to implement the task + "FFT", // Name of the task + 5000, // Stack size in words + NULL, // Task input parameter + 1, // Priority of the task + &FFT_Task, // Task handle + 0); // Core where the task should run + } + } + + /* * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. @@ -1098,7 +1110,7 @@ class AudioReactive : public Usermod { JsonObject top = root.createNestedObject(FPSTR(_name)); JsonObject amic = top.createNestedObject(FPSTR(_analogmic)); - top["pin"] = audioPin; + amic["pin"] = audioPin; JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic)); dmic[F("type")] = dmType; diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index fc556608..5b382f63 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -20,29 +20,6 @@ #define I2S_SAMPLE_DOWNSCALE_TO_16BIT #endif -#ifndef MCLK_PIN - int mclkPin = 0; -#else - int mclkPin = MLCK_PIN; -#endif - -#ifndef ES7243_ADDR - int addr_ES7243 = 0x13; -#else - int addr_ES7243 = ES7243_ADDR; -#endif - -#ifndef ES7243_SDAPIN - int pin_ES7243_SDA = 18; -#else - int pin_ES7243_SDA = ES7243_SDAPIN; -#endif - -#ifndef ES7243_SDAPIN - int pin_ES7243_SCL = 23; -#else - int pin_ES7243_SCL = ES7243_SCLPIN; -#endif /* Interface class AudioSource serves as base class for all microphone types @@ -50,7 +27,7 @@ which simplifies the caller code */ class AudioSource { -public: + public: /* All public methods are virtual, so they can be overridden Everything but the destructor is also removed, to make sure each mic Implementation provides its version of this function @@ -61,7 +38,7 @@ public: This function needs to take care of anything that needs to be done before samples can be obtained from the microphone. */ - virtual void initialize() = 0; + virtual void initialize(int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) {}; /* Deinitialize Release all resources and deactivate any functionality that is used @@ -76,148 +53,146 @@ public: virtual void getSamples(double *buffer, uint16_t num_samples) = 0; /* Get an up-to-date sample without DC offset */ - virtual int getSampleWithoutDCOffset() = 0; + virtual int getSampleWithoutDCOffset() { return _sampleNoDCOffset; }; -protected: + protected: // Private constructor, to make sure it is not callable except from derived classes - AudioSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : _sampleRate(sampleRate), _blockSize(blockSize), _sampleNoDCOffset(0), _dcOffset(0.0f), _shift(lshift), _mask(mask), _initialized(false) {}; + AudioSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : + _sampleRate(sampleRate), + _blockSize(blockSize), + _sampleNoDCOffset(0), + _dcOffset(0.0f), + _shift(lshift), + _mask(mask), + _initialized(false) + {}; - int _sampleRate; /* Microphone sampling rate */ - int _blockSize; /* I2S block size */ - volatile int _sampleNoDCOffset; /* Up-to-date sample without DCOffset */ - float _dcOffset; /* Rolling average DC offset */ - int16_t _shift; /* Shift obtained samples to the right (positive) or left(negative) by this amount */ - uint32_t _mask; /* Bitmask for sample data after shifting. Bitmask 0X0FFF means that we need to convert 12bit ADC samples from unsigned to signed*/ - bool _initialized; /* Gets set to true if initialization is successful */ + int _sampleRate; // Microphone sampling rate + int _blockSize; // I2S block size + volatile int _sampleNoDCOffset; // Up-to-date sample without DCOffset + float _dcOffset; // Rolling average DC offset + int16_t _shift; // Shift obtained samples to the right (positive) or left(negative) by this amount + uint32_t _mask; // Bitmask for sample data after shifting. Bitmask 0X0FFF means that we need to convert 12bit ADC samples from unsigned to signed + bool _initialized; // Gets set to true if initialization is successful }; /* Basic I2S microphone source All functions are marked virtual, so derived classes can replace them */ class I2SSource : public AudioSource { -public: + public: I2SSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : - AudioSource(sampleRate, blockSize, lshift, mask) { - _config = { - .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), - .sample_rate = _sampleRate, - .bits_per_sample = I2S_SAMPLE_RESOLUTION, - .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, - .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), - .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, - .dma_buf_count = 8, - .dma_buf_len = _blockSize - }; - - _pinConfig = { - .bck_io_num = i2sckPin, - .ws_io_num = i2swsPin, - .data_out_num = I2S_PIN_NO_CHANGE, - .data_in_num = i2ssdPin - }; - }; - - - - - virtual void initialize() { + AudioSource(sampleRate, blockSize, lshift, mask) { + _config = { + .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), + .sample_rate = _sampleRate, + .bits_per_sample = I2S_SAMPLE_RESOLUTION, + .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, + .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + .dma_buf_count = 8, + .dma_buf_len = _blockSize + }; + } + void initialize(int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE) { + if (i2swsPin != I2S_PIN_NO_CHANGE && i2ssdPin != I2S_PIN_NO_CHANGE) { if (!pinManager.allocatePin(i2swsPin, true, PinOwner::DigitalMic) || !pinManager.allocatePin(i2ssdPin, true, PinOwner::DigitalMic)) { - return; + return; } + } - // i2ssckPin needs special treatment, since it might be unused on PDM mics - if (i2sckPin != -1) { - if (!pinManager.allocatePin(i2sckPin, true, PinOwner::DigitalMic)) - return; - } + // i2ssckPin needs special treatment, since it might be unused on PDM mics + if (i2sckPin != I2S_PIN_NO_CHANGE) { + if (!pinManager.allocatePin(i2sckPin, true, PinOwner::DigitalMic)) return; + } - esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to install i2s driver: %d\n", err); - return; - } + _pinConfig = { + .bck_io_num = i2sckPin, + .ws_io_num = i2swsPin, + .data_out_num = I2S_PIN_NO_CHANGE, + .data_in_num = i2ssdPin + }; - err = i2s_set_pin(I2S_NUM_0, &_pinConfig); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to set i2s pin config: %d\n", err); - return; - } + esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to install i2s driver: %d\n", err); + return; + } - _initialized = true; + err = i2s_set_pin(I2S_NUM_0, &_pinConfig); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to set i2s pin config: %d\n", err); + return; + } + + _initialized = true; } - virtual void deinitialize() { - _initialized = false; - esp_err_t err = i2s_driver_uninstall(I2S_NUM_0); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err); - return; - } - pinManager.deallocatePin(i2swsPin, PinOwner::DigitalMic); - pinManager.deallocatePin(i2ssdPin, PinOwner::DigitalMic); - // i2ssckPin needs special treatment, since it might be unused on PDM mics - if (i2sckPin != -1) { - pinManager.deallocatePin(i2sckPin, PinOwner::DigitalMic); - } + void deinitialize() { + _initialized = false; + esp_err_t err = i2s_driver_uninstall(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err); + return; + } + if (_pinConfig.ws_io_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.ws_io_num, PinOwner::DigitalMic); + if (_pinConfig.data_in_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.data_in_num, PinOwner::DigitalMic); + if (_pinConfig.bck_io_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.bck_io_num, PinOwner::DigitalMic); } - virtual void getSamples(double *buffer, uint16_t num_samples) { - if(_initialized) { - esp_err_t err; - size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */ - I2S_datatype newSamples[num_samples]; /* Intermediary sample storage */ + void getSamples(double *buffer, uint16_t num_samples) { + if (_initialized) { + esp_err_t err; + size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */ + I2S_datatype newSamples[num_samples]; /* Intermediary sample storage */ - // Reset dc offset - _dcOffset = 0.0f; + // Reset dc offset + _dcOffset = 0.0f; - err = i2s_read(I2S_NUM_0, (void *)newSamples, sizeof(newSamples), &bytes_read, portMAX_DELAY); - if ((err != ESP_OK)){ - DEBUGSR_PRINTF("Failed to get samples: %d\n", err); - return; - } + err = i2s_read(I2S_NUM_0, (void *)newSamples, sizeof(newSamples), &bytes_read, portMAX_DELAY); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to get samples: %d\n", err); + return; + } - // For correct operation, we need to read exactly sizeof(samples) bytes from i2s - if(bytes_read != sizeof(newSamples)) { - DEBUGSR_PRINTF("Failed to get enough samples: wanted: %d read: %d\n", sizeof(newSamples), bytes_read); - return; - } + // For correct operation, we need to read exactly sizeof(samples) bytes from i2s + if (bytes_read != sizeof(newSamples)) { + DEBUGSR_PRINTF("Failed to get enough samples: wanted: %d read: %d\n", sizeof(newSamples), bytes_read); + return; + } - // Store samples in sample buffer and update DC offset - for (int i = 0; i < num_samples; i++) { - // pre-shift samples down to 16bit + // Store samples in sample buffer and update DC offset + for (int i = 0; i < num_samples; i++) { + // pre-shift samples down to 16bit #ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT - if (_shift != 0) - newSamples[i] >>= 16; + if (_shift != 0) + newSamples[i] >>= 16; #endif - double currSample = 0.0; - if(_shift > 0) - currSample = (double) (newSamples[i] >> _shift); - else { - if(_shift < 0) - currSample = (double) (newSamples[i] << (- _shift)); // need to "pump up" 12bit ADC to full 16bit as delivered by other digital mics - else + double currSample = 0.0; + if(_shift > 0) + currSample = (double) (newSamples[i] >> _shift); + else { + if(_shift < 0) + currSample = (double) (newSamples[i] << (- _shift)); // need to "pump up" 12bit ADC to full 16bit as delivered by other digital mics + else #ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT - currSample = (double) newSamples[i] / 65536.0; // _shift == 0 -> use the chance to keep lower 16bits + currSample = (double) newSamples[i] / 65536.0; // _shift == 0 -> use the chance to keep lower 16bits #else - currSample = (double) newSamples[i]; + currSample = (double) newSamples[i]; #endif - } - buffer[i] = currSample; - _dcOffset = ((_dcOffset * 31) + currSample) / 32; - } - - // Update no-DC sample - _sampleNoDCOffset = buffer[num_samples - 1] - _dcOffset; + } + buffer[i] = currSample; + _dcOffset = ((_dcOffset * 31) + currSample) / 32; } + + // Update no-DC sample + _sampleNoDCOffset = buffer[num_samples - 1] - _dcOffset; + } } - virtual int getSampleWithoutDCOffset() { - return _sampleNoDCOffset; - } - -protected: + protected: i2s_config_t _config; i2s_pin_config_t _pinConfig; }; @@ -227,97 +202,111 @@ protected: routing via the provided API, so we have to do it by hand */ class I2SSourceWithMasterClock : public I2SSource { -public: + public: I2SSourceWithMasterClock(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : - I2SSource(sampleRate, blockSize, lshift, mask) { + I2SSource(sampleRate, blockSize, lshift, mask) { }; - virtual void initialize() { - // Reserve the master clock pin - if(!pinManager.allocatePin(mclkPin, true, PinOwner::DigitalMic)) { - return; - } - _routeMclk(); - I2SSource::initialize(); - + virtual void initialize(int8_t mclkPin, int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE) { + // Reserve the master clock pin + if(!pinManager.allocatePin(mclkPin, true, PinOwner::DigitalMic)) { + return; + } + _mclkPin = mclkPin; + _routeMclk(mclkPin); + I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin); } virtual void deinitialize() { - // Release the master clock pin - pinManager.deallocatePin(mclkPin, PinOwner::DigitalMic); - I2SSource::deinitialize(); + // Release the master clock pin + pinManager.deallocatePin(_mclkPin, PinOwner::DigitalMic); + I2SSource::deinitialize(); } -protected: - void _routeMclk() { - /* Enable the mclk routing depending on the selected mclk pin - Only I2S_NUM_0 is supported - */ - if (mclkPin == GPIO_NUM_0) { - PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); - WRITE_PERI_REG(PIN_CTRL,0xFFF0); - } else if (mclkPin == GPIO_NUM_1) { - PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_CLK_OUT3); - WRITE_PERI_REG(PIN_CTRL, 0xF0F0); - } else { - PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_CLK_OUT2); - WRITE_PERI_REG(PIN_CTRL, 0xFF00); - } + + protected: + void _routeMclk(int8_t mclkPin) { + /* Enable the mclk routing depending on the selected mclk pin + Only I2S_NUM_0 is supported + */ + if (mclkPin == GPIO_NUM_0) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); + WRITE_PERI_REG(PIN_CTRL,0xFFF0); + } else if (mclkPin == GPIO_NUM_1) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_CLK_OUT3); + WRITE_PERI_REG(PIN_CTRL, 0xF0F0); + } else { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_CLK_OUT2); + WRITE_PERI_REG(PIN_CTRL, 0xFF00); + } } + + private: + int8_t _mclkPin; }; /* ES7243 Microphone This is an I2S microphone that requires ininitialization over I2C before I2S data can be received */ -class ES7243 : public I2SSourceWithMasterClock { - -private: +class ES7243 : public I2SSource { + private: // I2C initialization functions for ES7243 void _es7243I2cBegin() { - Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U); + Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U); } void _es7243I2cWrite(uint8_t reg, uint8_t val) { - Wire.beginTransmission(addr_ES7243); - Wire.write((uint8_t)reg); - Wire.write((uint8_t)val); - Wire.endTransmission(); +#ifndef ES7243_ADDR + Wire.beginTransmission(0x13); +#else + Wire.beginTransmission(ES7243_ADDR); +#endif + Wire.write((uint8_t)reg); + Wire.write((uint8_t)val); + Wire.endTransmission(); } void _es7243InitAdc() { - _es7243I2cBegin(); - _es7243I2cWrite(0x00, 0x01); - _es7243I2cWrite(0x06, 0x00); - _es7243I2cWrite(0x05, 0x1B); - _es7243I2cWrite(0x01, 0x00); // 0x00 for 24 bit to match INMP441 - not sure if this needs adjustment to get 16bit samples from I2S - _es7243I2cWrite(0x08, 0x43); - _es7243I2cWrite(0x05, 0x13); + _es7243I2cBegin(); + _es7243I2cWrite(0x00, 0x01); + _es7243I2cWrite(0x06, 0x00); + _es7243I2cWrite(0x05, 0x1B); + _es7243I2cWrite(0x01, 0x00); // 0x00 for 24 bit to match INMP441 - not sure if this needs adjustment to get 16bit samples from I2S + _es7243I2cWrite(0x08, 0x43); + _es7243I2cWrite(0x05, 0x13); } public: - ES7243(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : - I2SSourceWithMasterClock(sampleRate, blockSize, lshift, mask) { - _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; + I2SSource(sampleRate, blockSize, lshift, mask) { + _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; }; - void initialize() { - // Reserve SDA and SCL pins of the I2C interface - if (!pinManager.allocatePin(pin_ES7243_SDA, true, PinOwner::DigitalMic) || - !pinManager.allocatePin(pin_ES7243_SCL, true, PinOwner::DigitalMic)) { - return; - } - // First route mclk, then configure ADC over I2C, then configure I2S - _es7243InitAdc(); - I2SSourceWithMasterClock::initialize(); + void initialize(int8_t sdaPin, int8_t sclPin, int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE) { + // Reserve SDA and SCL pins of the I2C interface + if (!pinManager.allocatePin(sdaPin, true, PinOwner::DigitalMic) || + !pinManager.allocatePin(sclPin, true, PinOwner::DigitalMic)) { + return; + } + + pin_ES7243_SDA = sdaPin; + pin_ES7243_SCL = sclPin; + + // First route mclk, then configure ADC over I2C, then configure I2S + _es7243InitAdc(); + I2SSource::initialize(); } void deinitialize() { - // Release SDA and SCL pins of the I2C interface - pinManager.deallocatePin(pin_ES7243_SDA, PinOwner::DigitalMic); - pinManager.deallocatePin(pin_ES7243_SCL, PinOwner::DigitalMic); - I2SSourceWithMasterClock::deinitialize(); + // Release SDA and SCL pins of the I2C interface + pinManager.deallocatePin(pin_ES7243_SDA, PinOwner::DigitalMic); + pinManager.deallocatePin(pin_ES7243_SCL, PinOwner::DigitalMic); + I2SSource::deinitialize(); } + + private: + int8_t pin_ES7243_SDA; + int8_t pin_ES7243_SCL; }; /* ADC over I2S Microphone @@ -326,110 +315,113 @@ public: without the need of manual timing of the samples */ class I2SAdcSource : public I2SSource { -public: + public: I2SAdcSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : - I2SSource(sampleRate, blockSize, lshift, mask){ - _config = { - .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), - .sample_rate = _sampleRate, - .bits_per_sample = I2S_SAMPLE_RESOLUTION, - .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, - .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), - .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, - .dma_buf_count = 8, - .dma_buf_len = _blockSize - }; + I2SSource(sampleRate, blockSize, lshift, mask) { + _config = { + .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), + .sample_rate = _sampleRate, + .bits_per_sample = I2S_SAMPLE_RESOLUTION, + .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, + .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, + .dma_buf_count = 8, + .dma_buf_len = _blockSize + }; } - void initialize() { + void initialize(int8_t audioPin) { + if(!pinManager.allocatePin(audioPin, false, PinOwner::AnalogMic)) { + return; + } + _audioPin = audioPin; - if(!pinManager.allocatePin(audioPin, false, PinOwner::AnalogMic)) { - return; - } - // Determine Analog channel. Only Channels on ADC1 are supported - int8_t channel = digitalPinToAnalogChannel(audioPin); - if (channel > 9) { - DEBUGSR_PRINTF("Incompatible GPIO used for audio in: %d\n", audioPin); - return; - } else { - adc_gpio_init(ADC_UNIT_1, adc_channel_t(channel)); - } + // Determine Analog channel. Only Channels on ADC1 are supported + int8_t channel = digitalPinToAnalogChannel(_audioPin); + if (channel > 9) { + DEBUGSR_PRINTF("Incompatible GPIO used for audio in: %d\n", _audioPin); + return; + } else { + adc_gpio_init(ADC_UNIT_1, adc_channel_t(channel)); + } - // Install Driver - esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to install i2s driver: %d\n", err); - return; - } + // Install Driver + esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to install i2s driver: %d\n", err); + return; + } - // Enable I2S mode of ADC - err = i2s_set_adc_mode(ADC_UNIT_1, adc1_channel_t(channel)); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to set i2s adc mode: %d\n", err); - return; + // Enable I2S mode of ADC + err = i2s_set_adc_mode(ADC_UNIT_1, adc1_channel_t(channel)); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to set i2s adc mode: %d\n", err); + return; + } - } #if defined(ARDUINO_ARCH_ESP32) - // according to docs from espressif, the ADC needs to be started explicitly - // fingers crossed - err = i2s_adc_enable(I2S_NUM_0); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to enable i2s adc: %d\n", err); - //return; - } + // according to docs from espressif, the ADC needs to be started explicitly + // fingers crossed + err = i2s_adc_enable(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to enable i2s adc: %d\n", err); + //return; + } #endif - _initialized = true; + _initialized = true; } void getSamples(double *buffer, uint16_t num_samples) { - - /* Enable ADC. This has to be enabled and disabled directly before and - after sampling, otherwise Wifi dies - */ - if (_initialized) { + /* Enable ADC. This has to be enabled and disabled directly before and + * after sampling, otherwise Wifi dies + */ + if (_initialized) { #if !defined(ARDUINO_ARCH_ESP32) - // old code - works for me without enable/disable, at least on ESP32. - esp_err_t err = i2s_adc_enable(I2S_NUM_0); - //esp_err_t err = i2s_start(I2S_NUM_0); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to enable i2s adc: %d\n", err); - return; - } -#endif - I2SSource::getSamples(buffer, num_samples); - -#if !defined(ARDUINO_ARCH_ESP32) - // old code - works for me without enable/disable, at least on ESP32. - err = i2s_adc_disable(I2S_NUM_0); - //err = i2s_stop(I2S_NUM_0); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err); - return; - } -#endif + // old code - works for me without enable/disable, at least on ESP32. + esp_err_t err = i2s_adc_enable(I2S_NUM_0); + //esp_err_t err = i2s_start(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to enable i2s adc: %d\n", err); + return; } +#endif + I2SSource::getSamples(buffer, num_samples); + +#if !defined(ARDUINO_ARCH_ESP32) + // old code - works for me without enable/disable, at least on ESP32. + err = i2s_adc_disable(I2S_NUM_0); + //err = i2s_stop(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err); + return; + } +#endif + } } void deinitialize() { - pinManager.deallocatePin(audioPin, PinOwner::AnalogMic); - _initialized = false; - esp_err_t err; + pinManager.deallocatePin(_audioPin, PinOwner::AnalogMic); + _initialized = false; + esp_err_t err; #if defined(ARDUINO_ARCH_ESP32) - // according to docs from espressif, the ADC needs to be stopped explicitly - // fingers crossed - err = i2s_adc_disable(I2S_NUM_0); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err); - //return; - } + // according to docs from espressif, the ADC needs to be stopped explicitly + // fingers crossed + err = i2s_adc_disable(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err); + //return; + } #endif - err = i2s_driver_uninstall(I2S_NUM_0); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err); - return; - } + err = i2s_driver_uninstall(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err); + return; + } } + + private: + int8_t _audioPin; }; /* SPH0645 Microphone @@ -437,15 +429,14 @@ public: special consideration. */ class SPH0654 : public I2SSource { - -public: + public: SPH0654(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : - I2SSource(sampleRate, blockSize, lshift, mask){} + I2SSource(sampleRate, blockSize, lshift, mask){} - void initialize() { - I2SSource::initialize(); - REG_SET_BIT(I2S_TIMING_REG(I2S_NUM_0), BIT(9)); - REG_SET_BIT(I2S_CONF_REG(I2S_NUM_0), I2S_RX_MSB_SHIFT); + void initialize(uint8_t i2swsPin, uint8_t i2ssdPin, uint8_t i2sckPin) { + I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin); + REG_SET_BIT(I2S_TIMING_REG(I2S_NUM_0), BIT(9)); + REG_SET_BIT(I2S_CONF_REG(I2S_NUM_0), I2S_RX_MSB_SHIFT); } }; @@ -454,20 +445,14 @@ public: data line, to make it simpler to debug, use the WS pin as CLK and SD pin as DATA */ - class I2SPdmSource : public I2SSource { - -public: + public: I2SPdmSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : - I2SSource(sampleRate, blockSize, lshift, mask) { + I2SSource(sampleRate, blockSize, lshift, mask) { + _config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM); // Change mode to pdm + } - _config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM); // Change mode to pdm - - _pinConfig = { - .bck_io_num = I2S_PIN_NO_CHANGE, // bck is unused in PDM mics - .ws_io_num = i2swsPin, // clk pin for PDM mic - .data_out_num = I2S_PIN_NO_CHANGE, - .data_in_num = i2ssdPin - }; + void initialize(uint8_t i2swsPin, uint8_t i2ssdPin) { + I2SSource::initialize(i2swsPin, i2ssdPin, I2S_PIN_NO_CHANGE); } }; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a3ca5512..99836f8d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5569,7 +5569,7 @@ uint16_t WS2812FX::mode_2DAkemi(void) { um_data_t *um_data; if (usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { - fftResult = um_data->ub8_data[1]; + fftResult = (uint8_t*)um_data->u_data[1]; base = fftResult[0]/255.0f; } diff --git a/wled00/FX.h b/wled00/FX.h index ec6eef8a..b3df7e03 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -752,6 +752,8 @@ class WS2812FX { getLengthPhysical(void), getFps(); + inline uint16_t getMinShowDelay() { return MIN_SHOW_DELAY; } + uint32_t now, timebase, diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 1237fa12..26b69f48 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -216,54 +216,38 @@ int getSignalQuality(int rssi); void WiFiEvent(WiFiEvent_t event); //um_manager.cpp +typedef enum UM_Data_Types { + UMT_BYTE = 0, + UMT_UINT16, + UMT_INT16, + UMT_UINT32, + UMT_INT32, + UMT_FLOAT, + UMT_DOUBLE, + UMT_BYTE_ARR, + UMT_UINT16_ARR, + UMT_INT16_ARR, + UMT_UINT32_ARR, + UMT_INT32_ARR, + UMT_FLOAT_ARR, + UMT_DOUBLE_ARR +} um_types_t; typedef struct UM_Exchange_Data { // should just use: size_t arr_size, void **arr_ptr, byte *ptr_type - size_t ub8_size; // size of ub8_data - uint8_t **ub8_data; // array of pointers to bytes (pointer can point to an array of bytes, depends on the usermod) - size_t uw16_size; // size of uw16_data - uint16_t **uw16_data; // array of pointers to unsigned words - size_t uw32_size; // size of uw32_data - uint32_t **uw32_data; // array of pointers to unsigned long words - size_t ui32_size; // size of uw32_data - int32_t **ui32_data; // array of pointers to long words - size_t uf4_size; // size of ubf4_data - float **uf4_data; // array of pointers to floats - size_t uf8_size; // size of ubf4_data - double **uf8_data; // array of pointers to doubles -/* - uint8_t ub1, ub2, ub3, ub4; // 4 byte values - uint16_t ui1, ui2, *aui1, *aui2, *aui3; // 2 word values and 3 pointers to word arrays/values - int16_t ui3, ui4, *aui4, *aui5, *aui6; // 2 signed word values and 3 pointers to signed word arrays/values - uint32_t ul1, ul2; // 2 long word values - float uf1, uf2, uf3, *auf1, *auf2; // 3 float values and 2 pointers to float arrays/values -*/ + size_t u_size; // size of u_data array + um_types_t *u_type; // array of data types + void **u_data; // array of pointers to data UM_Exchange_Data() { - ub8_size = 0; - uw16_size = 0; - uw32_size = 0; - ui32_size = 0; - uf4_size = 0; - uf8_size = 0; -/* - ub1 = ub2 = ub3 = ub4 = 0; - ui1 = ui2 = ui3 = ui4 = 0; - ul1 = ul2 = 0; - uf1 = uf2 = uf3 = 0.0f; - aui1 = aui2 = aui3 = nullptr; - aui4 = aui5 = aui6 = nullptr; - auf1 = auf2 = nullptr; -*/ + u_size = 0; + u_type = nullptr; + u_data = nullptr; } ~UM_Exchange_Data() { - if (ub8_size && ub8_data ) delete[] ub8_data; - if (uw16_size && uw16_data) delete[] uw16_data; - if (uw32_size && uw32_data) delete[] uw32_data; - if (ui32_size && ui32_data) delete[] ui32_data; - if (uf4_size && uf4_data ) delete[] uf4_data; - if (uf8_size && uf8_data ) delete[] uf8_data; + if (u_type) delete[] u_type; + if (u_data) delete[] u_data; } } um_data_t; -const unsigned int um_data_size = sizeof(um_data_t); // about 64 bytes +const unsigned int um_data_size = sizeof(um_data_t); // 12 bytes class Usermod { protected: @@ -284,6 +268,7 @@ class Usermod { virtual bool readFromConfig(JsonObject& obj) { return true; } // Note as of 2021-06 readFromConfig() now needs to return a bool, see usermod_v2_example.h virtual void onMqttConnect(bool sessionPresent) {} virtual bool onMqttMessage(char* topic, char* payload) { return false; } + virtual void onUpdateBegin(bool) {} virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} }; @@ -306,6 +291,7 @@ class UsermodManager { bool readFromConfig(JsonObject& obj); void onMqttConnect(bool sessionPresent); bool onMqttMessage(char* topic, char* payload); + void onUpdateBegin(bool); bool add(Usermod* um); Usermod* lookup(uint16_t mod_id); byte getModCount(); diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 5f009425..f0099636 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -34,6 +34,8 @@ enum struct PinOwner : uint8_t { DebugOut = 0x89, // 'Dbg' == debug output always IO1 DMX = 0x8A, // 'DMX' == hard-coded to IO2 HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32) + AnalogMic = 0x8C, // WLEDSR + DigitalMic = 0x8D, // WLEDSR // Use UserMod IDs from const.h here UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01 UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h" @@ -53,7 +55,8 @@ enum struct PinOwner : uint8_t { // #define USERMOD_ID_ELEKSTUBE_IPS // 0x10 // Usermod "usermod_elekstube_ips.h" -- Uses quite a few pins ... see Hardware.h and User_Setup.h // #define USERMOD_ID_SN_PHOTORESISTOR // 0x11 // Usermod "usermod_sn_photoresistor.h" -- Uses hard-coded pin (PHOTORESISTOR_PIN == A0), but could be easily updated to use pinManager UM_RGBRotaryEncoder = USERMOD_RGB_ROTARY_ENCODER, // 0x16 // Usermod "rgb-rotary-encoder.h" - UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA // 0x17 // Usermod "quinled-an-penta.h" + UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA, // 0x17 // Usermod "quinled-an-penta.h" + UM_Audioreactive = USERMOD_ID_AUDIOREACTIVE // 0x1E // Usermod: "audio_reactive.h" }; static_assert(0u == static_cast(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 82abfcac..8afbdb21 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -40,6 +40,7 @@ bool UsermodManager::onMqttMessage(char* topic, char* payload) { for (byte i = 0; i < numMods; i++) if (ums[i]->onMqttMessage(topic, payload)) return true; return false; } +void UsermodManager::onUpdateBegin(bool init) { for (byte i = 0; i < numMods; i++) ums[i]->onUpdateBegin(init); } // notify usermods that update is to begin /* * Enables usermods to lookup another Usermod. diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index dbad5f80..86c993be 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -284,6 +284,7 @@ void initServer() if(!index){ DEBUG_PRINTLN(F("OTA Update Start")); WLED::instance().disableWatchdog(); + usermods.onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init) lastEditTime = millis(); // make sure PIN does not lock during update #ifdef ESP8266 Update.runAsync(true); @@ -297,6 +298,7 @@ void initServer() } else { DEBUG_PRINTLN(F("Update Failed")); WLED::instance().enableWatchdog(); + usermods.onUpdateBegin(false); // notify usermods that update has failed (some may require task init) } } });