mirror of
https://github.com/wled/WLED.git
synced 2025-12-03 14:47:55 +00:00
Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe1481651e | ||
|
|
d43e05605c | ||
|
|
071622c7b9 | ||
|
|
da2547e8e5 | ||
|
|
53b88ca6ca | ||
|
|
a3741656cd | ||
|
|
15317760b6 | ||
|
|
f9e72f9a55 | ||
|
|
7a5c6f9c11 | ||
|
|
77e3c4d80c | ||
|
|
c64c94d792 | ||
|
|
bc41b38c16 | ||
|
|
7a8f5d592b | ||
|
|
bb3ee7c3ad | ||
|
|
17cdca2c2d | ||
|
|
3738bf79f5 | ||
|
|
fea140e068 | ||
|
|
799f065d08 | ||
|
|
d85ef561b5 | ||
|
|
bdddca0a15 | ||
|
|
e357c8aad3 | ||
|
|
c8bda1abee | ||
|
|
0d911e93bc | ||
|
|
5a9bdad2ec | ||
|
|
379f9c9ffa | ||
|
|
4750ac5010 | ||
|
|
0bc64b49b6 | ||
|
|
2d0771fcaf | ||
|
|
1706fdce3d | ||
|
|
7d0a338058 | ||
|
|
319edc7ad4 | ||
|
|
90b1912fea | ||
|
|
9a32fea1a5 | ||
|
|
ab3cf4aa4d | ||
|
|
14ffde4fed | ||
|
|
114f85f95e | ||
|
|
d37cae8ad9 | ||
|
|
7b872bfff6 | ||
|
|
7e8bb20560 | ||
|
|
b552744a07 | ||
|
|
3029a4e735 | ||
|
|
c756a12880 | ||
|
|
d12bf77831 | ||
|
|
4cee97561f | ||
|
|
3e80c2ce40 | ||
|
|
32fccb6a34 | ||
|
|
139f4d949f | ||
|
|
b762704d37 | ||
|
|
37612e56eb | ||
|
|
a50f12d000 | ||
|
|
0516dfed78 | ||
|
|
5a52f4a4cd | ||
|
|
d935975ec1 | ||
|
|
ce5f6d7019 | ||
|
|
430a82af85 | ||
|
|
69263c198a | ||
|
|
e82b519c92 | ||
|
|
ed496fb426 | ||
|
|
66573be212 | ||
|
|
4cdaa57dce | ||
|
|
79762f45b2 | ||
|
|
fc35f0aab2 | ||
|
|
68a853455d | ||
|
|
c24f67c479 | ||
|
|
d1763024bf | ||
|
|
b155f80dc7 | ||
|
|
2df536c0a4 | ||
|
|
dba31cb433 | ||
|
|
501b6e7de5 | ||
|
|
693f3b0b04 | ||
|
|
f6d1f3b433 | ||
|
|
7a52144e98 | ||
|
|
9c82add757 | ||
|
|
4e1ca9be49 | ||
|
|
a9f52a132b | ||
|
|
0ecae7e831 | ||
|
|
a24d4bc7e9 | ||
|
|
26080b23b1 | ||
|
|
b6d9aad6b4 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,3 +23,4 @@ wled-update.sh
|
||||
/wled00/Release
|
||||
/wled00/wled00.ino.cpp
|
||||
/wled00/html_*.h
|
||||
_codeql_detected_source_root
|
||||
|
||||
469
lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h
Normal file
469
lib/NeoESP32RmtHI/include/NeoEsp32RmtHIMethod.h
Normal file
@@ -0,0 +1,469 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
NeoPixel driver for ESP32 RMTs using High-priority Interrupt
|
||||
|
||||
(NB. This cannot be mixed with the non-HI driver.)
|
||||
|
||||
Written by Will M. Miles.
|
||||
|
||||
I invest time and resources providing this open source code,
|
||||
please support me by donating (see https://github.com/Makuna/NeoPixelBus)
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
This file is part of the Makuna/NeoPixelBus library.
|
||||
|
||||
NeoPixelBus is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as
|
||||
published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
NeoPixelBus is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with NeoPixel. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
// Use the NeoEspRmtSpeed types from the driver-based implementation
|
||||
#include <NeoPixelBus.h>
|
||||
|
||||
|
||||
namespace NeoEsp32RmtHiMethodDriver {
|
||||
// Install the driver for a specific channel, specifying timing properties
|
||||
esp_err_t Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t resetDuration);
|
||||
|
||||
// Remove the driver on a specific channel
|
||||
esp_err_t Uninstall(rmt_channel_t channel);
|
||||
|
||||
// Write a buffer of data to a specific channel.
|
||||
// Buffer reference is held until write completes.
|
||||
esp_err_t Write(rmt_channel_t channel, const uint8_t *src, size_t src_size);
|
||||
|
||||
// Wait until transaction is complete.
|
||||
esp_err_t WaitForTxDone(rmt_channel_t channel, TickType_t wait_time);
|
||||
};
|
||||
|
||||
template<typename T_SPEED, typename T_CHANNEL> class NeoEsp32RmtHIMethodBase
|
||||
{
|
||||
public:
|
||||
typedef NeoNoSettings SettingsObject;
|
||||
|
||||
NeoEsp32RmtHIMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) :
|
||||
_sizeData(pixelCount * elementSize + settingsSize),
|
||||
_pin(pin)
|
||||
{
|
||||
construct();
|
||||
}
|
||||
|
||||
NeoEsp32RmtHIMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize, NeoBusChannel channel) :
|
||||
_sizeData(pixelCount* elementSize + settingsSize),
|
||||
_pin(pin),
|
||||
_channel(channel)
|
||||
{
|
||||
construct();
|
||||
}
|
||||
|
||||
~NeoEsp32RmtHIMethodBase()
|
||||
{
|
||||
// wait until the last send finishes before destructing everything
|
||||
// arbitrary time out of 10 seconds
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 10000 / portTICK_PERIOD_MS));
|
||||
|
||||
ESP_ERROR_CHECK(NeoEsp32RmtHiMethodDriver::Uninstall(_channel.RmtChannelNumber));
|
||||
|
||||
gpio_matrix_out(_pin, SIG_GPIO_OUT_IDX, false, false);
|
||||
pinMode(_pin, INPUT);
|
||||
|
||||
free(_dataEditing);
|
||||
free(_dataSending);
|
||||
}
|
||||
|
||||
bool IsReadyToUpdate() const
|
||||
{
|
||||
return (ESP_OK == ESP_ERROR_CHECK_WITHOUT_ABORT_SILENT_TIMEOUT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 0)));
|
||||
}
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
rmt_config_t config = {};
|
||||
|
||||
config.rmt_mode = RMT_MODE_TX;
|
||||
config.channel = _channel.RmtChannelNumber;
|
||||
config.gpio_num = static_cast<gpio_num_t>(_pin);
|
||||
config.mem_block_num = 1;
|
||||
config.tx_config.loop_en = false;
|
||||
|
||||
config.tx_config.idle_output_en = true;
|
||||
config.tx_config.idle_level = T_SPEED::IdleLevel;
|
||||
|
||||
config.tx_config.carrier_en = false;
|
||||
config.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
||||
|
||||
config.clk_div = T_SPEED::RmtClockDivider;
|
||||
|
||||
ESP_ERROR_CHECK(rmt_config(&config)); // Uses ESP library
|
||||
ESP_ERROR_CHECK(NeoEsp32RmtHiMethodDriver::Install(_channel.RmtChannelNumber, T_SPEED::RmtBit0, T_SPEED::RmtBit1, T_SPEED::RmtDurationReset));
|
||||
}
|
||||
|
||||
void Update(bool maintainBufferConsistency)
|
||||
{
|
||||
// wait for not actively sending data
|
||||
// this will time out at 10 seconds, an arbitrarily long period of time
|
||||
// and do nothing if this happens
|
||||
if (ESP_OK == ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::WaitForTxDone(_channel.RmtChannelNumber, 10000 / portTICK_PERIOD_MS)))
|
||||
{
|
||||
// now start the RMT transmit with the editing buffer before we swap
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(NeoEsp32RmtHiMethodDriver::Write(_channel.RmtChannelNumber, _dataEditing, _sizeData));
|
||||
|
||||
if (maintainBufferConsistency)
|
||||
{
|
||||
// copy editing to sending,
|
||||
// this maintains the contract that "colors present before will
|
||||
// be the same after", otherwise GetPixelColor will be inconsistent
|
||||
memcpy(_dataSending, _dataEditing, _sizeData);
|
||||
}
|
||||
|
||||
// swap so the user can modify without affecting the async operation
|
||||
std::swap(_dataSending, _dataEditing);
|
||||
}
|
||||
}
|
||||
|
||||
bool AlwaysUpdate()
|
||||
{
|
||||
// this method requires update to be called only if changes to buffer
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SwapBuffers()
|
||||
{
|
||||
std::swap(_dataSending, _dataEditing);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t* getData() const
|
||||
{
|
||||
return _dataEditing;
|
||||
};
|
||||
|
||||
size_t getDataSize() const
|
||||
{
|
||||
return _sizeData;
|
||||
}
|
||||
|
||||
void applySettings([[maybe_unused]] const SettingsObject& settings)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
const size_t _sizeData; // Size of '_data*' buffers
|
||||
const uint8_t _pin; // output pin number
|
||||
const T_CHANNEL _channel; // holds instance for multi channel support
|
||||
|
||||
// Holds data stream which include LED color values and other settings as needed
|
||||
uint8_t* _dataEditing; // exposed for get and set
|
||||
uint8_t* _dataSending; // used for async send using RMT
|
||||
|
||||
|
||||
void construct()
|
||||
{
|
||||
_dataEditing = static_cast<uint8_t*>(malloc(_sizeData));
|
||||
// data cleared later in Begin()
|
||||
|
||||
_dataSending = static_cast<uint8_t*>(malloc(_sizeData));
|
||||
// no need to initialize it, it gets overwritten on every send
|
||||
}
|
||||
};
|
||||
|
||||
// normal
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2811Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2812xMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2816Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2805Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannelN> NeoEsp32RmtHINSk6812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1814Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1829Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1914Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannelN> NeoEsp32RmtHINApa106Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannelN> NeoEsp32RmtHINTx1812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannelN> NeoEsp32RmtHINGs1903Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN800KbpsMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN400KbpsMethod;
|
||||
typedef NeoEsp32RmtHINWs2805Method NeoEsp32RmtHINWs2814Method;
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2811Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2812xMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2816Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2805Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Sk6812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1814Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1829Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1914Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Apa106Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tx1812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Gs1903Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0800KbpsMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0400KbpsMethod;
|
||||
typedef NeoEsp32RmtHI0Ws2805Method NeoEsp32RmtHI0Ws2814Method;
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2811Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2812xMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2816Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2805Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Sk6812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1814Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1829Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1914Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Apa106Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tx1812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Gs1903Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1800KbpsMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1400KbpsMethod;
|
||||
typedef NeoEsp32RmtHI1Ws2805Method NeoEsp32RmtHI1Ws2814Method;
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2811Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2812xMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2816Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2805Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Sk6812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1814Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1829Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1914Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Apa106Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tx1812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Gs1903Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2800KbpsMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2400KbpsMethod;
|
||||
typedef NeoEsp32RmtHI2Ws2805Method NeoEsp32RmtHI2Ws2814Method;
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2811Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2812xMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2816Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2805Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Sk6812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1814Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1829Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1914Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Apa106Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tx1812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Gs1903Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3800KbpsMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3400KbpsMethod;
|
||||
typedef NeoEsp32RmtHI3Ws2805Method NeoEsp32RmtHI3Ws2814Method;
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2811Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2812xMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2816Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2805Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Sk6812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1814Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1829Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1914Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Apa106Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tx1812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Gs1903Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4800KbpsMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4400KbpsMethod;
|
||||
typedef NeoEsp32RmtHI4Ws2805Method NeoEsp32RmtHI4Ws2814Method;
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2811Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2812xMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2816Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2805Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Sk6812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1814Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1829Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1914Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Apa106Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tx1812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Gs1903Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5800KbpsMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5400KbpsMethod;
|
||||
typedef NeoEsp32RmtHI5Ws2805Method NeoEsp32RmtHI5Ws2814Method;
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2811Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2812xMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2816Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2805Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Sk6812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1814Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1829Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1914Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Apa106Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tx1812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Gs1903Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6800KbpsMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6400KbpsMethod;
|
||||
typedef NeoEsp32RmtHI6Ws2805Method NeoEsp32RmtHI6Ws2814Method;
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2811, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2811Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2812xMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2816Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedWs2805, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2805Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedSk6812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Sk6812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1814, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1814Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1829, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1829Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTm1914, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1914Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedApa106, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Apa106Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedTx1812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tx1812Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeedGs1903, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Gs1903Method;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed800Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7800KbpsMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtSpeed400Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7400KbpsMethod;
|
||||
typedef NeoEsp32RmtHI7Ws2805Method NeoEsp32RmtHI7Ws2814Method;
|
||||
|
||||
#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
|
||||
// inverted
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2811InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2812xInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2816InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannelN> NeoEsp32RmtHINWs2805InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannelN> NeoEsp32RmtHINSk6812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1814InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1829InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannelN> NeoEsp32RmtHINTm1914InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannelN> NeoEsp32RmtHINApa106InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannelN> NeoEsp32RmtHINTx1812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannelN> NeoEsp32RmtHINGs1903InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN800KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannelN> NeoEsp32RmtHIN400KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHINWs2805InvertedMethod NeoEsp32RmtHINWs2814InvertedMethod;
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2811InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2812xInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2816InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Ws2805InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Sk6812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1814InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1829InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tm1914InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Apa106InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Tx1812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel0> NeoEsp32RmtHI0Gs1903InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0800KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel0> NeoEsp32RmtHI0400KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHI0Ws2805InvertedMethod NeoEsp32RmtHI0Ws2814InvertedMethod;
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2811InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2812xInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2816InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Ws2805InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Sk6812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1814InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1829InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tm1914InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Apa106InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Tx1812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel1> NeoEsp32RmtHI1Gs1903InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1800KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel1> NeoEsp32RmtHI1400KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHI1Ws2805InvertedMethod NeoEsp32RmtHI1Ws2814InvertedMethod;
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2811InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2812xInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2816InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Ws2805InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Sk6812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1814InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1829InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tm1914InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Apa106InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Tx1812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel2> NeoEsp32RmtHI2Gs1903InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2800KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel2> NeoEsp32RmtHI2400KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHI2Ws2805InvertedMethod NeoEsp32RmtHI2Ws2814InvertedMethod;
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2811InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2812xInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2805InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Ws2816InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Sk6812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1814InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1829InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tm1914InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Apa106InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Tx1812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel3> NeoEsp32RmtHI3Gs1903InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3800KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel3> NeoEsp32RmtHI3400KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHI3Ws2805InvertedMethod NeoEsp32RmtHI3Ws2814InvertedMethod;
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2811InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2812xInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2816InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Ws2805InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Sk6812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1814InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1829InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tm1914InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Apa106InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Tx1812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel4> NeoEsp32RmtHI4Gs1903InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4800KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel4> NeoEsp32RmtHI4400KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHI4Ws2805InvertedMethod NeoEsp32RmtHI4Ws2814InvertedMethod;
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2811InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2812xInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2816InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Ws2805InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Sk6812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1814InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1829InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tm1914InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Apa106InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Tx1812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel5> NeoEsp32RmtHI5Gs1903InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5800KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel5> NeoEsp32RmtHI5400KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHI5Ws2805InvertedMethod NeoEsp32RmtHI5Ws2814InvertedMethod;
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2811InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2812xInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2816InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Ws2805InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Sk6812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1814InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1829InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tm1914InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Apa106InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Tx1812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel6> NeoEsp32RmtHI6Gs1903InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6800KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel6> NeoEsp32RmtHI6400KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHI6Ws2805InvertedMethod NeoEsp32RmtHI6Ws2814InvertedMethod;
|
||||
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2811, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2811InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2812xInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2812x, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2816InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedWs2805, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Ws2805InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedSk6812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Sk6812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1814, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1814InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1829, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1829InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTm1914, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tm1914InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedApa106, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Apa106InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedTx1812, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Tx1812InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeedGs1903, NeoEsp32RmtChannel7> NeoEsp32RmtHI7Gs1903InvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed800Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7800KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHIMethodBase<NeoEsp32RmtInvertedSpeed400Kbps, NeoEsp32RmtChannel7> NeoEsp32RmtHI7400KbpsInvertedMethod;
|
||||
typedef NeoEsp32RmtHI7Ws2805InvertedMethod NeoEsp32RmtHI7Ws2814InvertedMethod;
|
||||
|
||||
#endif // !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
#endif // !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
|
||||
#endif
|
||||
12
lib/NeoESP32RmtHI/library.json
Normal file
12
lib/NeoESP32RmtHI/library.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "NeoESP32RmtHI",
|
||||
"build": { "libArchive": false },
|
||||
"platforms": ["espressif32"],
|
||||
"dependencies": [
|
||||
{
|
||||
"owner": "makuna",
|
||||
"name": "NeoPixelBus",
|
||||
"version": "^2.8.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
263
lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S
Normal file
263
lib/NeoESP32RmtHI/src/NeoEsp32RmtHI.S
Normal file
@@ -0,0 +1,263 @@
|
||||
/* RMT ISR shim
|
||||
* Bridges from a high-level interrupt to the C++ code.
|
||||
*
|
||||
* This code is largely derived from Espressif's 'hli_vector.S' Bluetooth ISR.
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined(__XTENSA__) && defined(ESP32) && !defined(CONFIG_BTDM_CTRL_HLI)
|
||||
|
||||
#include <freertos/xtensa_context.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc.h"
|
||||
|
||||
/* If the Bluetooth driver has hooked the high-priority interrupt, we piggyback on it and don't need this. */
|
||||
#ifndef CONFIG_BTDM_CTRL_HLI
|
||||
|
||||
/*
|
||||
Select interrupt based on system check level
|
||||
- Base ESP32: could be 4 or 5, depends on platform config
|
||||
- S2: 5
|
||||
- S3: 5
|
||||
*/
|
||||
|
||||
#if CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5
|
||||
/* Use level 4 */
|
||||
#define RFI_X 4
|
||||
#define xt_highintx xt_highint4
|
||||
#else /* !CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */
|
||||
/* Use level 5 */
|
||||
#define RFI_X 5
|
||||
#define xt_highintx xt_highint5
|
||||
#endif /* CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5 */
|
||||
|
||||
// Register map, based on interrupt level
|
||||
#define EPC_X (EPC + RFI_X)
|
||||
#define EXCSAVE_X (EXCSAVE + RFI_X)
|
||||
|
||||
// The sp mnemonic is used all over in ESP's assembly, though I'm not sure where it's expected to be defined?
|
||||
#define sp a1
|
||||
|
||||
/* Interrupt stack size, for C code. */
|
||||
#define RMT_INTR_STACK_SIZE 512
|
||||
|
||||
/* Save area for the CPU state:
|
||||
* - 64 words for the general purpose registers
|
||||
* - 7 words for some of the special registers:
|
||||
* - WINDOWBASE, WINDOWSTART — only WINDOWSTART is truly needed
|
||||
* - SAR, LBEG, LEND, LCOUNT — since the C code might use these
|
||||
* - EPC1 — since the C code might cause window overflow exceptions
|
||||
* This is not laid out as standard exception frame structure
|
||||
* for simplicity of the save/restore code.
|
||||
*/
|
||||
#define REG_FILE_SIZE (64 * 4)
|
||||
#define SPECREG_OFFSET REG_FILE_SIZE
|
||||
#define SPECREG_SIZE (7 * 4)
|
||||
#define REG_SAVE_AREA_SIZE (SPECREG_OFFSET + SPECREG_SIZE)
|
||||
|
||||
.data
|
||||
_rmt_intr_stack:
|
||||
.space RMT_INTR_STACK_SIZE
|
||||
_rmt_save_ctx:
|
||||
.space REG_SAVE_AREA_SIZE
|
||||
|
||||
.section .iram1,"ax"
|
||||
.global xt_highintx
|
||||
.type xt_highintx,@function
|
||||
.align 4
|
||||
|
||||
xt_highintx:
|
||||
|
||||
movi a0, _rmt_save_ctx
|
||||
/* save 4 lower registers */
|
||||
s32i a1, a0, 4
|
||||
s32i a2, a0, 8
|
||||
s32i a3, a0, 12
|
||||
rsr a2, EXCSAVE_X /* holds the value of a0 */
|
||||
s32i a2, a0, 0
|
||||
|
||||
/* Save special registers */
|
||||
addi a0, a0, SPECREG_OFFSET
|
||||
rsr a2, WINDOWBASE
|
||||
s32i a2, a0, 0
|
||||
rsr a2, WINDOWSTART
|
||||
s32i a2, a0, 4
|
||||
rsr a2, SAR
|
||||
s32i a2, a0, 8
|
||||
#if XCHAL_HAVE_LOOPS
|
||||
rsr a2, LBEG
|
||||
s32i a2, a0, 12
|
||||
rsr a2, LEND
|
||||
s32i a2, a0, 16
|
||||
rsr a2, LCOUNT
|
||||
s32i a2, a0, 20
|
||||
#endif
|
||||
rsr a2, EPC1
|
||||
s32i a2, a0, 24
|
||||
|
||||
/* disable exception mode, window overflow */
|
||||
movi a0, PS_INTLEVEL(RFI_X+1) | PS_EXCM
|
||||
wsr a0, PS
|
||||
rsync
|
||||
|
||||
/* Save the remaining physical registers.
|
||||
* 4 registers are already saved, which leaves 60 registers to save.
|
||||
* (FIXME: consider the case when the CPU is configured with physical 32 registers)
|
||||
* These 60 registers are saved in 5 iterations, 12 registers at a time.
|
||||
*/
|
||||
movi a1, 5
|
||||
movi a3, _rmt_save_ctx + 4 * 4
|
||||
|
||||
/* This is repeated 5 times, each time the window is shifted by 12 registers.
|
||||
* We come here with a1 = downcounter, a3 = save pointer, a2 and a0 unused.
|
||||
*/
|
||||
1:
|
||||
s32i a4, a3, 0
|
||||
s32i a5, a3, 4
|
||||
s32i a6, a3, 8
|
||||
s32i a7, a3, 12
|
||||
s32i a8, a3, 16
|
||||
s32i a9, a3, 20
|
||||
s32i a10, a3, 24
|
||||
s32i a11, a3, 28
|
||||
s32i a12, a3, 32
|
||||
s32i a13, a3, 36
|
||||
s32i a14, a3, 40
|
||||
s32i a15, a3, 44
|
||||
|
||||
/* We are about to rotate the window, so that a12-a15 will become the new a0-a3.
|
||||
* Copy a0-a3 to a12-15 to still have access to these values.
|
||||
* At the same time we can decrement the counter and adjust the save area pointer
|
||||
*/
|
||||
|
||||
/* a0 is constant (_rmt_save_ctx), no need to copy */
|
||||
addi a13, a1, -1 /* copy and decrement the downcounter */
|
||||
/* a2 is scratch so no need to copy */
|
||||
addi a15, a3, 48 /* copy and adjust the save area pointer */
|
||||
beqz a13, 2f /* have saved all registers ? */
|
||||
rotw 3 /* rotate the window and go back */
|
||||
j 1b
|
||||
|
||||
/* the loop is complete */
|
||||
2:
|
||||
rotw 4 /* this brings us back to the original window */
|
||||
/* a0 still points to _rmt_save_ctx */
|
||||
|
||||
/* Can clear WINDOWSTART now, all registers are saved */
|
||||
rsr a2, WINDOWBASE
|
||||
/* WINDOWSTART = (1 << WINDOWBASE) */
|
||||
movi a3, 1
|
||||
ssl a2
|
||||
sll a3, a3
|
||||
wsr a3, WINDOWSTART
|
||||
|
||||
_highint_stack_switch:
|
||||
movi a0, 0
|
||||
movi sp, _rmt_intr_stack + RMT_INTR_STACK_SIZE - 16
|
||||
s32e a0, sp, -12 /* For GDB: set null SP */
|
||||
s32e a0, sp, -16 /* For GDB: set null PC */
|
||||
movi a0, _highint_stack_switch /* For GDB: cosmetics, for the frame where stack switch happened */
|
||||
|
||||
/* Set up PS for C, disable all interrupts except NMI and debug, and clear EXCM. */
|
||||
movi a6, PS_INTLEVEL(RFI_X) | PS_UM | PS_WOE
|
||||
wsr a6, PS
|
||||
rsync
|
||||
|
||||
/* Call C handler */
|
||||
mov a6, sp
|
||||
call4 NeoEsp32RmtMethodIsr
|
||||
|
||||
l32e sp, sp, -12 /* switch back to the original stack */
|
||||
|
||||
/* Done with C handler; re-enable exception mode, disabling window overflow */
|
||||
movi a2, PS_INTLEVEL(RFI_X+1) | PS_EXCM /* TOCHECK */
|
||||
wsr a2, PS
|
||||
rsync
|
||||
|
||||
/* Restore the special registers.
|
||||
* WINDOWSTART will be restored near the end.
|
||||
*/
|
||||
movi a0, _rmt_save_ctx + SPECREG_OFFSET
|
||||
l32i a2, a0, 8
|
||||
wsr a2, SAR
|
||||
#if XCHAL_HAVE_LOOPS
|
||||
l32i a2, a0, 12
|
||||
wsr a2, LBEG
|
||||
l32i a2, a0, 16
|
||||
wsr a2, LEND
|
||||
l32i a2, a0, 20
|
||||
wsr a2, LCOUNT
|
||||
#endif
|
||||
l32i a2, a0, 24
|
||||
wsr a2, EPC1
|
||||
|
||||
/* Restoring the physical registers.
|
||||
* This is the reverse to the saving process above.
|
||||
*/
|
||||
|
||||
/* Rotate back to the final window, then start loading 12 registers at a time,
|
||||
* in 5 iterations.
|
||||
* Again, a1 is the downcounter and a3 is the save area pointer.
|
||||
* After each rotation, a1 and a3 are copied from a13 and a15.
|
||||
* To simplify the loop, we put the initial values into a13 and a15.
|
||||
*/
|
||||
rotw -4
|
||||
movi a15, _rmt_save_ctx + 64 * 4 /* point to the end of the save area */
|
||||
movi a13, 5
|
||||
|
||||
1:
|
||||
/* Copy a1 and a3 from their previous location,
|
||||
* at the same time decrementing and adjusting the save area pointer.
|
||||
*/
|
||||
addi a1, a13, -1
|
||||
addi a3, a15, -48
|
||||
|
||||
/* Load 12 registers */
|
||||
l32i a4, a3, 0
|
||||
l32i a5, a3, 4
|
||||
l32i a6, a3, 8
|
||||
l32i a7, a3, 12
|
||||
l32i a8, a3, 16
|
||||
l32i a9, a3, 20
|
||||
l32i a10, a3, 24
|
||||
l32i a11, a3, 28 /* ensure PS and EPC written */
|
||||
l32i a12, a3, 32
|
||||
l32i a13, a3, 36
|
||||
l32i a14, a3, 40
|
||||
l32i a15, a3, 44
|
||||
|
||||
/* Done with the loop? */
|
||||
beqz a1, 2f
|
||||
/* If no, rotate the window and repeat */
|
||||
rotw -3
|
||||
j 1b
|
||||
|
||||
2:
|
||||
/* Done with the loop. Only 4 registers (a0-a3 in the original window) remain
|
||||
* to be restored. Also need to restore WINDOWSTART, since all the general
|
||||
* registers are now in place.
|
||||
*/
|
||||
movi a0, _rmt_save_ctx
|
||||
|
||||
l32i a2, a0, SPECREG_OFFSET + 4
|
||||
wsr a2, WINDOWSTART
|
||||
|
||||
l32i a1, a0, 4
|
||||
l32i a2, a0, 8
|
||||
l32i a3, a0, 12
|
||||
rsr a0, EXCSAVE_X /* holds the value of a0 before the interrupt handler */
|
||||
|
||||
/* Return from the interrupt, restoring PS from EPS_X */
|
||||
rfi RFI_X
|
||||
|
||||
|
||||
/* The linker has no reason to link in this file; all symbols it exports are already defined
|
||||
(weakly!) in the default int handler. Define a symbol here so we can use it to have the
|
||||
linker inspect this anyway. */
|
||||
|
||||
.global ld_include_hli_vectors_rmt
|
||||
ld_include_hli_vectors_rmt:
|
||||
|
||||
|
||||
#endif // CONFIG_BTDM_CTRL_HLI
|
||||
#endif // XTensa
|
||||
507
lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp
Normal file
507
lib/NeoESP32RmtHI/src/NeoEsp32RmtHIMethod.cpp
Normal file
@@ -0,0 +1,507 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
NeoPixel library helper functions for Esp32.
|
||||
|
||||
A BIG thanks to Andreas Merkle for the investigation and implementation of
|
||||
a workaround to the GCC bug that drops method attributes from template methods
|
||||
|
||||
Written by Michael C. Miller.
|
||||
|
||||
I invest time and resources providing this open source code,
|
||||
please support me by donating (see https://github.com/Makuna/NeoPixelBus)
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
This file is part of the Makuna/NeoPixelBus library.
|
||||
|
||||
NeoPixelBus is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as
|
||||
published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
NeoPixelBus is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with NeoPixel. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include <algorithm>
|
||||
#include "esp_idf_version.h"
|
||||
#include "NeoEsp32RmtHIMethod.h"
|
||||
#include "soc/soc.h"
|
||||
#include "soc/rmt_reg.h"
|
||||
|
||||
#ifdef __riscv
|
||||
#include "riscv/interrupt.h"
|
||||
#endif
|
||||
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
|
||||
#include "hal/rmt_ll.h"
|
||||
#else
|
||||
/* Shims for older ESP-IDF v3; we can safely assume original ESP32 */
|
||||
#include "soc/rmt_struct.h"
|
||||
|
||||
// Selected RMT API functions borrowed from ESP-IDF v4.4.8
|
||||
// components/hal/esp32/include/hal/rmt_ll.h
|
||||
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_tx_reset_pointer(rmt_dev_t *dev, uint32_t channel)
|
||||
{
|
||||
dev->conf_ch[channel].conf1.mem_rd_rst = 1;
|
||||
dev->conf_ch[channel].conf1.mem_rd_rst = 0;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_tx_start(rmt_dev_t *dev, uint32_t channel)
|
||||
{
|
||||
dev->conf_ch[channel].conf1.tx_start = 1;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_tx_stop(rmt_dev_t *dev, uint32_t channel)
|
||||
{
|
||||
RMTMEM.chan[channel].data32[0].val = 0;
|
||||
dev->conf_ch[channel].conf1.tx_start = 0;
|
||||
dev->conf_ch[channel].conf1.mem_rd_rst = 1;
|
||||
dev->conf_ch[channel].conf1.mem_rd_rst = 0;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_tx_enable_pingpong(rmt_dev_t *dev, uint32_t channel, bool enable)
|
||||
{
|
||||
dev->apb_conf.mem_tx_wrap_en = enable;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_tx_enable_loop(rmt_dev_t *dev, uint32_t channel, bool enable)
|
||||
{
|
||||
dev->conf_ch[channel].conf1.tx_conti_mode = enable;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t rmt_ll_tx_get_channel_status(rmt_dev_t *dev, uint32_t channel)
|
||||
{
|
||||
return dev->status_ch[channel];
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_tx_set_limit(rmt_dev_t *dev, uint32_t channel, uint32_t limit)
|
||||
{
|
||||
dev->tx_lim_ch[channel].limit = limit;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_enable_interrupt(rmt_dev_t *dev, uint32_t mask, bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
dev->int_ena.val |= mask;
|
||||
} else {
|
||||
dev->int_ena.val &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_enable_tx_end_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable)
|
||||
{
|
||||
dev->int_ena.val &= ~(1 << (channel * 3));
|
||||
dev->int_ena.val |= (enable << (channel * 3));
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_enable_tx_err_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable)
|
||||
{
|
||||
dev->int_ena.val &= ~(1 << (channel * 3 + 2));
|
||||
dev->int_ena.val |= (enable << (channel * 3 + 2));
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_enable_tx_thres_interrupt(rmt_dev_t *dev, uint32_t channel, bool enable)
|
||||
{
|
||||
dev->int_ena.val &= ~(1 << (channel + 24));
|
||||
dev->int_ena.val |= (enable << (channel + 24));
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_clear_tx_end_interrupt(rmt_dev_t *dev, uint32_t channel)
|
||||
{
|
||||
dev->int_clr.val = (1 << (channel * 3));
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_clear_tx_err_interrupt(rmt_dev_t *dev, uint32_t channel)
|
||||
{
|
||||
dev->int_clr.val = (1 << (channel * 3 + 2));
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void rmt_ll_clear_tx_thres_interrupt(rmt_dev_t *dev, uint32_t channel)
|
||||
{
|
||||
dev->int_clr.val = (1 << (channel + 24));
|
||||
}
|
||||
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t rmt_ll_get_tx_thres_interrupt_status(rmt_dev_t *dev)
|
||||
{
|
||||
uint32_t status = dev->int_st.val;
|
||||
return (status & 0xFF000000) >> 24;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// *********************************
|
||||
// Select method for binding interrupt
|
||||
//
|
||||
// - If the Bluetooth driver has registered a high-level interrupt, piggyback on that API
|
||||
// - If we're on a modern core, allocate the interrupt with the API (old cores are bugged)
|
||||
// - Otherwise use the low-level hardware API to manually bind the interrupt
|
||||
|
||||
|
||||
#if defined(CONFIG_BTDM_CTRL_HLI)
|
||||
// Espressif's bluetooth driver offers a helpful sharing layer; bring in the interrupt management calls
|
||||
#include "hal/interrupt_controller_hal.h"
|
||||
extern "C" esp_err_t hli_intr_register(intr_handler_t handler, void* arg, uint32_t intr_reg, uint32_t intr_mask);
|
||||
|
||||
#else /* !CONFIG_BTDM_CTRL_HLI*/
|
||||
|
||||
// Declare the our high-priority ISR handler
|
||||
extern "C" void ld_include_hli_vectors_rmt(); // an object with an address, but no space
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#include "soc/periph_defs.h"
|
||||
#endif
|
||||
|
||||
// Select level flag
|
||||
#if defined(__riscv)
|
||||
// RISCV chips don't block interrupts while scheduling; all we need to do is be higher than the WiFi ISR
|
||||
#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL3
|
||||
#elif defined(CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5)
|
||||
#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL4
|
||||
#else
|
||||
#define INT_LEVEL_FLAG ESP_INTR_FLAG_LEVEL5
|
||||
#endif
|
||||
|
||||
// ESP-IDF v3 cannot enable high priority interrupts through the API at all;
|
||||
// and ESP-IDF v4 on XTensa cannot enable Level 5 due to incorrect interrupt descriptor tables
|
||||
#if !defined(__XTENSA__) || (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) || ((ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) && CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_5))
|
||||
#define NEOESP32_RMT_CAN_USE_INTR_ALLOC
|
||||
|
||||
// XTensa cores require the assembly bridge
|
||||
#ifdef __XTENSA__
|
||||
#define HI_IRQ_HANDLER nullptr
|
||||
#define HI_IRQ_HANDLER_ARG ld_include_hli_vectors_rmt
|
||||
#else
|
||||
#define HI_IRQ_HANDLER NeoEsp32RmtMethodIsr
|
||||
#define HI_IRQ_HANDLER_ARG nullptr
|
||||
#endif
|
||||
|
||||
#else
|
||||
/* !CONFIG_BTDM_CTRL_HLI && !NEOESP32_RMT_CAN_USE_INTR_ALLOC */
|
||||
// This is the index of the LV5 interrupt vector - see interrupt descriptor table in idf components/hal/esp32/interrupt_descriptor_table.c
|
||||
#define ESP32_LV5_IRQ_INDEX 26
|
||||
|
||||
#endif /* NEOESP32_RMT_CAN_USE_INTR_ALLOC */
|
||||
#endif /* CONFIG_BTDM_CTRL_HLI */
|
||||
|
||||
|
||||
// RMT driver implementation
|
||||
struct NeoEsp32RmtHIChannelState {
|
||||
uint32_t rmtBit0, rmtBit1;
|
||||
uint32_t resetDuration;
|
||||
|
||||
const byte* txDataStart; // data array
|
||||
const byte* txDataEnd; // one past end
|
||||
const byte* txDataCurrent; // current location
|
||||
size_t rmtOffset;
|
||||
};
|
||||
|
||||
// Global variables
|
||||
#if defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC)
|
||||
static intr_handle_t isrHandle = nullptr;
|
||||
#endif
|
||||
|
||||
static NeoEsp32RmtHIChannelState** driverState = nullptr;
|
||||
constexpr size_t rmtBatchSize = RMT_MEM_ITEM_NUM / 2;
|
||||
|
||||
// Fill the RMT buffer memory
|
||||
// This is implemented using many arguments instead of passing the structure object to ensure we do only one lookup
|
||||
// All the arguments are passed in registers, so they don't need to be looked up again
|
||||
static void IRAM_ATTR RmtFillBuffer(uint8_t channel, const byte** src_ptr, const byte* end, uint32_t bit0, uint32_t bit1, size_t* offset_ptr, size_t reserve) {
|
||||
// We assume that (rmtToWrite % 8) == 0
|
||||
size_t rmtToWrite = rmtBatchSize - reserve;
|
||||
rmt_item32_t* dest =(rmt_item32_t*) &RMTMEM.chan[channel].data32[*offset_ptr + reserve]; // write directly in to RMT memory
|
||||
const byte* psrc = *src_ptr;
|
||||
|
||||
*offset_ptr ^= rmtBatchSize;
|
||||
|
||||
if (psrc != end) {
|
||||
while (rmtToWrite > 0) {
|
||||
uint8_t data = *psrc;
|
||||
for (uint8_t bit = 0; bit < 8; bit++)
|
||||
{
|
||||
dest->val = (data & 0x80) ? bit1 : bit0;
|
||||
dest++;
|
||||
data <<= 1;
|
||||
}
|
||||
rmtToWrite -= 8;
|
||||
psrc++;
|
||||
|
||||
if (psrc == end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*src_ptr = psrc;
|
||||
}
|
||||
|
||||
if (rmtToWrite > 0) {
|
||||
// Add end event
|
||||
rmt_item32_t bit0_val = {{.val = bit0 }};
|
||||
*dest = rmt_item32_t {{{ .duration0 = 0, .level0 = bit0_val.level1, .duration1 = 0, .level1 = bit0_val.level1 }}};
|
||||
}
|
||||
}
|
||||
|
||||
static void IRAM_ATTR RmtStartWrite(uint8_t channel, NeoEsp32RmtHIChannelState& state) {
|
||||
// Reset context state
|
||||
state.rmtOffset = 0;
|
||||
|
||||
// Fill the first part of the buffer with a reset event
|
||||
// FUTURE: we could do timing analysis with the last interrupt on this channel
|
||||
// Use 8 words to stay aligned with the buffer fill logic
|
||||
rmt_item32_t bit0_val = {{.val = state.rmtBit0 }};
|
||||
rmt_item32_t fill = {{{ .duration0 = 100, .level0 = bit0_val.level1, .duration1 = 100, .level1 = bit0_val.level1 }}};
|
||||
rmt_item32_t* dest = (rmt_item32_t*) &RMTMEM.chan[channel].data32[0];
|
||||
for (auto i = 0; i < 7; ++i) dest[i] = fill;
|
||||
fill.duration1 = state.resetDuration > 1400 ? (state.resetDuration - 1400) : 100;
|
||||
dest[7] = fill;
|
||||
|
||||
// Fill the remaining buffer with real data
|
||||
RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 8);
|
||||
RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 0);
|
||||
|
||||
// Start operation
|
||||
rmt_ll_clear_tx_thres_interrupt(&RMT, channel);
|
||||
rmt_ll_tx_reset_pointer(&RMT, channel);
|
||||
rmt_ll_tx_start(&RMT, channel);
|
||||
}
|
||||
|
||||
extern "C" void IRAM_ATTR NeoEsp32RmtMethodIsr(void *arg) {
|
||||
// Tx threshold interrupt
|
||||
uint32_t status = rmt_ll_get_tx_thres_interrupt_status(&RMT);
|
||||
while (status) {
|
||||
uint8_t channel = __builtin_ffs(status) - 1;
|
||||
if (driverState[channel]) {
|
||||
// Normal case
|
||||
NeoEsp32RmtHIChannelState& state = *driverState[channel];
|
||||
RmtFillBuffer(channel, &state.txDataCurrent, state.txDataEnd, state.rmtBit0, state.rmtBit1, &state.rmtOffset, 0);
|
||||
} else {
|
||||
// Danger - another driver got invoked?
|
||||
rmt_ll_tx_stop(&RMT, channel);
|
||||
}
|
||||
rmt_ll_clear_tx_thres_interrupt(&RMT, channel);
|
||||
status = rmt_ll_get_tx_thres_interrupt_status(&RMT);
|
||||
}
|
||||
};
|
||||
|
||||
// Wrapper around the register analysis defines
|
||||
// For all currently supported chips, this is constant for all channels; but this is not true of *all* ESP32
|
||||
static inline bool _RmtStatusIsTransmitting(rmt_channel_t channel, uint32_t status) {
|
||||
uint32_t v;
|
||||
switch(channel) {
|
||||
#ifdef RMT_STATE_CH0
|
||||
case 0: v = (status >> RMT_STATE_CH0_S) & RMT_STATE_CH0_V; break;
|
||||
#endif
|
||||
#ifdef RMT_STATE_CH1
|
||||
case 1: v = (status >> RMT_STATE_CH1_S) & RMT_STATE_CH1_V; break;
|
||||
#endif
|
||||
#ifdef RMT_STATE_CH2
|
||||
case 2: v = (status >> RMT_STATE_CH2_S) & RMT_STATE_CH2_V; break;
|
||||
#endif
|
||||
#ifdef RMT_STATE_CH3
|
||||
case 3: v = (status >> RMT_STATE_CH3_S) & RMT_STATE_CH3_V; break;
|
||||
#endif
|
||||
#ifdef RMT_STATE_CH4
|
||||
case 4: v = (status >> RMT_STATE_CH4_S) & RMT_STATE_CH4_V; break;
|
||||
#endif
|
||||
#ifdef RMT_STATE_CH5
|
||||
case 5: v = (status >> RMT_STATE_CH5_S) & RMT_STATE_CH5_V; break;
|
||||
#endif
|
||||
#ifdef RMT_STATE_CH6
|
||||
case 6: v = (status >> RMT_STATE_CH6_S) & RMT_STATE_CH6_V; break;
|
||||
#endif
|
||||
#ifdef RMT_STATE_CH7
|
||||
case 7: v = (status >> RMT_STATE_CH7_S) & RMT_STATE_CH7_V; break;
|
||||
#endif
|
||||
default: v = 0;
|
||||
}
|
||||
|
||||
return v != 0;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t NeoEsp32RmtHiMethodDriver::Install(rmt_channel_t channel, uint32_t rmtBit0, uint32_t rmtBit1, uint32_t reset) {
|
||||
// Validate channel number
|
||||
if (channel >= RMT_CHANNEL_MAX) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
esp_err_t err = ESP_OK;
|
||||
if (!driverState) {
|
||||
// First time init
|
||||
driverState = reinterpret_cast<NeoEsp32RmtHIChannelState**>(heap_caps_calloc(RMT_CHANNEL_MAX, sizeof(NeoEsp32RmtHIChannelState*), MALLOC_CAP_INTERNAL));
|
||||
if (!driverState) return ESP_ERR_NO_MEM;
|
||||
|
||||
// Ensure all interrupts are cleared before binding
|
||||
RMT.int_ena.val = 0;
|
||||
RMT.int_clr.val = 0xFFFFFFFF;
|
||||
|
||||
// Bind interrupt handler
|
||||
#if defined(CONFIG_BTDM_CTRL_HLI)
|
||||
// Bluetooth driver has taken the empty high-priority interrupt. Fortunately, it allows us to
|
||||
// hook up another handler.
|
||||
err = hli_intr_register(NeoEsp32RmtMethodIsr, nullptr, (uintptr_t) &RMT.int_st, 0xFF000000);
|
||||
// 25 is the magic number of the bluetooth ISR on ESP32 - see soc/soc.h.
|
||||
intr_matrix_set(cpu_hal_get_core_id(), ETS_RMT_INTR_SOURCE, 25);
|
||||
intr_cntrl_ll_enable_interrupts(1<<25);
|
||||
#elif defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC)
|
||||
// Use the platform code to allocate the interrupt
|
||||
// If we need the additional assembly bridge, we pass it as the "arg" to the IDF so it gets linked in
|
||||
err = esp_intr_alloc(ETS_RMT_INTR_SOURCE, INT_LEVEL_FLAG | ESP_INTR_FLAG_IRAM, HI_IRQ_HANDLER, (void*) HI_IRQ_HANDLER_ARG, &isrHandle);
|
||||
//err = ESP_ERR_NOT_FINISHED;
|
||||
#else
|
||||
// Broken IDF API does not allow us to reserve the interrupt; do it manually
|
||||
static volatile const void* __attribute__((used)) pleaseLinkAssembly = (void*) ld_include_hli_vectors_rmt;
|
||||
intr_matrix_set(xPortGetCoreID(), ETS_RMT_INTR_SOURCE, ESP32_LV5_IRQ_INDEX);
|
||||
ESP_INTR_ENABLE(ESP32_LV5_IRQ_INDEX);
|
||||
#endif
|
||||
|
||||
if (err != ESP_OK) {
|
||||
heap_caps_free(driverState);
|
||||
driverState = nullptr;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (driverState[channel] != nullptr) {
|
||||
return ESP_ERR_INVALID_STATE; // already in use
|
||||
}
|
||||
|
||||
NeoEsp32RmtHIChannelState* state = reinterpret_cast<NeoEsp32RmtHIChannelState*>(heap_caps_calloc(1, sizeof(NeoEsp32RmtHIChannelState), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
|
||||
if (state == nullptr) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// Store timing information
|
||||
state->rmtBit0 = rmtBit0;
|
||||
state->rmtBit1 = rmtBit1;
|
||||
state->resetDuration = reset;
|
||||
|
||||
// Initialize hardware
|
||||
rmt_ll_tx_stop(&RMT, channel);
|
||||
rmt_ll_tx_reset_pointer(&RMT, channel);
|
||||
rmt_ll_enable_tx_err_interrupt(&RMT, channel, false);
|
||||
rmt_ll_enable_tx_end_interrupt(&RMT, channel, false);
|
||||
rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false);
|
||||
rmt_ll_clear_tx_err_interrupt(&RMT, channel);
|
||||
rmt_ll_clear_tx_end_interrupt(&RMT, channel);
|
||||
rmt_ll_clear_tx_thres_interrupt(&RMT, channel);
|
||||
|
||||
rmt_ll_tx_enable_loop(&RMT, channel, false);
|
||||
rmt_ll_tx_enable_pingpong(&RMT, channel, true);
|
||||
rmt_ll_tx_set_limit(&RMT, channel, rmtBatchSize);
|
||||
|
||||
driverState[channel] = state;
|
||||
|
||||
rmt_ll_enable_tx_thres_interrupt(&RMT, channel, true);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t NeoEsp32RmtHiMethodDriver::Uninstall(rmt_channel_t channel) {
|
||||
if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG;
|
||||
|
||||
NeoEsp32RmtHIChannelState* state = driverState[channel];
|
||||
|
||||
WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS);
|
||||
|
||||
// Done or not, we're out of here
|
||||
rmt_ll_tx_stop(&RMT, channel);
|
||||
rmt_ll_enable_tx_thres_interrupt(&RMT, channel, false);
|
||||
driverState[channel] = nullptr;
|
||||
heap_caps_free(state);
|
||||
|
||||
#if !defined(CONFIG_BTDM_CTRL_HLI) /* Cannot unbind from bluetooth ISR */
|
||||
// Turn off the driver ISR and release global state if none are left
|
||||
for (uint8_t channelIndex = 0; channelIndex < RMT_CHANNEL_MAX; ++channelIndex) {
|
||||
if (driverState[channelIndex]) return ESP_OK; // done
|
||||
}
|
||||
|
||||
#if defined(NEOESP32_RMT_CAN_USE_INTR_ALLOC)
|
||||
esp_intr_free(isrHandle);
|
||||
#else
|
||||
ESP_INTR_DISABLE(ESP32_LV5_IRQ_INDEX);
|
||||
#endif
|
||||
|
||||
heap_caps_free(driverState);
|
||||
driverState = nullptr;
|
||||
#endif /* !defined(CONFIG_BTDM_CTRL_HLI) */
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t NeoEsp32RmtHiMethodDriver::Write(rmt_channel_t channel, const uint8_t *src, size_t src_size) {
|
||||
if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG;
|
||||
|
||||
NeoEsp32RmtHIChannelState& state = *driverState[channel];
|
||||
esp_err_t result = WaitForTxDone(channel, 10000 / portTICK_PERIOD_MS);
|
||||
|
||||
if (result == ESP_OK) {
|
||||
state.txDataStart = src;
|
||||
state.txDataCurrent = src;
|
||||
state.txDataEnd = src + src_size;
|
||||
RmtStartWrite(channel, state);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
esp_err_t NeoEsp32RmtHiMethodDriver::WaitForTxDone(rmt_channel_t channel, TickType_t wait_time) {
|
||||
if ((channel >= RMT_CHANNEL_MAX) || !driverState || !driverState[channel]) return ESP_ERR_INVALID_ARG;
|
||||
|
||||
NeoEsp32RmtHIChannelState& state = *driverState[channel];
|
||||
// yield-wait until wait_time
|
||||
esp_err_t rv = ESP_OK;
|
||||
uint32_t status;
|
||||
while(1) {
|
||||
status = rmt_ll_tx_get_channel_status(&RMT, channel);
|
||||
if (!_RmtStatusIsTransmitting(channel, status)) break;
|
||||
if (wait_time == 0) { rv = ESP_ERR_TIMEOUT; break; };
|
||||
|
||||
TickType_t sleep = std::min(wait_time, (TickType_t) 5);
|
||||
vTaskDelay(sleep);
|
||||
wait_time -= sleep;
|
||||
};
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
#endif
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "0.15.1",
|
||||
"version": "0.15.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wled",
|
||||
"version": "0.15.1",
|
||||
"version": "0.15.2",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"clean-css": "^5.3.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wled",
|
||||
"version": "0.15.1",
|
||||
"version": "0.15.2",
|
||||
"description": "Tools for WLED project",
|
||||
"main": "tools/cdata.js",
|
||||
"directories": {
|
||||
|
||||
@@ -2,6 +2,7 @@ Import('env')
|
||||
import os
|
||||
import shutil
|
||||
import gzip
|
||||
import json
|
||||
|
||||
OUTPUT_DIR = "build_output{}".format(os.path.sep)
|
||||
#OUTPUT_DIR = os.path.join("build_output")
|
||||
@@ -22,7 +23,8 @@ def create_release(source):
|
||||
release_name_def = _get_cpp_define_value(env, "WLED_RELEASE_NAME")
|
||||
if release_name_def:
|
||||
release_name = release_name_def.replace("\\\"", "")
|
||||
version = _get_cpp_define_value(env, "WLED_VERSION")
|
||||
with open("package.json", "r") as package:
|
||||
version = json.load(package)["version"]
|
||||
release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin")
|
||||
release_gz_file = release_file + ".gz"
|
||||
print(f"Copying {source} to {release_file}")
|
||||
|
||||
116
pio-scripts/set_metadata.py
Normal file
116
pio-scripts/set_metadata.py
Normal file
@@ -0,0 +1,116 @@
|
||||
Import('env')
|
||||
import subprocess
|
||||
import json
|
||||
import re
|
||||
|
||||
def get_github_repo():
|
||||
"""Extract GitHub repository name from git remote URL.
|
||||
|
||||
Uses the remote that the current branch tracks, falling back to 'origin'.
|
||||
This handles cases where repositories have multiple remotes or where the
|
||||
main remote is not named 'origin'.
|
||||
|
||||
Returns:
|
||||
str: Repository name in 'owner/repo' format for GitHub repos,
|
||||
'unknown' for non-GitHub repos, missing git CLI, or any errors.
|
||||
"""
|
||||
try:
|
||||
remote_name = 'origin' # Default fallback
|
||||
|
||||
# Try to get the remote for the current branch
|
||||
try:
|
||||
# Get current branch name
|
||||
branch_result = subprocess.run(['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
||||
capture_output=True, text=True, check=True)
|
||||
current_branch = branch_result.stdout.strip()
|
||||
|
||||
# Get the remote for the current branch
|
||||
remote_result = subprocess.run(['git', 'config', f'branch.{current_branch}.remote'],
|
||||
capture_output=True, text=True, check=True)
|
||||
tracked_remote = remote_result.stdout.strip()
|
||||
|
||||
# Use the tracked remote if we found one
|
||||
if tracked_remote:
|
||||
remote_name = tracked_remote
|
||||
except subprocess.CalledProcessError:
|
||||
# If branch config lookup fails, continue with 'origin' as fallback
|
||||
pass
|
||||
|
||||
# Get the remote URL for the determined remote
|
||||
result = subprocess.run(['git', 'remote', 'get-url', remote_name],
|
||||
capture_output=True, text=True, check=True)
|
||||
remote_url = result.stdout.strip()
|
||||
|
||||
# Check if it's a GitHub URL
|
||||
if 'github.com' not in remote_url.lower():
|
||||
return None
|
||||
|
||||
# Parse GitHub URL patterns:
|
||||
# https://github.com/owner/repo.git
|
||||
# git@github.com:owner/repo.git
|
||||
# https://github.com/owner/repo
|
||||
|
||||
# Remove .git suffix if present
|
||||
if remote_url.endswith('.git'):
|
||||
remote_url = remote_url[:-4]
|
||||
|
||||
# Handle HTTPS URLs
|
||||
https_match = re.search(r'github\.com/([^/]+/[^/]+)', remote_url, re.IGNORECASE)
|
||||
if https_match:
|
||||
return https_match.group(1)
|
||||
|
||||
# Handle SSH URLs
|
||||
ssh_match = re.search(r'github\.com:([^/]+/[^/]+)', remote_url, re.IGNORECASE)
|
||||
if ssh_match:
|
||||
return ssh_match.group(1)
|
||||
|
||||
return None
|
||||
|
||||
except FileNotFoundError:
|
||||
# Git CLI is not installed or not in PATH
|
||||
return None
|
||||
except subprocess.CalledProcessError:
|
||||
# Git command failed (e.g., not a git repo, no remote, etc.)
|
||||
return None
|
||||
except Exception:
|
||||
# Any other unexpected error
|
||||
return None
|
||||
|
||||
# WLED version is managed by package.json; this is picked up in several places
|
||||
# - It's integrated in to the UI code
|
||||
# - Here, for wled_metadata.cpp
|
||||
# - The output_bins script
|
||||
# We always take it from package.json to ensure consistency
|
||||
with open("package.json", "r") as package:
|
||||
WLED_VERSION = json.load(package)["version"]
|
||||
|
||||
def has_def(cppdefs, name):
|
||||
""" Returns true if a given name is set in a CPPDEFINES collection """
|
||||
for f in cppdefs:
|
||||
if isinstance(f, tuple):
|
||||
f = f[0]
|
||||
if f == name:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def add_wled_metadata_flags(env, node):
|
||||
cdefs = env["CPPDEFINES"].copy()
|
||||
|
||||
if not has_def(cdefs, "WLED_REPO"):
|
||||
repo = get_github_repo()
|
||||
if repo:
|
||||
cdefs.append(("WLED_REPO", f"\\\"{repo}\\\""))
|
||||
|
||||
cdefs.append(("WLED_VERSION", WLED_VERSION))
|
||||
|
||||
# This transforms the node in to a Builder; it cannot be modified again
|
||||
return env.Object(
|
||||
node,
|
||||
CPPDEFINES=cdefs
|
||||
)
|
||||
|
||||
env.AddBuildMiddleware(
|
||||
add_wled_metadata_flags,
|
||||
"*/wled_metadata.cpp"
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
Import('env')
|
||||
import json
|
||||
|
||||
PACKAGE_FILE = "package.json"
|
||||
|
||||
with open(PACKAGE_FILE, "r") as package:
|
||||
version = json.load(package)["version"]
|
||||
env.Append(BUILD_FLAGS=[f"-DWLED_VERSION={version}"])
|
||||
@@ -110,7 +110,7 @@ ldscript_4m1m = eagle.flash.4m1m.ld
|
||||
|
||||
[scripts_defaults]
|
||||
extra_scripts =
|
||||
pre:pio-scripts/set_version.py
|
||||
pre:pio-scripts/set_metadata.py
|
||||
post:pio-scripts/output_bins.py
|
||||
post:pio-scripts/strip-floats.py
|
||||
pre:pio-scripts/user_config_copy.py
|
||||
@@ -139,8 +139,7 @@ lib_deps =
|
||||
fastled/FastLED @ 3.6.0
|
||||
IRremoteESP8266 @ 2.8.2
|
||||
makuna/NeoPixelBus @ 2.8.3
|
||||
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
|
||||
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1
|
||||
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.2
|
||||
# for I2C interface
|
||||
;Wire
|
||||
# ESP-NOW library
|
||||
@@ -248,6 +247,7 @@ build_flags = -g
|
||||
-DARDUINO_ARCH_ESP32
|
||||
#-DCONFIG_LITTLEFS_FOR_IDF_3_2
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
|
||||
#use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x
|
||||
-D LOROL_LITTLEFS
|
||||
; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
|
||||
@@ -259,7 +259,7 @@ large_partitions = tools/WLED_ESP32_8MB.csv
|
||||
extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv
|
||||
lib_deps =
|
||||
https://github.com/lorol/LITTLEFS.git
|
||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||
esp32async/AsyncTCP @ 3.4.7
|
||||
${env.lib_deps}
|
||||
# additional build flags for audioreactive
|
||||
AR_build_flags = -D USERMOD_AUDIOREACTIVE
|
||||
@@ -280,10 +280,11 @@ build_flags = -g
|
||||
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
|
||||
-DARDUINO_ARCH_ESP32 -DESP32
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
|
||||
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
|
||||
-D WLED_ENABLE_DMX_INPUT
|
||||
lib_deps =
|
||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||
esp32async/AsyncTCP @ 3.4.7
|
||||
https://github.com/someweisguy/esp_dmx.git#47db25d
|
||||
${env.lib_deps}
|
||||
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
||||
@@ -298,13 +299,14 @@ build_flags = -g
|
||||
-DARDUINO_ARCH_ESP32S2
|
||||
-DCONFIG_IDF_TARGET_ESP32S2=1
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
|
||||
-DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0
|
||||
-DCO
|
||||
-DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 !
|
||||
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
||||
;; ARDUINO_USB_CDC_ON_BOOT
|
||||
lib_deps =
|
||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||
esp32async/AsyncTCP @ 3.4.7
|
||||
${env.lib_deps}
|
||||
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
||||
|
||||
@@ -318,12 +320,13 @@ build_flags = -g
|
||||
-DARDUINO_ARCH_ESP32C3
|
||||
-DCONFIG_IDF_TARGET_ESP32C3=1
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
|
||||
-DCO
|
||||
-DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3
|
||||
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
||||
;; ARDUINO_USB_CDC_ON_BOOT
|
||||
lib_deps =
|
||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||
esp32async/AsyncTCP @ 3.4.7
|
||||
${env.lib_deps}
|
||||
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
|
||||
|
||||
@@ -338,12 +341,13 @@ build_flags = -g
|
||||
-DARDUINO_ARCH_ESP32S3
|
||||
-DCONFIG_IDF_TARGET_ESP32S3=1
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
|
||||
-DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=0
|
||||
-DCO
|
||||
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
|
||||
;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT
|
||||
lib_deps =
|
||||
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
|
||||
esp32async/AsyncTCP @ 3.4.7
|
||||
${env.lib_deps}
|
||||
board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs
|
||||
|
||||
@@ -644,6 +648,7 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=
|
||||
-DLOLIN_WIFI_FIX ; seems to work much better with this
|
||||
-D WLED_WATCHDOG_TIMEOUT=0
|
||||
-D CONFIG_ASYNC_TCP_USE_WDT=0
|
||||
-D CONFIG_ASYNC_TCP_STACK_SIZE=8192
|
||||
-D DATA_PINS=16
|
||||
-D HW_PIN_SCL=35
|
||||
-D HW_PIN_SDA=33
|
||||
|
||||
@@ -370,12 +370,6 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()=====";
|
||||
name: "PAGE_update",
|
||||
method: "gzip",
|
||||
filter: "html-minify",
|
||||
mangle: (str) =>
|
||||
str
|
||||
.replace(
|
||||
/function GetV().*\<\/script\>/gms,
|
||||
"</script><script src=\"/settings/s.js?p=9\"></script>"
|
||||
)
|
||||
},
|
||||
{
|
||||
file: "welcome.htm",
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
#include <driver/i2s.h>
|
||||
#include <driver/adc.h>
|
||||
|
||||
#ifdef WLED_ENABLE_DMX
|
||||
#error This audio reactive usermod is not compatible with DMX Out.
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32) && (defined(WLED_DEBUG) || defined(SR_DEBUG))
|
||||
@@ -1225,7 +1221,6 @@ class AudioReactive : public Usermod {
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
// ADC over I2S is only possible on "classic" ESP32
|
||||
case 0:
|
||||
default:
|
||||
DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only)."));
|
||||
audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE);
|
||||
delay(100);
|
||||
@@ -1233,6 +1228,13 @@ class AudioReactive : public Usermod {
|
||||
if (audioSource) audioSource->initialize(audioPin);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 255: // 255 = -1 = no audio source
|
||||
// falls through to default
|
||||
default:
|
||||
if (audioSource) delete audioSource; audioSource = nullptr;
|
||||
enabled = false;
|
||||
break;
|
||||
}
|
||||
delay(250); // give microphone enough time to initialise
|
||||
|
||||
|
||||
@@ -580,7 +580,7 @@ uint16_t mode_twinkle(void) {
|
||||
SEGENV.step = it;
|
||||
}
|
||||
|
||||
unsigned PRNG16 = SEGENV.aux1;
|
||||
uint16_t PRNG16 = SEGENV.aux1;
|
||||
|
||||
for (unsigned i = 0; i < SEGENV.aux0; i++)
|
||||
{
|
||||
@@ -1646,8 +1646,8 @@ static const char _data_FX_MODE_TRICOLOR_WIPE[] PROGMEM = "Tri Wipe@!;1,2,3;!";
|
||||
* Modified by Aircoookie
|
||||
*/
|
||||
uint16_t mode_tricolor_fade(void) {
|
||||
unsigned counter = strip.now * ((SEGMENT.speed >> 3) +1);
|
||||
uint16_t prog = (counter * 768) >> 16;
|
||||
uint16_t counter = strip.now * ((SEGMENT.speed >> 3) +1);
|
||||
uint32_t prog = (counter * 768) >> 16;
|
||||
|
||||
uint32_t color1 = 0, color2 = 0;
|
||||
unsigned stage = 0;
|
||||
@@ -3913,7 +3913,7 @@ uint16_t mode_percent(void) {
|
||||
|
||||
return FRAMETIME;
|
||||
}
|
||||
static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@,% of fill,,,,One color;!,!;!";
|
||||
static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@!,% of fill,,,,One color;!,!;!";
|
||||
|
||||
|
||||
/*
|
||||
|
||||
@@ -486,8 +486,8 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui
|
||||
if (ofs < UINT16_MAX) offset = ofs;
|
||||
|
||||
DEBUG_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y);
|
||||
markForReset();
|
||||
if (boundsUnchanged) return;
|
||||
markForReset();
|
||||
|
||||
// apply change immediately
|
||||
if (i2 <= i1) { //disable segment
|
||||
|
||||
@@ -244,53 +244,61 @@
|
||||
typedef NeoEsp32I2s1Tm1914Method X1Tm1914Method;
|
||||
#endif
|
||||
|
||||
// RMT driver selection
|
||||
#if !defined(WLED_USE_SHARED_RMT) && !defined(__riscv)
|
||||
#include <NeoEsp32RmtHIMethod.h>
|
||||
#define NeoEsp32RmtMethod(x) NeoEsp32RmtHIN ## x ## Method
|
||||
#else
|
||||
#define NeoEsp32RmtMethod(x) NeoEsp32RmtN ## x ## Method
|
||||
#endif
|
||||
|
||||
//RGB
|
||||
#define B_32_RN_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod> // ESP32, S2, S3, C3
|
||||
#define B_32_RN_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtMethod(Ws2812x), NeoGammaNullMethod> // ESP32, S2, S3, C3
|
||||
//#define B_32_IN_NEO_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32I2sNWs2812xMethod, NeoGammaNullMethod> // ESP32 (dynamic I2S selection)
|
||||
#define B_32_I2_NEO_3 NeoPixelBusLg<NeoGrbFeature, X1Ws2812xMethod, NeoGammaNullMethod> // ESP32, S2, S3 (automatic I2S selection, see typedef above)
|
||||
#define B_32_IP_NEO_3 NeoPixelBusLg<NeoGrbFeature, X8Ws2812xMethod, NeoGammaNullMethod> // parallel I2S (ESP32, S2, S3)
|
||||
//RGBW
|
||||
#define B_32_RN_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp32RmtNSk6812Method, NeoGammaNullMethod>
|
||||
#define B_32_RN_NEO_4 NeoPixelBusLg<NeoGrbwFeature, NeoEsp32RmtMethod(Sk6812), NeoGammaNullMethod>
|
||||
#define B_32_I2_NEO_4 NeoPixelBusLg<NeoGrbwFeature, X1Sk6812Method, NeoGammaNullMethod>
|
||||
#define B_32_IP_NEO_4 NeoPixelBusLg<NeoGrbwFeature, X8Sk6812Method, NeoGammaNullMethod> // parallel I2S
|
||||
//400Kbps
|
||||
#define B_32_RN_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtN400KbpsMethod, NeoGammaNullMethod>
|
||||
#define B_32_RN_400_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtMethod(400Kbps), NeoGammaNullMethod>
|
||||
#define B_32_I2_400_3 NeoPixelBusLg<NeoGrbFeature, X1400KbpsMethod, NeoGammaNullMethod>
|
||||
#define B_32_IP_400_3 NeoPixelBusLg<NeoGrbFeature, X8400KbpsMethod, NeoGammaNullMethod> // parallel I2S
|
||||
//TM1814 (RGBW)
|
||||
#define B_32_RN_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp32RmtNTm1814Method, NeoGammaNullMethod>
|
||||
#define B_32_RN_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, NeoEsp32RmtMethod(Tm1814), NeoGammaNullMethod>
|
||||
#define B_32_I2_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, X1Tm1814Method, NeoGammaNullMethod>
|
||||
#define B_32_IP_TM1_4 NeoPixelBusLg<NeoWrgbTm1814Feature, X8Tm1814Method, NeoGammaNullMethod> // parallel I2S
|
||||
//TM1829 (RGB)
|
||||
#define B_32_RN_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp32RmtNTm1829Method, NeoGammaNullMethod>
|
||||
#define B_32_RN_TM2_3 NeoPixelBusLg<NeoBrgFeature, NeoEsp32RmtMethod(Tm1829), NeoGammaNullMethod>
|
||||
#define B_32_I2_TM2_3 NeoPixelBusLg<NeoBrgFeature, X1Tm1829Method, NeoGammaNullMethod>
|
||||
#define B_32_IP_TM2_3 NeoPixelBusLg<NeoBrgFeature, X8Tm1829Method, NeoGammaNullMethod> // parallel I2S
|
||||
//UCS8903
|
||||
#define B_32_RN_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
|
||||
#define B_32_RN_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, NeoEsp32RmtMethod(Ws2812x), NeoGammaNullMethod>
|
||||
#define B_32_I2_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, X1800KbpsMethod, NeoGammaNullMethod>
|
||||
#define B_32_IP_UCS_3 NeoPixelBusLg<NeoRgbUcs8903Feature, X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S
|
||||
//UCS8904
|
||||
#define B_32_RN_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
|
||||
#define B_32_RN_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, NeoEsp32RmtMethod(Ws2812x), NeoGammaNullMethod>
|
||||
#define B_32_I2_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, X1800KbpsMethod, NeoGammaNullMethod>
|
||||
#define B_32_IP_UCS_4 NeoPixelBusLg<NeoRgbwUcs8904Feature, X8800KbpsMethod, NeoGammaNullMethod>// parallel I2S
|
||||
//APA106
|
||||
#define B_32_RN_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtNApa106Method, NeoGammaNullMethod>
|
||||
#define B_32_RN_APA106_3 NeoPixelBusLg<NeoGrbFeature, NeoEsp32RmtMethod(Apa106), NeoGammaNullMethod>
|
||||
#define B_32_I2_APA106_3 NeoPixelBusLg<NeoGrbFeature, X1Apa106Method, NeoGammaNullMethod>
|
||||
#define B_32_IP_APA106_3 NeoPixelBusLg<NeoGrbFeature, X8Apa106Method, NeoGammaNullMethod> // parallel I2S
|
||||
//FW1906 GRBCW
|
||||
#define B_32_RN_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
|
||||
#define B_32_RN_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, NeoEsp32RmtMethod(Ws2812x), NeoGammaNullMethod>
|
||||
#define B_32_I2_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, X1800KbpsMethod, NeoGammaNullMethod>
|
||||
#define B_32_IP_FW6_5 NeoPixelBusLg<NeoGrbcwxFeature, X8800KbpsMethod, NeoGammaNullMethod> // parallel I2S
|
||||
//WS2805 RGBWC
|
||||
#define B_32_RN_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp32RmtNWs2805Method, NeoGammaNullMethod>
|
||||
#define B_32_RN_2805_5 NeoPixelBusLg<NeoGrbwwFeature, NeoEsp32RmtMethod(Ws2805), NeoGammaNullMethod>
|
||||
#define B_32_I2_2805_5 NeoPixelBusLg<NeoGrbwwFeature, X1Ws2805Method, NeoGammaNullMethod>
|
||||
#define B_32_IP_2805_5 NeoPixelBusLg<NeoGrbwwFeature, X8Ws2805Method, NeoGammaNullMethod> // parallel I2S
|
||||
//TM1914 (RGB)
|
||||
#define B_32_RN_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, NeoEsp32RmtNTm1914Method, NeoGammaNullMethod>
|
||||
#define B_32_RN_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, NeoEsp32RmtMethod(Tm1914), NeoGammaNullMethod>
|
||||
#define B_32_I2_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, X1Tm1914Method, NeoGammaNullMethod>
|
||||
#define B_32_IP_TM1914_3 NeoPixelBusLg<NeoGrbTm1914Feature, X8Tm1914Method, NeoGammaNullMethod> // parallel I2S
|
||||
//Sm16825 (RGBWC)
|
||||
#define B_32_RN_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, NeoEsp32RmtNWs2812xMethod, NeoGammaNullMethod>
|
||||
#define B_32_RN_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, NeoEsp32RmtMethod(Ws2812x), NeoGammaNullMethod>
|
||||
#define B_32_I2_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, X1Ws2812xMethod, NeoGammaNullMethod>
|
||||
#define B_32_IP_SM16825_5 NeoPixelBusLg<NeoRgbcwSm16825eFeature, X8Ws2812xMethod, NeoGammaNullMethod> // parallel I2S
|
||||
#endif
|
||||
@@ -1209,54 +1217,54 @@ class PolyBus {
|
||||
switch (busType) {
|
||||
case I_NONE: break;
|
||||
#ifdef ESP8266
|
||||
case I_8266_U0_NEO_3: size = (static_cast<B_8266_U0_NEO_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U1_NEO_3: size = (static_cast<B_8266_U1_NEO_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U0_NEO_3: size = (static_cast<B_8266_U0_NEO_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U1_NEO_3: size = (static_cast<B_8266_U1_NEO_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_DM_NEO_3: size = (static_cast<B_8266_DM_NEO_3*>(busPtr))->PixelsSize()*5; break;
|
||||
case I_8266_BB_NEO_3: size = (static_cast<B_8266_BB_NEO_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U0_NEO_4: size = (static_cast<B_8266_U0_NEO_4*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U1_NEO_4: size = (static_cast<B_8266_U1_NEO_4*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_BB_NEO_3: size = (static_cast<B_8266_BB_NEO_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U0_NEO_4: size = (static_cast<B_8266_U0_NEO_4*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U1_NEO_4: size = (static_cast<B_8266_U1_NEO_4*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_DM_NEO_4: size = (static_cast<B_8266_DM_NEO_4*>(busPtr))->PixelsSize()*5; break;
|
||||
case I_8266_BB_NEO_4: size = (static_cast<B_8266_BB_NEO_4*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U0_400_3: size = (static_cast<B_8266_U0_400_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U1_400_3: size = (static_cast<B_8266_U1_400_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_BB_NEO_4: size = (static_cast<B_8266_BB_NEO_4*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U0_400_3: size = (static_cast<B_8266_U0_400_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U1_400_3: size = (static_cast<B_8266_U1_400_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_DM_400_3: size = (static_cast<B_8266_DM_400_3*>(busPtr))->PixelsSize()*5; break;
|
||||
case I_8266_BB_400_3: size = (static_cast<B_8266_BB_400_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U0_TM1_4: size = (static_cast<B_8266_U0_TM1_4*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U1_TM1_4: size = (static_cast<B_8266_U1_TM1_4*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_BB_400_3: size = (static_cast<B_8266_BB_400_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U0_TM1_4: size = (static_cast<B_8266_U0_TM1_4*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U1_TM1_4: size = (static_cast<B_8266_U1_TM1_4*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_DM_TM1_4: size = (static_cast<B_8266_DM_TM1_4*>(busPtr))->PixelsSize()*5; break;
|
||||
case I_8266_BB_TM1_4: size = (static_cast<B_8266_BB_TM1_4*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U0_TM2_3: size = (static_cast<B_8266_U0_TM2_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U1_TM2_3: size = (static_cast<B_8266_U1_TM2_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_BB_TM1_4: size = (static_cast<B_8266_BB_TM1_4*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U0_TM2_3: size = (static_cast<B_8266_U0_TM2_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U1_TM2_3: size = (static_cast<B_8266_U1_TM2_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_DM_TM2_3: size = (static_cast<B_8266_DM_TM2_3*>(busPtr))->PixelsSize()*5; break;
|
||||
case I_8266_BB_TM2_3: size = (static_cast<B_8266_BB_TM2_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U0_UCS_3: size = (static_cast<B_8266_U0_UCS_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U1_UCS_3: size = (static_cast<B_8266_U1_UCS_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_BB_TM2_3: size = (static_cast<B_8266_BB_TM2_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U0_UCS_3: size = (static_cast<B_8266_U0_UCS_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U1_UCS_3: size = (static_cast<B_8266_U1_UCS_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_DM_UCS_3: size = (static_cast<B_8266_DM_UCS_3*>(busPtr))->PixelsSize()*5; break;
|
||||
case I_8266_BB_UCS_3: size = (static_cast<B_8266_BB_UCS_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U0_UCS_4: size = (static_cast<B_8266_U0_UCS_4*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U1_UCS_4: size = (static_cast<B_8266_U1_UCS_4*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_BB_UCS_3: size = (static_cast<B_8266_BB_UCS_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U0_UCS_4: size = (static_cast<B_8266_U0_UCS_4*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U1_UCS_4: size = (static_cast<B_8266_U1_UCS_4*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_DM_UCS_4: size = (static_cast<B_8266_DM_UCS_4*>(busPtr))->PixelsSize()*5; break;
|
||||
case I_8266_BB_UCS_4: size = (static_cast<B_8266_BB_UCS_4*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U0_APA106_3: size = (static_cast<B_8266_U0_APA106_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U1_APA106_3: size = (static_cast<B_8266_U1_APA106_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_BB_UCS_4: size = (static_cast<B_8266_BB_UCS_4*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U0_APA106_3: size = (static_cast<B_8266_U0_APA106_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U1_APA106_3: size = (static_cast<B_8266_U1_APA106_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_DM_APA106_3: size = (static_cast<B_8266_DM_APA106_3*>(busPtr))->PixelsSize()*5; break;
|
||||
case I_8266_BB_APA106_3: size = (static_cast<B_8266_BB_APA106_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U0_FW6_5: size = (static_cast<B_8266_U0_FW6_5*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U1_FW6_5: size = (static_cast<B_8266_U1_FW6_5*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_BB_APA106_3: size = (static_cast<B_8266_BB_APA106_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U0_FW6_5: size = (static_cast<B_8266_U0_FW6_5*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U1_FW6_5: size = (static_cast<B_8266_U1_FW6_5*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_DM_FW6_5: size = (static_cast<B_8266_DM_FW6_5*>(busPtr))->PixelsSize()*5; break;
|
||||
case I_8266_BB_FW6_5: size = (static_cast<B_8266_BB_FW6_5*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U0_2805_5: size = (static_cast<B_8266_U0_2805_5*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U1_2805_5: size = (static_cast<B_8266_U1_2805_5*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_BB_FW6_5: size = (static_cast<B_8266_BB_FW6_5*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U0_2805_5: size = (static_cast<B_8266_U0_2805_5*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U1_2805_5: size = (static_cast<B_8266_U1_2805_5*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_DM_2805_5: size = (static_cast<B_8266_DM_2805_5*>(busPtr))->PixelsSize()*5; break;
|
||||
case I_8266_BB_2805_5: size = (static_cast<B_8266_BB_2805_5*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U0_TM1914_3: size = (static_cast<B_8266_U0_TM1914_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U1_TM1914_3: size = (static_cast<B_8266_U1_TM1914_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_BB_2805_5: size = (static_cast<B_8266_BB_2805_5*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U0_TM1914_3: size = (static_cast<B_8266_U0_TM1914_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U1_TM1914_3: size = (static_cast<B_8266_U1_TM1914_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_DM_TM1914_3: size = (static_cast<B_8266_DM_TM1914_3*>(busPtr))->PixelsSize()*5; break;
|
||||
case I_8266_BB_TM1914_3: size = (static_cast<B_8266_BB_TM1914_3*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U0_SM16825_5: size = (static_cast<B_8266_U0_SM16825_5*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_U1_SM16825_5: size = (static_cast<B_8266_U1_SM16825_5*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_BB_TM1914_3: size = (static_cast<B_8266_BB_TM1914_3*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U0_SM16825_5: size = (static_cast<B_8266_U0_SM16825_5*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_U1_SM16825_5: size = (static_cast<B_8266_U1_SM16825_5*>(busPtr))->PixelsSize(); break;
|
||||
case I_8266_DM_SM16825_5: size = (static_cast<B_8266_DM_SM16825_5*>(busPtr))->PixelsSize()*5; break;
|
||||
case I_8266_BB_SM16825_5: size = (static_cast<B_8266_BB_SM16825_5*>(busPtr))->PixelsSize()*2; break;
|
||||
case I_8266_BB_SM16825_5: size = (static_cast<B_8266_BB_SM16825_5*>(busPtr))->PixelsSize(); break;
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// RMT buses (front + back + small system managed RMT)
|
||||
@@ -1308,68 +1316,65 @@ class PolyBus {
|
||||
case I_NONE: size = 0; break;
|
||||
#ifdef ESP8266
|
||||
// UART methods have front + back buffers + small UART
|
||||
case I_8266_U0_NEO_4: size = (size + count)*2; break; // 4 channels
|
||||
case I_8266_U1_NEO_4: size = (size + count)*2; break; // 4 channels
|
||||
case I_8266_BB_NEO_4: size = (size + count)*2; break; // 4 channels
|
||||
case I_8266_U0_TM1_4: size = (size + count)*2; break; // 4 channels
|
||||
case I_8266_U1_TM1_4: size = (size + count)*2; break; // 4 channels
|
||||
case I_8266_BB_TM1_4: size = (size + count)*2; break; // 4 channels
|
||||
case I_8266_U0_UCS_3: size *= 4; break; // 16 bit
|
||||
case I_8266_U1_UCS_3: size *= 4; break; // 16 bit
|
||||
case I_8266_BB_UCS_3: size *= 4; break; // 16 bit
|
||||
case I_8266_U0_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels
|
||||
case I_8266_U1_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels
|
||||
case I_8266_BB_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels
|
||||
case I_8266_U0_FW6_5: size = (size + 2*count)*2; break; // 5 channels
|
||||
case I_8266_U1_FW6_5: size = (size + 2*count)*2; break; // 5channels
|
||||
case I_8266_BB_FW6_5: size = (size + 2*count)*2; break; // 5 channels
|
||||
case I_8266_U0_2805_5: size = (size + 2*count)*2; break; // 5 channels
|
||||
case I_8266_U1_2805_5: size = (size + 2*count)*2; break; // 5 channels
|
||||
case I_8266_BB_2805_5: size = (size + 2*count)*2; break; // 5 channels
|
||||
case I_8266_U0_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels
|
||||
case I_8266_U1_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels
|
||||
case I_8266_BB_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels
|
||||
// DMA methods have front + DMA buffer = ((1+(3+1)) * channels)
|
||||
case I_8266_DM_NEO_3: size *= 5; break;
|
||||
case I_8266_DM_NEO_4: size = (size + count)*5; break;
|
||||
case I_8266_DM_400_3: size *= 5; break;
|
||||
case I_8266_DM_TM1_4: size = (size + count)*5; break;
|
||||
case I_8266_DM_TM2_3: size *= 5; break;
|
||||
case I_8266_DM_UCS_3: size *= 2*5; break;
|
||||
case I_8266_DM_UCS_4: size = (size + count)*2*5; break;
|
||||
case I_8266_DM_APA106_3: size *= 5; break;
|
||||
case I_8266_DM_FW6_5: size = (size + 2*count)*5; break;
|
||||
case I_8266_DM_2805_5: size = (size + 2*count)*5; break;
|
||||
case I_8266_DM_TM1914_3: size *= 5; break;
|
||||
case I_8266_U0_NEO_4 : // fallthrough
|
||||
case I_8266_U1_NEO_4 : // fallthrough
|
||||
case I_8266_BB_NEO_4 : // fallthrough
|
||||
case I_8266_U0_TM1_4 : // fallthrough
|
||||
case I_8266_U1_TM1_4 : // fallthrough
|
||||
case I_8266_BB_TM1_4 : size = (size + count); break; // 4 channels
|
||||
case I_8266_U0_UCS_3 : // fallthrough
|
||||
case I_8266_U1_UCS_3 : // fallthrough
|
||||
case I_8266_BB_UCS_3 : size *= 2; break; // 16 bit
|
||||
case I_8266_U0_UCS_4 : // fallthrough
|
||||
case I_8266_U1_UCS_4 : // fallthrough
|
||||
case I_8266_BB_UCS_4 : size = (size + count)*2; break; // 16 bit 4 channels
|
||||
case I_8266_U0_FW6_5 : // fallthrough
|
||||
case I_8266_U1_FW6_5 : // fallthrough
|
||||
case I_8266_BB_FW6_5 : // fallthrough
|
||||
case I_8266_U0_2805_5 : // fallthrough
|
||||
case I_8266_U1_2805_5 : // fallthrough
|
||||
case I_8266_BB_2805_5 : size = (size + 2*count); break; // 5 channels
|
||||
case I_8266_U0_SM16825_5: // fallthrough
|
||||
case I_8266_U1_SM16825_5: // fallthrough
|
||||
case I_8266_BB_SM16825_5: size = (size + 2*count)*2; break; // 16 bit 5 channels
|
||||
// DMA methods have front + DMA buffer = ((1+(3+1)) * channels; exact value is a bit of mistery - needs a dig into NPB)
|
||||
case I_8266_DM_NEO_3 : // fallthrough
|
||||
case I_8266_DM_400_3 : // fallthrough
|
||||
case I_8266_DM_TM2_3 : // fallthrough
|
||||
case I_8266_DM_APA106_3 : // fallthrough
|
||||
case I_8266_DM_TM1914_3 : size *= 5; break;
|
||||
case I_8266_DM_NEO_4 : // fallthrough
|
||||
case I_8266_DM_TM1_4 : size = (size + count)*5; break;
|
||||
case I_8266_DM_UCS_3 : size *= 2*5; break;
|
||||
case I_8266_DM_UCS_4 : size = (size + count)*2*5; break;
|
||||
case I_8266_DM_FW6_5 : // fallthrough
|
||||
case I_8266_DM_2805_5 : size = (size + 2*count)*5; break;
|
||||
case I_8266_DM_SM16825_5: size = (size + 2*count)*2*5; break;
|
||||
#endif
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// RMT buses (1x front and 1x back buffer)
|
||||
case I_32_RN_NEO_4: size = (size + count)*2; break;
|
||||
case I_32_RN_TM1_4: size = (size + count)*2; break;
|
||||
case I_32_RN_UCS_3: size *= 2*2; break;
|
||||
case I_32_RN_UCS_4: size = (size + count)*2*2; break;
|
||||
case I_32_RN_FW6_5: size = (size + 2*count)*2; break;
|
||||
case I_32_RN_2805_5: size = (size + 2*count)*2; break;
|
||||
case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break;
|
||||
// I2S1 bus or paralell buses (individual 1x front and 1 DMA (3x or 4x pixel count) or common back DMA buffers)
|
||||
#else
|
||||
// RMT buses (1x front and 1x back buffer, does not include small RMT buffer)
|
||||
case I_32_RN_NEO_4 : // fallthrough
|
||||
case I_32_RN_TM1_4 : size = (size + count)*2; break; // 4 channels
|
||||
case I_32_RN_UCS_3 : size *= 2*2; break; // 16bit
|
||||
case I_32_RN_UCS_4 : size = (size + count)*2*2; break; // 16bit, 4 channels
|
||||
case I_32_RN_FW6_5 : // fallthrough
|
||||
case I_32_RN_2805_5 : size = (size + 2*count)*2; break; // 5 channels
|
||||
case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break; // 16bit, 5 channels
|
||||
// I2S1 bus or paralell I2S1 buses (1x front, does not include DMA buffer which is front*cadence, a bit(?) more for LCD)
|
||||
#ifndef CONFIG_IDF_TARGET_ESP32C3
|
||||
case I_32_I2_NEO_3: size *= 4; break;
|
||||
case I_32_I2_NEO_4: size = (size + count)*4; break;
|
||||
case I_32_I2_400_3: size *= 4; break;
|
||||
case I_32_I2_TM1_4: size = (size + count)*4; break;
|
||||
case I_32_I2_TM2_3: size *= 4; break;
|
||||
case I_32_I2_UCS_3: size *= 2*4; break;
|
||||
case I_32_I2_UCS_4: size = (size + count)*2*4; break;
|
||||
case I_32_I2_APA106_3: size *= 4; break;
|
||||
case I_32_I2_FW6_5: size = (size + 2*count)*4; break;
|
||||
case I_32_I2_2805_5: size = (size + 2*count)*4; break;
|
||||
case I_32_I2_TM1914_3: size *= 4; break;
|
||||
case I_32_I2_SM16825_5: size = (size + 2*count)*2*4; break;
|
||||
case I_32_I2_NEO_3 : // fallthrough
|
||||
case I_32_I2_400_3 : // fallthrough
|
||||
case I_32_I2_TM2_3 : // fallthrough
|
||||
case I_32_I2_APA106_3 : break; // do nothing, I2S uses single buffer + DMA buffer
|
||||
case I_32_I2_NEO_4 : // fallthrough
|
||||
case I_32_I2_TM1_4 : size = (size + count); break; // 4 channels
|
||||
case I_32_I2_UCS_3 : size *= 2; break; // 16 bit
|
||||
case I_32_I2_UCS_4 : size = (size + count)*2; break; // 16 bit, 4 channels
|
||||
case I_32_I2_FW6_5 : // fallthrough
|
||||
case I_32_I2_2805_5 : size = (size + 2*count); break; // 5 channels
|
||||
case I_32_I2_SM16825_5: size = (size + 2*count)*2; break; // 16 bit, 5 channels
|
||||
#endif
|
||||
default : size *= 2; break; // everything else uses 2 buffers
|
||||
#endif
|
||||
// everything else uses 2 buffers
|
||||
default: size *= 2; break;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -636,9 +636,32 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
|
||||
return (doc["sv"] | true);
|
||||
}
|
||||
|
||||
|
||||
static const char s_cfg_json[] PROGMEM = "/cfg.json";
|
||||
|
||||
bool backupConfig() {
|
||||
return backupFile(s_cfg_json);
|
||||
}
|
||||
|
||||
bool restoreConfig() {
|
||||
return restoreFile(s_cfg_json);
|
||||
}
|
||||
|
||||
bool verifyConfig() {
|
||||
return validateJsonFile(s_cfg_json);
|
||||
}
|
||||
|
||||
// rename config file and reboot
|
||||
// if the cfg file doesn't exist, such as after a reset, do nothing
|
||||
void resetConfig() {
|
||||
if (WLED_FS.exists(s_cfg_json)) {
|
||||
DEBUG_PRINTLN(F("Reset config"));
|
||||
char backupname[32];
|
||||
snprintf_P(backupname, sizeof(backupname), PSTR("/rst.%s"), &s_cfg_json[1]);
|
||||
WLED_FS.rename(s_cfg_json, backupname);
|
||||
doReboot = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool deserializeConfigFromFS() {
|
||||
[[maybe_unused]] bool success = deserializeConfigSec();
|
||||
#ifdef WLED_ADD_EEPROM_SUPPORT
|
||||
@@ -676,6 +699,7 @@ bool deserializeConfigFromFS() {
|
||||
|
||||
void serializeConfig() {
|
||||
serializeConfigSec();
|
||||
backupConfig(); // backup before writing new config
|
||||
|
||||
DEBUG_PRINTLN(F("Writing settings to /cfg.json..."));
|
||||
|
||||
|
||||
@@ -793,7 +793,7 @@ input[type=range]::-moz-range-thumb {
|
||||
/* buttons */
|
||||
.btn {
|
||||
padding: 8px;
|
||||
/*margin: 10px 4px;*/
|
||||
margin: 10px 4px;
|
||||
width: 230px;
|
||||
font-size: 19px;
|
||||
color: var(--c-d);
|
||||
|
||||
@@ -688,6 +688,8 @@ function parseInfo(i) {
|
||||
// gId("filterVol").classList.add("hide"); hideModes(" ♪"); // hide volume reactive effects
|
||||
// gId("filterFreq").classList.add("hide"); hideModes(" ♫"); // hide frequency reactive effects
|
||||
// }
|
||||
// Check for version upgrades on page load
|
||||
checkVersionUpgrade(i);
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/questions/2592092/executing-script-elements-inserted-with-innerhtml
|
||||
@@ -3248,6 +3250,191 @@ function simplifyUI() {
|
||||
gId("btns").style.display = "none";
|
||||
}
|
||||
|
||||
// Version reporting feature
|
||||
var versionCheckDone = false;
|
||||
|
||||
function checkVersionUpgrade(info) {
|
||||
// Only check once per page load
|
||||
if (versionCheckDone) return;
|
||||
versionCheckDone = true;
|
||||
|
||||
// Suppress feature if in AP mode (no internet connection available)
|
||||
if (info.wifi && info.wifi.ap) return;
|
||||
|
||||
// Fetch version-info.json using existing /edit endpoint
|
||||
fetch(getURL('/edit?edit=/version-info.json'), {
|
||||
method: 'get'
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status === 404) {
|
||||
// File doesn't exist - first install, show install prompt
|
||||
showVersionUpgradePrompt(info, null, info.ver);
|
||||
return null;
|
||||
}
|
||||
if (!res.ok) {
|
||||
throw new Error('Failed to fetch version-info.json');
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(versionInfo => {
|
||||
if (!versionInfo) return; // 404 case already handled
|
||||
|
||||
// Check if user opted out
|
||||
if (versionInfo.neverAsk) return;
|
||||
|
||||
// Check if version has changed
|
||||
const currentVersion = info.ver;
|
||||
const storedVersion = versionInfo.version || '';
|
||||
|
||||
if (storedVersion && storedVersion !== currentVersion) {
|
||||
// Version has changed, show upgrade prompt
|
||||
showVersionUpgradePrompt(info, storedVersion, currentVersion);
|
||||
} else if (!storedVersion) {
|
||||
// Empty version in file, show install prompt
|
||||
showVersionUpgradePrompt(info, null, currentVersion);
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
console.log('Failed to load version-info.json', e);
|
||||
});
|
||||
}
|
||||
|
||||
function showVersionUpgradePrompt(info, oldVersion, newVersion) {
|
||||
// Determine if this is an install or upgrade
|
||||
const isInstall = !oldVersion;
|
||||
|
||||
// Create overlay and dialog
|
||||
const overlay = d.createElement('div');
|
||||
overlay.id = 'versionUpgradeOverlay';
|
||||
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center;';
|
||||
|
||||
const dialog = d.createElement('div');
|
||||
dialog.style.cssText = 'background:var(--c-1);border-radius:10px;padding:25px;max-width:500px;margin:20px;box-shadow:0 4px 6px rgba(0,0,0,0.3);';
|
||||
|
||||
// Build contextual message based on install vs upgrade
|
||||
const title = isInstall
|
||||
? '🎉 Thank you for installing WLED!'
|
||||
: '🎉 WLED Upgrade Detected!';
|
||||
|
||||
const description = isInstall
|
||||
? `You are now running WLED <strong style="text-wrap: nowrap">${newVersion}</strong>.`
|
||||
: `Your WLED has been upgraded from <strong style="text-wrap: nowrap">${oldVersion}</strong> to <strong style="text-wrap: nowrap">${newVersion}</strong>.`;
|
||||
|
||||
const question = 'Help make WLED better with a one-time hardware report? It includes only device details like chip type, LED count, etc. — never personal data or your activities.'
|
||||
|
||||
dialog.innerHTML = `
|
||||
<h2 style="margin-top:0;color:var(--c-f);">${title}</h2>
|
||||
<p style="color:var(--c-f);">${description}</p>
|
||||
<p style="color:var(--c-f);">${question}</p>
|
||||
<p style="color:var(--c-f);font-size:0.9em;">
|
||||
<a href="https://kno.wled.ge/about/privacy-policy/" target="_blank" style="color:var(--c-6);">Learn more about what data is collected and why</a>
|
||||
</p>
|
||||
<div style="margin-top:20px;">
|
||||
<button id="versionReportYes" class="btn">Yes</button>
|
||||
<button id="versionReportNo" class="btn">Not Now</button>
|
||||
<button id="versionReportNever" class="btn">Never Ask</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
overlay.appendChild(dialog);
|
||||
d.body.appendChild(overlay);
|
||||
|
||||
// Add event listeners
|
||||
gId('versionReportYes').addEventListener('click', () => {
|
||||
reportUpgradeEvent(info, oldVersion);
|
||||
d.body.removeChild(overlay);
|
||||
});
|
||||
|
||||
gId('versionReportNo').addEventListener('click', () => {
|
||||
// Don't update version, will ask again on next load
|
||||
d.body.removeChild(overlay);
|
||||
});
|
||||
|
||||
gId('versionReportNever').addEventListener('click', () => {
|
||||
updateVersionInfo(newVersion, true);
|
||||
d.body.removeChild(overlay);
|
||||
showToast('You will not be asked again.');
|
||||
});
|
||||
}
|
||||
|
||||
function reportUpgradeEvent(info, oldVersion) {
|
||||
showToast('Reporting upgrade...');
|
||||
|
||||
// Fetch fresh data from /json/info endpoint as requested
|
||||
fetch(getURL('/json/info'), {
|
||||
method: 'get'
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(infoData => {
|
||||
// Map to UpgradeEventRequest structure per OpenAPI spec
|
||||
// Required fields: deviceId, version, previousVersion, releaseName, chip, ledCount, isMatrix, bootloaderSHA256
|
||||
const upgradeData = {
|
||||
deviceId: infoData.deviceId, // Use anonymous unique device ID
|
||||
version: infoData.ver || '', // Current version string
|
||||
previousVersion: oldVersion || '', // Previous version from version-info.json
|
||||
releaseName: infoData.release || '', // Release name (e.g., "WLED 0.15.0")
|
||||
chip: infoData.arch || '', // Chip architecture (esp32, esp8266, etc)
|
||||
ledCount: infoData.leds ? infoData.leds.count : 0, // Number of LEDs
|
||||
isMatrix: !!(infoData.leds && infoData.leds.matrix), // Whether it's a 2D matrix setup
|
||||
bootloaderSHA256: infoData.bootloaderSHA256 || '', // Bootloader SHA256 hash
|
||||
brand: infoData.brand, // Device brand (always present)
|
||||
product: infoData.product, // Product name (always present)
|
||||
flashSize: infoData.flash // Flash size (always present)
|
||||
};
|
||||
|
||||
// Add optional fields if available
|
||||
if (infoData.psram !== undefined) upgradeData.psramSize = Math.round(infoData.psram / (1024 * 1024)); // convert bytes to MB
|
||||
// Note: partitionSizes not currently available in /json/info endpoint
|
||||
|
||||
// Make AJAX call to postUpgradeEvent API
|
||||
return fetch('https://usage.wled.me/api/usage/upgrade', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(upgradeData)
|
||||
});
|
||||
})
|
||||
.then(res => {
|
||||
if (res.ok) {
|
||||
showToast('Thank you for reporting!');
|
||||
updateVersionInfo(info.ver, false);
|
||||
} else {
|
||||
showToast('Report failed. Please try again later.', true);
|
||||
// Do NOT update version info on failure - user will be prompted again
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
console.log('Failed to report upgrade', e);
|
||||
showToast('Report failed. Please try again later.', true);
|
||||
// Do NOT update version info on error - user will be prompted again
|
||||
});
|
||||
}
|
||||
|
||||
function updateVersionInfo(version, neverAsk) {
|
||||
const versionInfo = {
|
||||
version: version,
|
||||
neverAsk: neverAsk
|
||||
};
|
||||
|
||||
// Create a Blob with JSON content and use /upload endpoint
|
||||
const blob = new Blob([JSON.stringify(versionInfo)], {type: 'application/json'});
|
||||
const formData = new FormData();
|
||||
formData.append('data', blob, 'version-info.json');
|
||||
|
||||
fetch(getURL('/upload'), {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(res => res.text())
|
||||
.then(data => {
|
||||
console.log('Version info updated', data);
|
||||
})
|
||||
.catch(e => {
|
||||
console.log('Failed to update version-info.json', e);
|
||||
});
|
||||
}
|
||||
|
||||
size();
|
||||
_C.style.setProperty('--n', N);
|
||||
|
||||
|
||||
@@ -198,6 +198,7 @@
|
||||
let dbl = 0;
|
||||
let ch = 3*hasRGB(t) + hasW(t) + hasCCT(t);
|
||||
let mul = 1;
|
||||
if (d.Sf.LD.checked) dbl = len * ch; // global buffer
|
||||
if (isDig(t)) {
|
||||
if (is16b(t)) len *= 2; // 16 bit LEDs
|
||||
if (maxM < 10000 && d.getElementsByName("L0"+n)[0].value == 3) { //8266 DMA uses 5x the mem
|
||||
@@ -206,7 +207,6 @@
|
||||
if (maxM >= 10000) { //ESP32 RMT uses double buffer?
|
||||
mul = 2;
|
||||
}
|
||||
if (d.Sf.LD.checked) dbl = len * ch; // double buffering
|
||||
}
|
||||
return len * ch * mul + dbl;
|
||||
}
|
||||
@@ -254,10 +254,10 @@
|
||||
}
|
||||
|
||||
// enable/disable LED fields
|
||||
updateTypeDropdowns(); // restrict bus types in dropdowns to max allowed digital/analog buses
|
||||
let dC = 0; // count of digital buses (for parallel I2S)
|
||||
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
|
||||
LTs.forEach((s,i)=>{
|
||||
if (i < LTs.length-1) s.disabled = true; // prevent changing type (as we can't update options)
|
||||
// is the field a LED type?
|
||||
var n = s.name.substring(2);
|
||||
var t = parseInt(s.value);
|
||||
@@ -414,17 +414,7 @@
|
||||
{
|
||||
var o = gEBCN("iST");
|
||||
var i = o.length;
|
||||
let disable = (sel,opt) => { sel.querySelectorAll(opt).forEach((o)=>{o.disabled=true;}); }
|
||||
|
||||
var f = gId("mLC");
|
||||
let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0;
|
||||
f.querySelectorAll("select[name^=LT]").forEach((s)=>{
|
||||
let t = s.value;
|
||||
if (isDig(t) && !isD2P(t)) digitalB++;
|
||||
if (isD2P(t)) twopinB++;
|
||||
if (isPWM(t)) analogB += numPins(t); // each GPIO is assigned to a channel
|
||||
if (isVir(t)) virtB++;
|
||||
});
|
||||
|
||||
if ((n==1 && i>=maxB+maxV) || (n==-1 && i==0)) return;
|
||||
var s = chrID(i);
|
||||
@@ -434,7 +424,7 @@
|
||||
var cn = `<div class="iST">
|
||||
<hr class="sml">
|
||||
${i+1}:
|
||||
<select name="LT${s}" onchange="UI(true)"></select><br>
|
||||
<select name="LT${s}" onchange="updateTypeDropdowns();UI(true)"></select><br>
|
||||
<div id="abl${s}">
|
||||
mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
|
||||
<option value="55" selected>55mA (typ. 5V WS281x)</option>
|
||||
@@ -488,18 +478,15 @@ mA/LED: <select name="LAsel${s}" onchange="enLA(this,'${s}');UI();">
|
||||
}
|
||||
});
|
||||
enLA(d.Sf["LAsel"+s],s); // update LED mA
|
||||
// disable inappropriate LED types
|
||||
// temporarily set to virtual (network) type to avoid "same type" exception during dropdown update
|
||||
let sel = d.getElementsByName("LT"+s)[0];
|
||||
// 32 & S2 supports mono I2S as well as parallel so we need to take that into account; S3 only supports parallel
|
||||
let maxDB = maxD - (is32() || isS2() || isS3() ? (!d.Sf["PR"].checked)*8 - (!isS3()) : 0); // adjust max digital buses if parallel I2S is not used
|
||||
if (digitalB >= maxDB) disable(sel,'option[data-type="D"]'); // NOTE: see isDig()
|
||||
if (twopinB >= 2) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P() (we will only allow 2 2pin buses)
|
||||
disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM()
|
||||
sel.value = sel.querySelector('option[data-type="N"]').value;
|
||||
updateTypeDropdowns(); // update valid bus options including this new one
|
||||
sel.selectedIndex = sel.querySelector('option:not(:disabled)').index;
|
||||
updateTypeDropdowns(); // update again for the newly selected type
|
||||
}
|
||||
if (n==-1) {
|
||||
o[--i].remove();--i;
|
||||
o[i].querySelector("[name^=LT]").disabled = false;
|
||||
}
|
||||
|
||||
gId("+").style.display = (i<maxB+maxV-1) ? "inline":"none";
|
||||
@@ -763,6 +750,34 @@ Swap: <select id="xw${s}" name="XW${s}">
|
||||
}
|
||||
return opt;
|
||||
}
|
||||
// dynamically enforce bus type availability based on current usage
|
||||
function updateTypeDropdowns() {
|
||||
let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]");
|
||||
let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0;
|
||||
// count currently used buses
|
||||
LTs.forEach(sel => {
|
||||
let t = parseInt(sel.value);
|
||||
if (isDig(t) && !isD2P(t)) digitalB++;
|
||||
if (isPWM(t)) analogB += numPins(t);
|
||||
if (isD2P(t)) twopinB++;
|
||||
if (isVir(t)) virtB++;
|
||||
});
|
||||
// enable/disable type options according to limits in dropdowns
|
||||
LTs.forEach(sel => {
|
||||
const curType = parseInt(sel.value);
|
||||
const disable = (q) => sel.querySelectorAll(q).forEach(o => o.disabled = true);
|
||||
const enable = (q) => sel.querySelectorAll(q).forEach(o => o.disabled = false);
|
||||
enable('option'); // reset all first
|
||||
// max digital buses: ESP32 & S2 support mono I2S as well as parallel so we need to take that into account; S3 only supports parallel
|
||||
// supported outputs using parallel I2S/mono I2S: S2: 12/5, S3: 12/4, ESP32: 16/9
|
||||
let maxDB = maxD - ((is32() || isS2() || isS3()) ? (!d.Sf["PR"].checked) * 8 - (!isS3()) : 0); // adjust max digital buses if parallel I2S is not used
|
||||
// disallow adding more of a type that has reached its limit but allow changing the current type
|
||||
if (digitalB >= maxDB && !(isDig(curType) && !isD2P(curType))) disable('option[data-type="D"]');
|
||||
if (twopinB >= 2 && !isD2P(curType)) disable('option[data-type="2P"]');
|
||||
// Disable PWM types that need more pins than available (accounting for current type's pins if PWM)
|
||||
disable(`option[data-type^="${'A'.repeat(maxA - analogB + (isPWM(curType)?numPins(curType):0) + 1)}"]`);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style>@import url("style.css");</style>
|
||||
</head>
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
A huge thank you to everyone who helped me create WLED!<br><br>
|
||||
(c) 2016-2024 Christian Schwinne <br>
|
||||
<i>Licensed under the <a href="https://github.com/Aircoookie/WLED/blob/master/LICENSE" target="_blank">EUPL v1.2 license</a></i><br><br>
|
||||
Server message: <span class="sip"> Response error! </span><hr>
|
||||
Installed version: <span class="sip">WLED ##VERSION##</span><hr>
|
||||
<div id="toast"></div>
|
||||
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
|
||||
</form>
|
||||
|
||||
@@ -6,7 +6,26 @@
|
||||
<script>
|
||||
function B() { window.history.back(); }
|
||||
function U() { document.getElementById("uf").style.display="none";document.getElementById("msg").style.display="block"; }
|
||||
function GetV() {/*injected values here*/}
|
||||
function GetV() {
|
||||
// Fetch device info via JSON API instead of compiling it in
|
||||
fetch('/json/info')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.querySelector('.installed-version').textContent = `${data.brand} ${data.ver} (${data.vid})`;
|
||||
document.querySelector('.release-name').textContent = data.release;
|
||||
// TODO - assemble update URL
|
||||
// TODO - can this be done at build time?
|
||||
if (data.arch == "esp8266") {
|
||||
toggle('rev');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Could not fetch device info:', error);
|
||||
// Fallback to compiled-in value if API call fails
|
||||
document.querySelector('.installed-version').textContent = 'Unknown';
|
||||
document.querySelector('.release-name').textContent = 'Unknown';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
@import url("style.css");
|
||||
@@ -16,11 +35,15 @@
|
||||
<body onload="GetV()">
|
||||
<h2>WLED Software Update</h2>
|
||||
<form method='POST' action='./update' id='uf' enctype='multipart/form-data' onsubmit="U()">
|
||||
Installed version: <span class="sip">##VERSION##</span><br>
|
||||
Installed version: <span class="sip installed-version">Loading...</span><br>
|
||||
Release: <span class="sip release-name">Loading...</span><br>
|
||||
Download the latest binary: <a href="https://github.com/Aircoookie/WLED/releases" target="_blank"
|
||||
style="vertical-align: text-bottom; display: inline-flex;">
|
||||
<img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br>
|
||||
<input type="hidden" name="skipValidation" value="" id="sV">
|
||||
<input type='file' name='update' required><br> <!--should have accept='.bin', but it prevents file upload from android app-->
|
||||
<input type='checkbox' onchange="sV.value=checked?1:''" id="skipValidation">
|
||||
<label for='skipValidation'>Ignore firmware validation</label><br>
|
||||
<button type="submit">Update!</button><br>
|
||||
<button type="button" onclick="B()">Back</button>
|
||||
</form>
|
||||
|
||||
@@ -416,7 +416,7 @@ void prepareArtnetPollReply(ArtPollReply *reply) {
|
||||
|
||||
reply->reply_port = ARTNET_DEFAULT_PORT;
|
||||
|
||||
char * numberEnd = versionString;
|
||||
char * numberEnd = (char*) versionString; // strtol promises not to try to edit this.
|
||||
reply->reply_version_h = (uint8_t)strtol(numberEnd, &numberEnd, 10);
|
||||
numberEnd++;
|
||||
reply->reply_version_l = (uint8_t)strtol(numberEnd, &numberEnd, 10);
|
||||
|
||||
@@ -24,6 +24,10 @@ void handleIO();
|
||||
void IRAM_ATTR touchButtonISR();
|
||||
|
||||
//cfg.cpp
|
||||
bool backupConfig();
|
||||
bool restoreConfig();
|
||||
bool verifyConfig();
|
||||
void resetConfig();
|
||||
bool deserializeConfig(JsonObject doc, bool fromFS = false);
|
||||
bool deserializeConfigFromFS();
|
||||
bool deserializeConfigSec();
|
||||
@@ -114,10 +118,15 @@ bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest
|
||||
bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest);
|
||||
void updateFSInfo();
|
||||
void closeFile();
|
||||
inline bool writeObjectToFileUsingId(const String &file, uint16_t id, JsonDocument* content) { return writeObjectToFileUsingId(file.c_str(), id, content); };
|
||||
inline bool writeObjectToFile(const String &file, const char* key, JsonDocument* content) { return writeObjectToFile(file.c_str(), key, content); };
|
||||
inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest) { return readObjectFromFileUsingId(file.c_str(), id, dest); };
|
||||
inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest) { return readObjectFromFile(file.c_str(), key, dest); };
|
||||
inline bool writeObjectToFileUsingId(const String &file, uint16_t id, const JsonDocument* content) { return writeObjectToFileUsingId(file.c_str(), id, content); };
|
||||
inline bool writeObjectToFile(const String &file, const char* key, const JsonDocument* content) { return writeObjectToFile(file.c_str(), key, content); };
|
||||
inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFileUsingId(file.c_str(), id, dest); };
|
||||
inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFile(file.c_str(), key, dest); };
|
||||
bool copyFile(const char* src_path, const char* dst_path);
|
||||
bool backupFile(const char* filename);
|
||||
bool restoreFile(const char* filename);
|
||||
bool validateJsonFile(const char* filename);
|
||||
void dumpFilesToSerial();
|
||||
|
||||
//hue.cpp
|
||||
void handleHue();
|
||||
@@ -391,6 +400,8 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
|
||||
int16_t extractModeDefaults(uint8_t mode, const char *segVar);
|
||||
void checkSettingsPIN(const char *pin);
|
||||
uint16_t crc16(const unsigned char* data_p, size_t length);
|
||||
String computeSHA1(const String& input);
|
||||
String getDeviceId();
|
||||
uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
|
||||
uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t highest = 65535, uint32_t timebase = 0, uint16_t phase_offset = 0);
|
||||
uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0);
|
||||
@@ -399,6 +410,15 @@ void enumerateLedmaps();
|
||||
uint8_t get_random_wheel_index(uint8_t pos);
|
||||
float mapf(float x, float in_min, float in_max, float out_min, float out_max);
|
||||
|
||||
void handleBootLoop(); // detect and handle bootloops
|
||||
#ifndef ESP8266
|
||||
void bootloopCheckOTA(); // swap boot image if bootloop is detected instead of restoring config
|
||||
#endif
|
||||
|
||||
void handleBootLoop(); // detect and handle bootloops
|
||||
#ifndef ESP8266
|
||||
void bootloopCheckOTA(); // swap boot image if bootloop is detected instead of restoring config
|
||||
#endif
|
||||
// RAII guard class for the JSON Buffer lock
|
||||
// Modeled after std::lock_guard
|
||||
class JSONBufferGuard {
|
||||
|
||||
153
wled00/file.cpp
153
wled00/file.cpp
@@ -438,3 +438,156 @@ bool handleFileRead(AsyncWebServerRequest* request, String path){
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// copy a file, delete destination file if incomplete to prevent corrupted files
|
||||
bool copyFile(const char* src_path, const char* dst_path) {
|
||||
DEBUG_PRINTF("copyFile from %s to %s\n", src_path, dst_path);
|
||||
if(!WLED_FS.exists(src_path)) {
|
||||
DEBUG_PRINTLN(F("file not found"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = true; // is set to false on error
|
||||
File src = WLED_FS.open(src_path, "r");
|
||||
File dst = WLED_FS.open(dst_path, "w");
|
||||
|
||||
if (src && dst) {
|
||||
uint8_t buf[128]; // copy file in 128-byte blocks
|
||||
while (src.available() > 0) {
|
||||
size_t bytesRead = src.read(buf, sizeof(buf));
|
||||
if (bytesRead == 0) {
|
||||
success = false;
|
||||
break; // error, no data read
|
||||
}
|
||||
size_t bytesWritten = dst.write(buf, bytesRead);
|
||||
if (bytesWritten != bytesRead) {
|
||||
success = false;
|
||||
break; // error, not all data written
|
||||
}
|
||||
}
|
||||
} else {
|
||||
success = false; // error, could not open files
|
||||
}
|
||||
if(src) src.close();
|
||||
if(dst) dst.close();
|
||||
if (!success) {
|
||||
DEBUG_PRINTLN(F("copy failed"));
|
||||
WLED_FS.remove(dst_path); // delete incomplete file
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// compare two files, return true if identical
|
||||
bool compareFiles(const char* path1, const char* path2) {
|
||||
DEBUG_PRINTF("compareFile %s and %s\n", path1, path2);
|
||||
if (!WLED_FS.exists(path1) || !WLED_FS.exists(path2)) {
|
||||
DEBUG_PRINTLN(F("file not found"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool identical = true; // set to false on mismatch
|
||||
File f1 = WLED_FS.open(path1, "r");
|
||||
File f2 = WLED_FS.open(path2, "r");
|
||||
|
||||
if (f1 && f2) {
|
||||
uint8_t buf1[128], buf2[128];
|
||||
while (f1.available() > 0 || f2.available() > 0) {
|
||||
size_t len1 = f1.read(buf1, sizeof(buf1));
|
||||
size_t len2 = f2.read(buf2, sizeof(buf2));
|
||||
|
||||
if (len1 != len2) {
|
||||
identical = false;
|
||||
break; // files differ in size or read failed
|
||||
}
|
||||
|
||||
if (memcmp(buf1, buf2, len1) != 0) {
|
||||
identical = false;
|
||||
break; // files differ in content
|
||||
}
|
||||
}
|
||||
} else {
|
||||
identical = false; // error opening files
|
||||
}
|
||||
|
||||
if (f1) f1.close();
|
||||
if (f2) f2.close();
|
||||
return identical;
|
||||
}
|
||||
|
||||
static const char s_backup_fmt[] PROGMEM = "/bkp.%s";
|
||||
|
||||
bool backupFile(const char* filename) {
|
||||
DEBUG_PRINTF("backup %s \n", filename);
|
||||
if (!validateJsonFile(filename)) {
|
||||
DEBUG_PRINTLN(F("broken file"));
|
||||
return false;
|
||||
}
|
||||
char backupname[32];
|
||||
snprintf_P(backupname, sizeof(backupname), s_backup_fmt, filename + 1); // skip leading '/' in filename
|
||||
|
||||
if (copyFile(filename, backupname)) {
|
||||
DEBUG_PRINTLN(F("backup ok"));
|
||||
return true;
|
||||
}
|
||||
DEBUG_PRINTLN(F("backup failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool restoreFile(const char* filename) {
|
||||
DEBUG_PRINTF("restore %s \n", filename);
|
||||
char backupname[32];
|
||||
snprintf_P(backupname, sizeof(backupname), s_backup_fmt, filename + 1); // skip leading '/' in filename
|
||||
|
||||
if (!WLED_FS.exists(backupname)) {
|
||||
DEBUG_PRINTLN(F("no backup found"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validateJsonFile(backupname)) {
|
||||
DEBUG_PRINTLN(F("broken backup"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (copyFile(backupname, filename)) {
|
||||
DEBUG_PRINTLN(F("restore ok"));
|
||||
return true;
|
||||
}
|
||||
DEBUG_PRINTLN(F("restore failed"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool validateJsonFile(const char* filename) {
|
||||
if (!WLED_FS.exists(filename)) return false;
|
||||
File file = WLED_FS.open(filename, "r");
|
||||
if (!file) return false;
|
||||
StaticJsonDocument<0> doc, filter; // https://arduinojson.org/v6/how-to/validate-json/
|
||||
bool result = deserializeJson(doc, file, DeserializationOption::Filter(filter)) == DeserializationError::Ok;
|
||||
file.close();
|
||||
if (!result) {
|
||||
DEBUG_PRINTF_P(PSTR("Invalid JSON file %s\n"), filename);
|
||||
} else {
|
||||
DEBUG_PRINTF_P(PSTR("Valid JSON file %s\n"), filename);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// print contents of all files in root dir to Serial except wsec files
|
||||
void dumpFilesToSerial() {
|
||||
File rootdir = WLED_FS.open("/", "r");
|
||||
File rootfile = rootdir.openNextFile();
|
||||
while (rootfile) {
|
||||
size_t len = strlen(rootfile.name());
|
||||
// skip files starting with "wsec" and dont end in .json
|
||||
if (strncmp(rootfile.name(), "wsec", 4) != 0 && len >= 6 && strcmp(rootfile.name() + len - 5, ".json") == 0) {
|
||||
Serial.println(rootfile.name());
|
||||
while (rootfile.available()) {
|
||||
Serial.write(rootfile.read());
|
||||
}
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
}
|
||||
rootfile.close();
|
||||
rootfile = rootdir.openNextFile();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "wled.h"
|
||||
#include "ota_update.h"
|
||||
|
||||
#include "palettes.h"
|
||||
|
||||
@@ -631,6 +632,8 @@ void serializeInfo(JsonObject root)
|
||||
root[F("vid")] = VERSION;
|
||||
root[F("cn")] = F(WLED_CODENAME);
|
||||
root[F("release")] = releaseString;
|
||||
root[F("repo")] = repoString;
|
||||
root[F("deviceId")] = getDeviceId();
|
||||
|
||||
JsonObject leds = root.createNestedObject(F("leds"));
|
||||
leds[F("count")] = strip.getLengthTotal();
|
||||
@@ -753,6 +756,9 @@ void serializeInfo(JsonObject root)
|
||||
root[F("resetReason1")] = (int)rtc_get_reset_reason(1);
|
||||
#endif
|
||||
root[F("lwip")] = 0; //deprecated
|
||||
#ifndef WLED_DISABLE_OTA
|
||||
root[F("bootloaderSHA256")] = getBootloaderSHA256Hex();
|
||||
#endif
|
||||
#else
|
||||
root[F("arch")] = "esp8266";
|
||||
root[F("core")] = ESP.getCoreVersion();
|
||||
|
||||
323
wled00/ota_update.cpp
Normal file
323
wled00/ota_update.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
#include "ota_update.h"
|
||||
#include "wled.h"
|
||||
|
||||
#ifdef ESP32
|
||||
#include <esp_ota_ops.h>
|
||||
#include <esp_spi_flash.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#endif
|
||||
|
||||
// Platform-specific metadata locations
|
||||
#ifdef ESP32
|
||||
constexpr size_t METADATA_OFFSET = 256; // ESP32: metadata appears after Espressif metadata
|
||||
#define UPDATE_ERROR errorString
|
||||
|
||||
// Bootloader is at fixed offset 0x1000 (4KB), 0x0000 (0KB), or 0x2000 (8KB), and is typically 32KB
|
||||
// Bootloader offsets for different MCUs => see https://github.com/wled/WLED/issues/5064
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||
constexpr size_t BOOTLOADER_OFFSET = 0x0000; // esp32-S3, esp32-C3 and (future support) esp32-c6
|
||||
constexpr size_t BOOTLOADER_SIZE = 0x8000; // 32KB, typical bootloader size
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32P4) || defined(CONFIG_IDF_TARGET_ESP32C5)
|
||||
constexpr size_t BOOTLOADER_OFFSET = 0x2000; // (future support) esp32-P4 and esp32-C5
|
||||
constexpr size_t BOOTLOADER_SIZE = 0x8000; // 32KB, typical bootloader size
|
||||
#else
|
||||
constexpr size_t BOOTLOADER_OFFSET = 0x1000; // esp32 and esp32-s2
|
||||
constexpr size_t BOOTLOADER_SIZE = 0x8000; // 32KB, typical bootloader size
|
||||
#endif
|
||||
|
||||
#elif defined(ESP8266)
|
||||
constexpr size_t METADATA_OFFSET = 0x1000; // ESP8266: metadata appears at 4KB offset
|
||||
#define UPDATE_ERROR getErrorString
|
||||
#endif
|
||||
constexpr size_t METADATA_SEARCH_RANGE = 512; // bytes
|
||||
|
||||
|
||||
/**
|
||||
* Check if OTA should be allowed based on release compatibility using custom description
|
||||
* @param binaryData Pointer to binary file data (not modified)
|
||||
* @param dataSize Size of binary data in bytes
|
||||
* @param errorMessage Buffer to store error message if validation fails
|
||||
* @param errorMessageLen Maximum length of error message buffer
|
||||
* @return true if OTA should proceed, false if it should be blocked
|
||||
*/
|
||||
|
||||
static bool validateOTA(const uint8_t* binaryData, size_t dataSize, char* errorMessage, size_t errorMessageLen) {
|
||||
// Clear error message
|
||||
if (errorMessage && errorMessageLen > 0) {
|
||||
errorMessage[0] = '\0';
|
||||
}
|
||||
|
||||
// Try to extract WLED structure directly from binary data
|
||||
wled_metadata_t extractedDesc;
|
||||
bool hasDesc = findWledMetadata(binaryData, dataSize, &extractedDesc);
|
||||
|
||||
if (hasDesc) {
|
||||
return shouldAllowOTA(extractedDesc, errorMessage, errorMessageLen);
|
||||
} else {
|
||||
// No custom description - this could be a legacy binary
|
||||
if (errorMessage && errorMessageLen > 0) {
|
||||
strncpy_P(errorMessage, PSTR("This firmware file is missing compatibility metadata."), errorMessageLen - 1);
|
||||
errorMessage[errorMessageLen - 1] = '\0';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct UpdateContext {
|
||||
// State flags
|
||||
// FUTURE: the flags could be replaced by a state machine
|
||||
bool replySent = false;
|
||||
bool needsRestart = false;
|
||||
bool updateStarted = false;
|
||||
bool uploadComplete = false;
|
||||
bool releaseCheckPassed = false;
|
||||
String errorMessage;
|
||||
|
||||
// Buffer to hold block data across posts, if needed
|
||||
std::vector<uint8_t> releaseMetadataBuffer;
|
||||
};
|
||||
|
||||
|
||||
static void endOTA(AsyncWebServerRequest *request) {
|
||||
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
|
||||
request->_tempObject = nullptr;
|
||||
|
||||
DEBUG_PRINTF_P(PSTR("EndOTA %x --> %x (%d)\n"), (uintptr_t)request,(uintptr_t) context, context ? context->uploadComplete : 0);
|
||||
if (context) {
|
||||
if (context->updateStarted) { // We initialized the update
|
||||
// We use Update.end() because not all forms of Update() support an abort.
|
||||
// If the upload is incomplete, Update.end(false) should error out.
|
||||
if (Update.end(context->uploadComplete)) {
|
||||
// Update successful!
|
||||
#ifndef ESP8266
|
||||
bootloopCheckOTA(); // let the bootloop-checker know there was an OTA update
|
||||
#endif
|
||||
doReboot = true;
|
||||
context->needsRestart = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (context->needsRestart) {
|
||||
strip.resume();
|
||||
UsermodManager::onUpdateBegin(false);
|
||||
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||
WLED::instance().enableWatchdog();
|
||||
#endif
|
||||
}
|
||||
delete context;
|
||||
}
|
||||
};
|
||||
|
||||
static bool beginOTA(AsyncWebServerRequest *request, UpdateContext* context)
|
||||
{
|
||||
#ifdef ESP8266
|
||||
Update.runAsync(true);
|
||||
#endif
|
||||
|
||||
if (Update.isRunning()) {
|
||||
request->send(503);
|
||||
setOTAReplied(request);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||
WLED::instance().disableWatchdog();
|
||||
#endif
|
||||
UsermodManager::onUpdateBegin(true); // notify usermods that update is about to begin (some may require task de-init)
|
||||
|
||||
strip.suspend();
|
||||
strip.resetSegments(); // free as much memory as you can
|
||||
context->needsRestart = true;
|
||||
backupConfig(); // backup current config in case the update ends badly
|
||||
|
||||
DEBUG_PRINTF_P(PSTR("OTA Update Start, %x --> %x\n"), (uintptr_t)request,(uintptr_t) context);
|
||||
|
||||
auto skipValidationParam = request->getParam("skipValidation", true);
|
||||
if (skipValidationParam && (skipValidationParam->value() == "1")) {
|
||||
context->releaseCheckPassed = true;
|
||||
DEBUG_PRINTLN(F("OTA validation skipped by user"));
|
||||
}
|
||||
|
||||
// Begin update with the firmware size from content length
|
||||
size_t updateSize = request->contentLength() > 0 ? request->contentLength() : ((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
||||
if (!Update.begin(updateSize)) {
|
||||
context->errorMessage = Update.UPDATE_ERROR();
|
||||
DEBUG_PRINTF_P(PSTR("OTA Failed to begin: %s\n"), context->errorMessage.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
context->updateStarted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create an OTA context object on an AsyncWebServerRequest
|
||||
// Returns true if successful, false on failure.
|
||||
bool initOTA(AsyncWebServerRequest *request) {
|
||||
// Allocate update context
|
||||
UpdateContext* context = new (std::nothrow) UpdateContext {};
|
||||
if (context) {
|
||||
request->_tempObject = context;
|
||||
request->onDisconnect([=]() { endOTA(request); }); // ensures we restart on failure
|
||||
};
|
||||
|
||||
DEBUG_PRINTF_P(PSTR("OTA Update init, %x --> %x\n"), (uintptr_t)request,(uintptr_t) context);
|
||||
return (context != nullptr);
|
||||
}
|
||||
|
||||
void setOTAReplied(AsyncWebServerRequest *request) {
|
||||
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
|
||||
if (!context) return;
|
||||
context->replySent = true;
|
||||
};
|
||||
|
||||
// Returns pointer to error message, or nullptr if OTA was successful.
|
||||
std::pair<bool, String> getOTAResult(AsyncWebServerRequest* request) {
|
||||
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
|
||||
if (!context) return { true, F("OTA context unexpectedly missing") };
|
||||
if (context->replySent) return { false, {} };
|
||||
if (context->errorMessage.length()) return { true, context->errorMessage };
|
||||
|
||||
if (context->updateStarted) {
|
||||
// Release the OTA context now.
|
||||
endOTA(request);
|
||||
if (Update.hasError()) {
|
||||
return { true, Update.UPDATE_ERROR() };
|
||||
} else {
|
||||
return { true, {} };
|
||||
}
|
||||
}
|
||||
|
||||
// Should never happen
|
||||
return { true, F("Internal software failure") };
|
||||
}
|
||||
|
||||
|
||||
|
||||
void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal)
|
||||
{
|
||||
UpdateContext* context = reinterpret_cast<UpdateContext*>(request->_tempObject);
|
||||
if (!context) return;
|
||||
|
||||
//DEBUG_PRINTF_P(PSTR("HandleOTAData: %d %d %d\n"), index, len, isFinal);
|
||||
|
||||
if (context->replySent || (context->errorMessage.length())) return;
|
||||
|
||||
if (index == 0) {
|
||||
if (!beginOTA(request, context)) return;
|
||||
}
|
||||
|
||||
// Perform validation if we haven't done it yet and we have reached the metadata offset
|
||||
if (!context->releaseCheckPassed && (index+len) > METADATA_OFFSET) {
|
||||
// Current chunk contains the metadata offset
|
||||
size_t availableDataAfterOffset = (index + len) - METADATA_OFFSET;
|
||||
|
||||
DEBUG_PRINTF_P(PSTR("OTA metadata check: %d in buffer, %d received, %d available\n"), context->releaseMetadataBuffer.size(), len, availableDataAfterOffset);
|
||||
|
||||
if (availableDataAfterOffset >= METADATA_SEARCH_RANGE) {
|
||||
// We have enough data to validate, one way or another
|
||||
const uint8_t* search_data = data;
|
||||
size_t search_len = len;
|
||||
|
||||
// If we have saved data, use that instead
|
||||
if (context->releaseMetadataBuffer.size()) {
|
||||
// Add this data
|
||||
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
|
||||
search_data = context->releaseMetadataBuffer.data();
|
||||
search_len = context->releaseMetadataBuffer.size();
|
||||
}
|
||||
|
||||
// Do the checking
|
||||
char errorMessage[128];
|
||||
bool OTA_ok = validateOTA(search_data, search_len, errorMessage, sizeof(errorMessage));
|
||||
|
||||
// Release buffer if there was one
|
||||
context->releaseMetadataBuffer = decltype(context->releaseMetadataBuffer){};
|
||||
|
||||
if (!OTA_ok) {
|
||||
DEBUG_PRINTF_P(PSTR("OTA declined: %s\n"), errorMessage);
|
||||
context->errorMessage = errorMessage;
|
||||
context->errorMessage += F(" Enable 'Ignore firmware validation' to proceed anyway.");
|
||||
return;
|
||||
} else {
|
||||
DEBUG_PRINTLN(F("OTA allowed: Release compatibility check passed"));
|
||||
context->releaseCheckPassed = true;
|
||||
}
|
||||
} else {
|
||||
// Store the data we just got for next pass
|
||||
context->releaseMetadataBuffer.insert(context->releaseMetadataBuffer.end(), data, data+len);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if validation was still pending (shouldn't happen normally)
|
||||
// This is done before writing the last chunk, so endOTA can abort
|
||||
if (isFinal && !context->releaseCheckPassed) {
|
||||
DEBUG_PRINTLN(F("OTA failed: Validation never completed"));
|
||||
// Don't write the last chunk to the updater: this will trip an error later
|
||||
context->errorMessage = F("Release check data never arrived?");
|
||||
return;
|
||||
}
|
||||
|
||||
// Write chunk data to OTA update (only if release check passed or still pending)
|
||||
if (!Update.hasError()) {
|
||||
if (Update.write(data, len) != len) {
|
||||
DEBUG_PRINTF_P(PSTR("OTA write failed on chunk %zu: %s\n"), index, Update.UPDATE_ERROR());
|
||||
}
|
||||
}
|
||||
|
||||
if(isFinal) {
|
||||
DEBUG_PRINTLN(F("OTA Update End"));
|
||||
// Upload complete
|
||||
context->uploadComplete = true;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
|
||||
static String bootloaderSHA256HexCache = "";
|
||||
|
||||
// Calculate and cache the bootloader SHA256 digest as hex string
|
||||
void calculateBootloaderSHA256() {
|
||||
if (!bootloaderSHA256HexCache.isEmpty()) return;
|
||||
|
||||
// Calculate SHA256
|
||||
uint8_t sha256[32];
|
||||
mbedtls_sha256_context ctx;
|
||||
mbedtls_sha256_init(&ctx);
|
||||
mbedtls_sha256_starts(&ctx, 0); // 0 = SHA256 (not SHA224)
|
||||
|
||||
const size_t chunkSize = 256;
|
||||
uint8_t buffer[chunkSize];
|
||||
|
||||
for (uint32_t offset = 0; offset < BOOTLOADER_SIZE; offset += chunkSize) {
|
||||
size_t readSize = min((size_t)(BOOTLOADER_SIZE - offset), chunkSize);
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||
if (esp_flash_read(NULL, buffer, BOOTLOADER_OFFSET + offset, readSize) == ESP_OK) { // use esp_flash_read for V4 framework (-S2, -S3, -C3)
|
||||
#else
|
||||
if (spi_flash_read(BOOTLOADER_OFFSET + offset, buffer, readSize) == ESP_OK) { // use spi_flash_read for old V3 framework (legacy esp32)
|
||||
#endif
|
||||
mbedtls_sha256_update(&ctx, buffer, readSize);
|
||||
}
|
||||
}
|
||||
|
||||
mbedtls_sha256_finish(&ctx, sha256);
|
||||
mbedtls_sha256_free(&ctx);
|
||||
|
||||
// Convert to hex string and cache it
|
||||
char hex[65];
|
||||
for (int i = 0; i < 32; i++) {
|
||||
sprintf(hex + (i * 2), "%02x", sha256[i]);
|
||||
}
|
||||
hex[64] = '\0';
|
||||
bootloaderSHA256HexCache = hex;
|
||||
}
|
||||
|
||||
// Get bootloader SHA256 as hex string
|
||||
String getBootloaderSHA256Hex() {
|
||||
calculateBootloaderSHA256();
|
||||
return bootloaderSHA256HexCache;
|
||||
}
|
||||
|
||||
// Invalidate cached bootloader SHA256 (call after bootloader update)
|
||||
void invalidateBootloaderSHA256Cache() {
|
||||
bootloaderSHA256HexCache = "";
|
||||
}
|
||||
#endif
|
||||
72
wled00/ota_update.h
Normal file
72
wled00/ota_update.h
Normal file
@@ -0,0 +1,72 @@
|
||||
// WLED OTA update interface
|
||||
|
||||
#include <Arduino.h>
|
||||
#ifdef ESP8266
|
||||
#include <Updater.h>
|
||||
#else
|
||||
#include <Update.h>
|
||||
#endif
|
||||
|
||||
#pragma once
|
||||
|
||||
// Platform-specific metadata locations
|
||||
#ifdef ESP32
|
||||
#define BUILD_METADATA_SECTION ".rodata_custom_desc"
|
||||
#elif defined(ESP8266)
|
||||
#define BUILD_METADATA_SECTION ".ver_number"
|
||||
#endif
|
||||
|
||||
|
||||
class AsyncWebServerRequest;
|
||||
|
||||
/**
|
||||
* Create an OTA context object on an AsyncWebServerRequest
|
||||
* @param request Pointer to web request object
|
||||
* @return true if allocation was successful, false if not
|
||||
*/
|
||||
bool initOTA(AsyncWebServerRequest *request);
|
||||
|
||||
/**
|
||||
* Indicate to the OTA subsystem that a reply has already been generated
|
||||
* @param request Pointer to web request object
|
||||
*/
|
||||
void setOTAReplied(AsyncWebServerRequest *request);
|
||||
|
||||
/**
|
||||
* Retrieve the OTA result.
|
||||
* @param request Pointer to web request object
|
||||
* @return bool indicating if a reply is necessary; string with error message if the update failed.
|
||||
*/
|
||||
std::pair<bool, String> getOTAResult(AsyncWebServerRequest *request);
|
||||
|
||||
/**
|
||||
* Process a block of OTA data. This is a passthrough of an ArUploadHandlerFunction.
|
||||
* Requires that initOTA be called on the handler object before any work will be done.
|
||||
* @param request Pointer to web request object
|
||||
* @param index Offset in to uploaded file
|
||||
* @param data New data bytes
|
||||
* @param len Length of new data bytes
|
||||
* @param isFinal Indicates that this is the last block
|
||||
* @return bool indicating if a reply is necessary; string with error message if the update failed.
|
||||
*/
|
||||
void handleOTAData(AsyncWebServerRequest *request, size_t index, uint8_t *data, size_t len, bool isFinal);
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32) && !defined(WLED_DISABLE_OTA)
|
||||
/**
|
||||
* Calculate and cache the bootloader SHA256 digest
|
||||
* Reads the bootloader from flash at offset 0x1000 and computes SHA256 hash
|
||||
*/
|
||||
void calculateBootloaderSHA256();
|
||||
|
||||
/**
|
||||
* Get bootloader SHA256 as hex string
|
||||
* @return String containing 64-character hex representation of SHA256 hash
|
||||
*/
|
||||
String getBootloaderSHA256Hex();
|
||||
|
||||
/**
|
||||
* Invalidate cached bootloader SHA256 (call after bootloader update)
|
||||
* Forces recalculation on next call to calculateBootloaderSHA256 or getBootloaderSHA256Hex
|
||||
*/
|
||||
void invalidateBootloaderSHA256Cache();
|
||||
#endif
|
||||
250
wled00/util.cpp
250
wled00/util.cpp
@@ -1,6 +1,19 @@
|
||||
#include "wled.h"
|
||||
#include "fcn_declare.h"
|
||||
#include "const.h"
|
||||
#ifdef ESP8266
|
||||
#include "user_interface.h" // for bootloop detection
|
||||
#include <Hash.h> // for SHA1 on ESP8266
|
||||
#else
|
||||
#include <Update.h>
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||
#include "esp32/rtc.h" // for bootloop detection
|
||||
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
|
||||
#include "soc/rtc.h"
|
||||
#endif
|
||||
#include "mbedtls/sha1.h" // for SHA1 on ESP32
|
||||
#include "esp_adc_cal.h"
|
||||
#endif
|
||||
|
||||
|
||||
//helper to get int value at a position in string
|
||||
@@ -594,3 +607,240 @@ uint8_t get_random_wheel_index(uint8_t pos) {
|
||||
float mapf(float x, float in_min, float in_max, float out_min, float out_max) {
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||||
}
|
||||
|
||||
// bootloop detection and handling
|
||||
// checks if the ESP reboots multiple times due to a crash or watchdog timeout
|
||||
// if a bootloop is detected: restore settings from backup, then reset settings, then switch boot image (and repeat)
|
||||
|
||||
#define BOOTLOOP_INTERVAL_MILLIS 120000 // time limit between crashes: 120 seconds (2 minutes)
|
||||
#define BOOTLOOP_THRESHOLD 5 // number of consecutive crashes to trigger bootloop detection
|
||||
#define BOOTLOOP_ACTION_RESTORE 0 // default action: restore config from /bkp.cfg.json
|
||||
#define BOOTLOOP_ACTION_RESET 1 // if restore does not work, reset config (rename /cfg.json to /rst.cfg.json)
|
||||
#define BOOTLOOP_ACTION_OTA 2 // swap the boot partition
|
||||
#define BOOTLOOP_ACTION_DUMP 3 // nothing seems to help, dump files to serial and reboot (until hardware reset)
|
||||
|
||||
// Platform-agnostic abstraction
|
||||
enum class ResetReason {
|
||||
Power,
|
||||
Software,
|
||||
Crash,
|
||||
Brownout
|
||||
};
|
||||
|
||||
#ifdef ESP8266
|
||||
// Place variables in RTC memory via references, since RTC memory is not exposed via the linker in the Non-OS SDK
|
||||
// Use an offset of 32 as there's some hints that the first 128 bytes of "user" memory are used by the OTA system
|
||||
// Ref: https://github.com/esp8266/Arduino/blob/78d0d0aceacc1553f45ad8154592b0af22d1eede/cores/esp8266/Esp.cpp#L168
|
||||
static volatile uint32_t& bl_last_boottime = *(RTC_USER_MEM + 32);
|
||||
static volatile uint32_t& bl_crashcounter = *(RTC_USER_MEM + 33);
|
||||
static volatile uint32_t& bl_actiontracker = *(RTC_USER_MEM + 34);
|
||||
|
||||
static inline ResetReason rebootReason() {
|
||||
uint32_t resetReason = system_get_rst_info()->reason;
|
||||
if (resetReason == REASON_EXCEPTION_RST
|
||||
|| resetReason == REASON_WDT_RST
|
||||
|| resetReason == REASON_SOFT_WDT_RST)
|
||||
return ResetReason::Crash;
|
||||
if (resetReason == REASON_SOFT_RESTART)
|
||||
return ResetReason::Software;
|
||||
return ResetReason::Power;
|
||||
}
|
||||
|
||||
static inline uint32_t getRtcMillis() { return system_get_rtc_time() / 160; }; // rtc ticks ~160000Hz
|
||||
|
||||
#else
|
||||
// variables in RTC_NOINIT memory persist between reboots (but not on hardware reset)
|
||||
RTC_NOINIT_ATTR static uint32_t bl_last_boottime;
|
||||
RTC_NOINIT_ATTR static uint32_t bl_crashcounter;
|
||||
RTC_NOINIT_ATTR static uint32_t bl_actiontracker;
|
||||
|
||||
static inline ResetReason rebootReason() {
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
if (reason == ESP_RST_BROWNOUT) return ResetReason::Brownout;
|
||||
if (reason == ESP_RST_SW) return ResetReason::Software;
|
||||
if (reason == ESP_RST_PANIC || reason == ESP_RST_WDT || reason == ESP_RST_INT_WDT || reason == ESP_RST_TASK_WDT) return ResetReason::Crash;
|
||||
return ResetReason::Power;
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
|
||||
static inline uint32_t getRtcMillis() { return esp_rtc_get_time_us() / 1000; }
|
||||
#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
|
||||
static inline uint32_t getRtcMillis() { return rtc_time_slowclk_to_us(rtc_time_get(), rtc_clk_slow_freq_get_hz()) / 1000; }
|
||||
#endif
|
||||
|
||||
void bootloopCheckOTA() { bl_actiontracker = BOOTLOOP_ACTION_OTA; } // swap boot image if bootloop is detected instead of restoring config
|
||||
|
||||
#endif
|
||||
|
||||
// detect bootloop by checking the reset reason and the time since last boot
|
||||
static bool detectBootLoop() {
|
||||
uint32_t rtctime = getRtcMillis();
|
||||
bool result = false;
|
||||
|
||||
switch(rebootReason()) {
|
||||
case ResetReason::Power:
|
||||
bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // init action tracker if not an intentional reboot (e.g. from OTA or bootloop handler)
|
||||
// fall through
|
||||
case ResetReason::Software:
|
||||
// no crash detected, reset counter
|
||||
bl_crashcounter = 0;
|
||||
break;
|
||||
|
||||
case ResetReason::Crash:
|
||||
{
|
||||
uint32_t rebootinterval = rtctime - bl_last_boottime;
|
||||
if (rebootinterval < BOOTLOOP_INTERVAL_MILLIS) {
|
||||
bl_crashcounter++;
|
||||
if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
|
||||
DEBUG_PRINTLN(F("!BOOTLOOP DETECTED!"));
|
||||
bl_crashcounter = 0;
|
||||
if(bl_actiontracker > BOOTLOOP_ACTION_DUMP) bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // reset action tracker if out of bounds
|
||||
result = true;
|
||||
}
|
||||
} else {
|
||||
// Reset counter on long intervals to track only consecutive short-interval crashes
|
||||
bl_crashcounter = 0;
|
||||
// TODO: crash reporting goes here
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ResetReason::Brownout:
|
||||
// crash due to brownout can't be detected unless using flash memory to store bootloop variables
|
||||
DEBUG_PRINTLN(F("brownout detected"));
|
||||
//restoreConfig(); // TODO: blindly restoring config if brownout detected is a bad idea, need a better way (if at all)
|
||||
break;
|
||||
}
|
||||
|
||||
bl_last_boottime = rtctime; // store current runtime for next reboot
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void handleBootLoop() {
|
||||
DEBUG_PRINTF_P(PSTR("checking for bootloop: time %d, counter %d, action %d\n"), bl_last_boottime, bl_crashcounter, bl_actiontracker);
|
||||
if (!detectBootLoop()) return; // no bootloop detected
|
||||
|
||||
switch(bl_actiontracker) {
|
||||
case BOOTLOOP_ACTION_RESTORE:
|
||||
restoreConfig();
|
||||
++bl_actiontracker;
|
||||
break;
|
||||
case BOOTLOOP_ACTION_RESET:
|
||||
resetConfig();
|
||||
++bl_actiontracker;
|
||||
break;
|
||||
case BOOTLOOP_ACTION_OTA:
|
||||
#ifndef ESP8266
|
||||
if(Update.canRollBack()) {
|
||||
DEBUG_PRINTLN(F("Swapping boot partition..."));
|
||||
Update.rollBack(); // swap boot partition
|
||||
}
|
||||
++bl_actiontracker;
|
||||
break;
|
||||
#else
|
||||
// fall through
|
||||
#endif
|
||||
case BOOTLOOP_ACTION_DUMP:
|
||||
dumpFilesToSerial();
|
||||
break;
|
||||
}
|
||||
|
||||
ESP.restart(); // restart cleanly and don't wait for another crash
|
||||
}
|
||||
|
||||
// Platform-agnostic SHA1 computation from String input
|
||||
String computeSHA1(const String& input) {
|
||||
#ifdef ESP8266
|
||||
return sha1(input); // ESP8266 has built-in sha1() function
|
||||
#else
|
||||
// ESP32: Compute SHA1 hash using mbedtls
|
||||
unsigned char shaResult[20]; // SHA1 produces 20 bytes
|
||||
mbedtls_sha1_context ctx;
|
||||
|
||||
mbedtls_sha1_init(&ctx);
|
||||
mbedtls_sha1_starts_ret(&ctx);
|
||||
mbedtls_sha1_update_ret(&ctx, (const unsigned char*)input.c_str(), input.length());
|
||||
mbedtls_sha1_finish_ret(&ctx, shaResult);
|
||||
mbedtls_sha1_free(&ctx);
|
||||
|
||||
// Convert to hexadecimal string
|
||||
char hexString[41];
|
||||
for (int i = 0; i < 20; i++) {
|
||||
sprintf(&hexString[i*2], "%02x", shaResult[i]);
|
||||
}
|
||||
hexString[40] = '\0';
|
||||
|
||||
return String(hexString);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
String generateDeviceFingerprint() {
|
||||
uint32_t fp[2] = {0, 0}; // create 64 bit fingerprint
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
esp_efuse_mac_get_default((uint8_t*)fp);
|
||||
fp[1] ^= ESP.getFlashChipSize();
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 4)
|
||||
fp[0] ^= chip_info.full_revision | (chip_info.model << 16);
|
||||
#else
|
||||
fp[0] ^= chip_info.revision | (chip_info.model << 16);
|
||||
#endif
|
||||
// mix in ADC calibration data:
|
||||
esp_adc_cal_characteristics_t ch;
|
||||
#if SOC_ADC_MAX_BITWIDTH == 13 // S2 has 13 bit ADC
|
||||
constexpr auto myBIT_WIDTH = ADC_WIDTH_BIT_13;
|
||||
#else
|
||||
constexpr auto myBIT_WIDTH = ADC_WIDTH_BIT_12;
|
||||
#endif
|
||||
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, myBIT_WIDTH, 1100, &ch);
|
||||
fp[0] ^= ch.coeff_a;
|
||||
fp[1] ^= ch.coeff_b;
|
||||
if (ch.low_curve) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
fp[0] ^= ch.low_curve[i];
|
||||
}
|
||||
}
|
||||
if (ch.high_curve) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
fp[1] ^= ch.high_curve[i];
|
||||
}
|
||||
}
|
||||
char fp_string[17]; // 16 hex chars + null terminator
|
||||
sprintf(fp_string, "%08X%08X", fp[1], fp[0]);
|
||||
return String(fp_string);
|
||||
}
|
||||
#else // ESP8266
|
||||
String generateDeviceFingerprint() {
|
||||
uint32_t fp[2] = {0, 0}; // create 64 bit fingerprint
|
||||
WiFi.macAddress((uint8_t*)&fp); // use MAC address as fingerprint base
|
||||
fp[0] ^= ESP.getFlashChipId();
|
||||
fp[1] ^= ESP.getFlashChipSize() | ESP.getFlashChipVendorId() << 16;
|
||||
char fp_string[17]; // 16 hex chars + null terminator
|
||||
sprintf(fp_string, "%08X%08X", fp[1], fp[0]);
|
||||
return String(fp_string);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Generate a device ID based on SHA1 hash of MAC address salted with other unique device info
|
||||
// Returns: original SHA1 + last 2 chars of double-hashed SHA1 (42 chars total)
|
||||
String getDeviceId() {
|
||||
static String cachedDeviceId = "";
|
||||
if (cachedDeviceId.length() > 0) return cachedDeviceId;
|
||||
// The device string is deterministic as it needs to be consistent for the same device, even after a full flash erase
|
||||
// MAC is salted with other consistent device info to avoid rainbow table attacks.
|
||||
// If the MAC address is known by malicious actors, they could precompute SHA1 hashes to impersonate devices,
|
||||
// but as WLED developers are just looking at statistics and not authenticating devices, this is acceptable.
|
||||
// If the usage data was exfiltrated, you could not easily determine the MAC from the device ID without brute forcing SHA1
|
||||
|
||||
String firstHash = computeSHA1(generateDeviceFingerprint());
|
||||
|
||||
// Second hash: SHA1 of the first hash
|
||||
String secondHash = computeSHA1(firstHash);
|
||||
|
||||
// Concatenate first hash + last 2 chars of second hash
|
||||
cachedDeviceId = firstHash + secondHash.substring(38);
|
||||
|
||||
return cachedDeviceId;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#define WLED_DEFINE_GLOBAL_VARS //only in one source file, wled.cpp!
|
||||
#include "wled.h"
|
||||
#include "wled_ethernet.h"
|
||||
#include "ota_update.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET)
|
||||
@@ -164,9 +165,9 @@ void WLED::loop()
|
||||
if (millis() - heapTime > 15000) {
|
||||
uint32_t heap = ESP.getFreeHeap();
|
||||
if (heap < MIN_HEAP_SIZE && lastHeap < MIN_HEAP_SIZE) {
|
||||
DEBUG_PRINTF_P(PSTR("Heap too low! %u\n"), heap);
|
||||
forceReconnect = true;
|
||||
DEBUG_PRINTF_P(PSTR("Heap too low! %u\n"), heap);
|
||||
strip.resetSegments(); // remove all but one segments from memory
|
||||
if (!Update.isRunning()) forceReconnect = true;
|
||||
} else if (heap < MIN_HEAP_SIZE) {
|
||||
DEBUG_PRINTLN(F("Heap low, purging segments."));
|
||||
strip.purgeSegments();
|
||||
@@ -183,9 +184,9 @@ void WLED::loop()
|
||||
bool aligned = strip.checkSegmentAlignment(); //see if old segments match old bus(ses)
|
||||
BusManager::removeAll();
|
||||
strip.finalizeInit(); // will create buses and also load default ledmap if present
|
||||
BusManager::setBrightness(bri); // fix re-initialised bus' brightness #4005
|
||||
if (aligned) strip.makeAutoSegments();
|
||||
else strip.fixInvalidSegments();
|
||||
BusManager::setBrightness(scaledBri(bri)); // fix re-initialised bus' brightness #4005 and #4824
|
||||
doSerializeConfig = true;
|
||||
}
|
||||
if (loadLedmap >= 0) {
|
||||
@@ -403,6 +404,9 @@ void WLED::setup()
|
||||
DEBUGFS_PRINTLN(F("FS failed!"));
|
||||
errorFlag = ERR_FS_BEGIN;
|
||||
}
|
||||
|
||||
handleBootLoop(); // check for bootloop and take action (requires WLED_FS)
|
||||
|
||||
#ifdef WLED_ADD_EEPROM_SUPPORT
|
||||
else deEEP();
|
||||
#else
|
||||
@@ -418,6 +422,11 @@ void WLED::setup()
|
||||
WLED_SET_AP_SSID(); // otherwise it is empty on first boot until config is saved
|
||||
multiWiFi.push_back(WiFiConfig(CLIENT_SSID,CLIENT_PASS)); // initialise vector with default WiFi
|
||||
|
||||
if(!verifyConfig()) {
|
||||
if(!restoreConfig()) {
|
||||
resetConfig();
|
||||
}
|
||||
}
|
||||
DEBUG_PRINTLN(F("Reading config"));
|
||||
bool needsCfgSave = deserializeConfigFromFS();
|
||||
DEBUG_PRINTF_P(PSTR("heap %u\n"), ESP.getFreeHeap());
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
// version code in format yymmddb (b = daily build)
|
||||
#define VERSION 2507300
|
||||
#define VERSION 2508020
|
||||
|
||||
//uncomment this if you have a "my_config.h" file you'd like to use
|
||||
//#define WLED_USE_MY_CONFIG
|
||||
@@ -187,6 +187,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
|
||||
#include "pin_manager.h"
|
||||
#include "bus_manager.h"
|
||||
#include "FX.h"
|
||||
#include "wled_metadata.h"
|
||||
|
||||
#ifndef CLIENT_SSID
|
||||
#define CLIENT_SSID DEFAULT_CLIENT_SSID
|
||||
@@ -259,16 +260,6 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
|
||||
#define STRINGIFY(X) #X
|
||||
#define TOSTRING(X) STRINGIFY(X)
|
||||
|
||||
#ifndef WLED_VERSION
|
||||
#define WLED_VERSION dev
|
||||
#endif
|
||||
#ifndef WLED_RELEASE_NAME
|
||||
#define WLED_RELEASE_NAME "Custom"
|
||||
#endif
|
||||
|
||||
// Global Variable definitions
|
||||
WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION));
|
||||
WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // must include the quotes when defining, e.g -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\"
|
||||
#define WLED_CODENAME "Kōsen"
|
||||
|
||||
// AP and OTA default passwords (for maximum security change them!)
|
||||
@@ -697,10 +688,10 @@ WLED_GLOBAL bool receiveNotificationPalette _INIT(true); // apply palet
|
||||
WLED_GLOBAL bool receiveSegmentOptions _INIT(false); // apply segment options
|
||||
WLED_GLOBAL bool receiveSegmentBounds _INIT(false); // apply segment bounds (start, stop, offset)
|
||||
WLED_GLOBAL bool receiveDirect _INIT(true); // receive UDP/Hyperion realtime
|
||||
WLED_GLOBAL bool notifyDirect _INIT(false); // send notification if change via UI or HTTP API
|
||||
WLED_GLOBAL bool notifyButton _INIT(false); // send if updated by button or infrared remote
|
||||
WLED_GLOBAL bool notifyDirect _INIT(true); // send notification if change via UI or HTTP API
|
||||
WLED_GLOBAL bool notifyButton _INIT(true); // send if updated by button or infrared remote
|
||||
WLED_GLOBAL bool notifyAlexa _INIT(false); // send notification if updated via Alexa
|
||||
WLED_GLOBAL bool notifyHue _INIT(true); // send notification if Hue light changes
|
||||
WLED_GLOBAL bool notifyHue _INIT(false); // send notification if Hue light changes
|
||||
#endif
|
||||
|
||||
// effects
|
||||
|
||||
195
wled00/wled_metadata.cpp
Normal file
195
wled00/wled_metadata.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
#include "ota_update.h"
|
||||
#include "wled.h"
|
||||
#include "wled_metadata.h"
|
||||
|
||||
#ifndef WLED_VERSION
|
||||
#warning WLED_VERSION was not set - using default value of 'dev'
|
||||
#define WLED_VERSION dev
|
||||
#endif
|
||||
#ifndef WLED_RELEASE_NAME
|
||||
#warning WLED_RELEASE_NAME was not set - using default value of 'Custom'
|
||||
#define WLED_RELEASE_NAME "Custom"
|
||||
#endif
|
||||
#ifndef WLED_REPO
|
||||
// No warning for this one: integrators are not always on GitHub
|
||||
#define WLED_REPO "unknown"
|
||||
#endif
|
||||
|
||||
constexpr uint32_t WLED_CUSTOM_DESC_MAGIC = 0x57535453; // "WSTS" (WLED System Tag Structure)
|
||||
constexpr uint32_t WLED_CUSTOM_DESC_VERSION = 2; // v1 - original PR; v2 - "safe to update from" version
|
||||
|
||||
// Compile-time validation that release name doesn't exceed maximum length
|
||||
static_assert(sizeof(WLED_RELEASE_NAME) <= WLED_RELEASE_NAME_MAX_LEN,
|
||||
"WLED_RELEASE_NAME exceeds maximum length of WLED_RELEASE_NAME_MAX_LEN characters");
|
||||
|
||||
|
||||
/**
|
||||
* DJB2 hash function (C++11 compatible constexpr)
|
||||
* Used for compile-time hash computation to validate structure contents
|
||||
* Recursive for compile time: not usable at runtime due to stack depth
|
||||
*
|
||||
* Note that this only works on strings; there is no way to produce a compile-time
|
||||
* hash of a struct in C++11 without explicitly listing all the struct members.
|
||||
* So for now, we hash only the release name. This suffices for a "did you find
|
||||
* valid structure" check.
|
||||
*
|
||||
*/
|
||||
constexpr uint32_t djb2_hash_constexpr(const char* str, uint32_t hash = 5381) {
|
||||
return (*str == '\0') ? hash : djb2_hash_constexpr(str + 1, ((hash << 5) + hash) + *str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime DJB2 hash function for validation
|
||||
*/
|
||||
inline uint32_t djb2_hash_runtime(const char* str) {
|
||||
uint32_t hash = 5381;
|
||||
while (*str) {
|
||||
hash = ((hash << 5) + hash) + *str++;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// GLOBAL VARIABLES
|
||||
// ------------------------------------
|
||||
// Structure instantiation for this build
|
||||
const wled_metadata_t __attribute__((section(BUILD_METADATA_SECTION))) WLED_BUILD_DESCRIPTION = {
|
||||
WLED_CUSTOM_DESC_MAGIC, // magic
|
||||
WLED_CUSTOM_DESC_VERSION, // version
|
||||
TOSTRING(WLED_VERSION),
|
||||
WLED_RELEASE_NAME, // release_name
|
||||
std::integral_constant<uint32_t, djb2_hash_constexpr(WLED_RELEASE_NAME)>::value, // hash - computed at compile time; integral_constant enforces this
|
||||
{ 0, 0, 0 }, // All other platforms can update safely
|
||||
};
|
||||
|
||||
static const char repoString_s[] PROGMEM = WLED_REPO;
|
||||
const __FlashStringHelper* repoString = FPSTR(repoString_s);
|
||||
|
||||
static const char productString_s[] PROGMEM = WLED_PRODUCT_NAME;
|
||||
const __FlashStringHelper* productString = FPSTR(productString_s);
|
||||
|
||||
static const char brandString_s [] PROGMEM = WLED_BRAND;
|
||||
const __FlashStringHelper* brandString = FPSTR(brandString_s);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Extract WLED custom description structure from binary
|
||||
* @param binaryData Pointer to binary file data
|
||||
* @param dataSize Size of binary data in bytes
|
||||
* @param extractedDesc Buffer to store extracted custom description structure
|
||||
* @return true if structure was found and extracted, false otherwise
|
||||
*/
|
||||
bool findWledMetadata(const uint8_t* binaryData, size_t dataSize, wled_metadata_t* extractedDesc) {
|
||||
if (!binaryData || !extractedDesc || dataSize < sizeof(wled_metadata_t)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t offset = 0; offset <= dataSize - sizeof(wled_metadata_t); offset++) {
|
||||
if ((binaryData[offset]) == static_cast<char>(WLED_CUSTOM_DESC_MAGIC)) {
|
||||
// First byte matched; check next in an alignment-safe way
|
||||
uint32_t data_magic;
|
||||
memcpy(&data_magic, binaryData + offset, sizeof(data_magic));
|
||||
|
||||
// Check for magic number
|
||||
if (data_magic == WLED_CUSTOM_DESC_MAGIC) {
|
||||
wled_metadata_t candidate;
|
||||
memcpy(&candidate, binaryData + offset, sizeof(candidate));
|
||||
|
||||
// Found potential match, validate version
|
||||
if (candidate.desc_version > WLED_CUSTOM_DESC_VERSION) {
|
||||
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but version mismatch: %u\n"),
|
||||
offset, candidate.desc_version);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate hash using runtime function
|
||||
uint32_t expected_hash = djb2_hash_runtime(candidate.release_name);
|
||||
if (candidate.hash != expected_hash) {
|
||||
DEBUG_PRINTF_P(PSTR("Found WLED structure at offset %u but hash mismatch\n"), offset);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Valid structure found - copy entire structure
|
||||
*extractedDesc = candidate;
|
||||
|
||||
DEBUG_PRINTF_P(PSTR("Extracted WLED structure at offset %u: '%s'\n"),
|
||||
offset, extractedDesc->release_name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DEBUG_PRINTLN(F("No WLED custom description found in binary"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if OTA should be allowed based on release compatibility using custom description
|
||||
* @param binaryData Pointer to binary file data (not modified)
|
||||
* @param dataSize Size of binary data in bytes
|
||||
* @param errorMessage Buffer to store error message if validation fails
|
||||
* @param errorMessageLen Maximum length of error message buffer
|
||||
* @return true if OTA should proceed, false if it should be blocked
|
||||
*/
|
||||
|
||||
bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessage, size_t errorMessageLen) {
|
||||
// Clear error message
|
||||
if (errorMessage && errorMessageLen > 0) {
|
||||
errorMessage[0] = '\0';
|
||||
}
|
||||
|
||||
// Validate compatibility using extracted release name
|
||||
// We make a stack copy so we can print it safely
|
||||
char safeFirmwareRelease[WLED_RELEASE_NAME_MAX_LEN];
|
||||
strncpy(safeFirmwareRelease, firmwareDescription.release_name, WLED_RELEASE_NAME_MAX_LEN - 1);
|
||||
safeFirmwareRelease[WLED_RELEASE_NAME_MAX_LEN - 1] = '\0';
|
||||
|
||||
if (strlen(safeFirmwareRelease) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strncmp_P(safeFirmwareRelease, releaseString, WLED_RELEASE_NAME_MAX_LEN) != 0) {
|
||||
if (errorMessage && errorMessageLen > 0) {
|
||||
snprintf_P(errorMessage, errorMessageLen, PSTR("Firmware release name mismatch: current='%s', uploaded='%s'."),
|
||||
releaseString, safeFirmwareRelease);
|
||||
errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (firmwareDescription.desc_version > 1) {
|
||||
// Add safe version check
|
||||
// Parse our version (x.y.z) and compare it to the "safe version" array
|
||||
const char* our_version = versionString;
|
||||
for(unsigned v_index = 0; v_index < 3; ++v_index) {
|
||||
char* our_version_end = nullptr;
|
||||
long our_v_parsed = strtol(our_version, &our_version_end, 10);
|
||||
if (!our_version_end || (our_version_end == our_version)) {
|
||||
// We were built with a malformed version string
|
||||
// We blame the integrator and attempt the update anyways - nothing the user can do to fix this
|
||||
break;
|
||||
}
|
||||
|
||||
if (firmwareDescription.safe_update_version[v_index] > our_v_parsed) {
|
||||
if (errorMessage && errorMessageLen > 0) {
|
||||
snprintf_P(errorMessage, errorMessageLen, PSTR("Cannot update from this version: requires at least %d.%d.%d, current='%s'."),
|
||||
firmwareDescription.safe_update_version[0], firmwareDescription.safe_update_version[1], firmwareDescription.safe_update_version[2],
|
||||
versionString);
|
||||
errorMessage[errorMessageLen - 1] = '\0'; // Ensure null termination
|
||||
}
|
||||
return false;
|
||||
} else if (firmwareDescription.safe_update_version[v_index] < our_v_parsed) {
|
||||
break; // no need to check the other components
|
||||
}
|
||||
|
||||
if (*our_version_end == '.') ++our_version_end;
|
||||
our_version = our_version_end;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: additional checks go here
|
||||
|
||||
return true;
|
||||
}
|
||||
62
wled00/wled_metadata.h
Normal file
62
wled00/wled_metadata.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
WLED build metadata
|
||||
|
||||
Manages and exports information about the current WLED build.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string.h>
|
||||
#include <WString.h>
|
||||
|
||||
#define WLED_VERSION_MAX_LEN 48
|
||||
#define WLED_RELEASE_NAME_MAX_LEN 48
|
||||
|
||||
/**
|
||||
* WLED Custom Description Structure
|
||||
* This structure is embedded in platform-specific sections at an approximately
|
||||
* fixed offset in ESP32/ESP8266 binaries, where it can be found and validated
|
||||
* by the OTA process.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t magic; // Magic number to identify WLED custom description
|
||||
uint32_t desc_version; // Structure version for future compatibility
|
||||
char wled_version[WLED_VERSION_MAX_LEN];
|
||||
char release_name[WLED_RELEASE_NAME_MAX_LEN]; // Release name (null-terminated)
|
||||
uint32_t hash; // Structure sanity check
|
||||
uint8_t safe_update_version[3]; // Indicates version it's known to be safe to install this update from: major, minor, patch
|
||||
} __attribute__((packed)) wled_metadata_t;
|
||||
|
||||
|
||||
// Global build description
|
||||
extern const wled_metadata_t WLED_BUILD_DESCRIPTION;
|
||||
|
||||
// Convenient metdata pointers
|
||||
#define versionString (WLED_BUILD_DESCRIPTION.wled_version) // Build version, WLED_VERSION
|
||||
#define releaseString (WLED_BUILD_DESCRIPTION.release_name) // Release name, WLED_RELEASE_NAME
|
||||
extern const __FlashStringHelper* repoString; // Github repository (if available)
|
||||
extern const __FlashStringHelper* productString; // Product, WLED_PRODUCT_NAME -- deprecated, use WLED_RELEASE_NAME
|
||||
extern const __FlashStringHelper* brandString ; // Brand
|
||||
|
||||
|
||||
// Metadata analysis functions
|
||||
|
||||
/**
|
||||
* Extract WLED custom description structure from binary data
|
||||
* @param binaryData Pointer to binary file data
|
||||
* @param dataSize Size of binary data in bytes
|
||||
* @param extractedDesc Buffer to store extracted custom description structure
|
||||
* @return true if structure was found and extracted, false otherwise
|
||||
*/
|
||||
bool findWledMetadata(const uint8_t* binaryData, size_t dataSize, wled_metadata_t* extractedDesc);
|
||||
|
||||
/**
|
||||
* Check if OTA should be allowed based on release compatibility
|
||||
* @param firmwareDescription Pointer to firmware description
|
||||
* @param errorMessage Buffer to store error message if validation fails
|
||||
* @param errorMessageLen Maximum length of error message buffer
|
||||
* @return true if OTA should proceed, false if it should be blocked
|
||||
*/
|
||||
bool shouldAllowOTA(const wled_metadata_t& firmwareDescription, char* errorMessage, size_t errorMessageLen);
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "wled.h"
|
||||
|
||||
#ifndef WLED_DISABLE_OTA
|
||||
#include "ota_update.h"
|
||||
#endif
|
||||
#include "html_ui.h"
|
||||
#include "html_settings.h"
|
||||
#include "html_other.h"
|
||||
@@ -16,6 +19,7 @@ static const char s_redirecting[] PROGMEM = "Redirecting...";
|
||||
static const char s_content_enc[] PROGMEM = "Content-Encoding";
|
||||
static const char s_unlock_ota [] PROGMEM = "Please unlock OTA in security settings!";
|
||||
static const char s_unlock_cfg [] PROGMEM = "Please unlock settings using PIN code!";
|
||||
static const char s_rebooting [] PROGMEM = "Rebooting now...";
|
||||
static const char s_notimplemented[] PROGMEM = "Not implemented";
|
||||
static const char s_accessdenied[] PROGMEM = "Access Denied";
|
||||
static const char _common_js[] PROGMEM = "/common.js";
|
||||
@@ -375,49 +379,40 @@ void initServer()
|
||||
});
|
||||
|
||||
server.on(_update, HTTP_POST, [](AsyncWebServerRequest *request){
|
||||
if (!correctPIN) {
|
||||
serveSettings(request, true); // handle PIN page POST request
|
||||
return;
|
||||
}
|
||||
if (otaLock) {
|
||||
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254);
|
||||
return;
|
||||
}
|
||||
if (Update.hasError()) {
|
||||
serveMessage(request, 500, F("Update failed!"), F("Please check your file and retry!"), 254);
|
||||
} else {
|
||||
serveMessage(request, 200, F("Update successful!"), F("Rebooting..."), 131);
|
||||
doReboot = true;
|
||||
}
|
||||
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
|
||||
if (!correctPIN || otaLock) return;
|
||||
if(!index){
|
||||
DEBUG_PRINTLN(F("OTA Update Start"));
|
||||
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||
WLED::instance().disableWatchdog();
|
||||
#endif
|
||||
UsermodManager::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
|
||||
strip.suspend();
|
||||
#ifdef ESP8266
|
||||
strip.resetSegments(); // free as much memory as you can
|
||||
Update.runAsync(true);
|
||||
#endif
|
||||
Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
||||
}
|
||||
if(!Update.hasError()) Update.write(data, len);
|
||||
if(final){
|
||||
if(Update.end(true)){
|
||||
DEBUG_PRINTLN(F("Update Success"));
|
||||
} else {
|
||||
DEBUG_PRINTLN(F("Update Failed"));
|
||||
strip.resume();
|
||||
UsermodManager::onUpdateBegin(false); // notify usermods that update has failed (some may require task init)
|
||||
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||
WLED::instance().enableWatchdog();
|
||||
#endif
|
||||
if (request->_tempObject) {
|
||||
auto ota_result = getOTAResult(request);
|
||||
if (ota_result.first) {
|
||||
if (ota_result.second.length() > 0) {
|
||||
serveMessage(request, 500, F("Update failed!"), ota_result.second, 254);
|
||||
} else {
|
||||
serveMessage(request, 200, F("Update successful!"), FPSTR(s_rebooting), 131);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No context structure - something's gone horribly wrong
|
||||
serveMessage(request, 500, F("Update failed!"), F("Internal server fault"), 254);
|
||||
}
|
||||
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){
|
||||
if (index == 0) {
|
||||
// Allocate the context structure
|
||||
if (!initOTA(request)) {
|
||||
return; // Error will be dealt with after upload in response handler, above
|
||||
}
|
||||
|
||||
// Privilege checks
|
||||
if (!correctPIN) {
|
||||
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_cfg), 254);
|
||||
setOTAReplied(request);
|
||||
return;
|
||||
};
|
||||
if (otaLock) {
|
||||
serveMessage(request, 401, FPSTR(s_accessdenied), FPSTR(s_unlock_ota), 254);
|
||||
setOTAReplied(request);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
handleOTAData(request, index, data, len, isFinal);
|
||||
});
|
||||
#else
|
||||
server.on(_update, HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
|
||||
@@ -26,7 +26,8 @@ void XML_response(Print& dest)
|
||||
);
|
||||
}
|
||||
|
||||
static void extractPin(Print& settingsScript, JsonObject &obj, const char *key) {
|
||||
static void extractPin(Print& settingsScript, JsonObject &obj, const char *key)
|
||||
{
|
||||
if (obj[key].is<JsonArray>()) {
|
||||
JsonArray pins = obj[key].as<JsonArray>();
|
||||
for (JsonVariant pv : pins) {
|
||||
@@ -37,6 +38,22 @@ static void extractPin(Print& settingsScript, JsonObject &obj, const char *key)
|
||||
}
|
||||
}
|
||||
|
||||
void fillWLEDVersion(char *buf, size_t len)
|
||||
{
|
||||
if (!buf || len == 0) return;
|
||||
|
||||
snprintf_P(buf,len,PSTR("WLED %s (%d)<br>\\\"%s\\\"<br>(Processor: %s)"),
|
||||
versionString,
|
||||
VERSION,
|
||||
releaseString,
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
ESP.getChipModel()
|
||||
#else
|
||||
"ESP8266"
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
// print used pins by scanning JsonObject (1 level deep)
|
||||
static void fillUMPins(Print& settingsScript, JsonObject &mods)
|
||||
{
|
||||
@@ -72,7 +89,8 @@ static void fillUMPins(Print& settingsScript, JsonObject &mods)
|
||||
}
|
||||
}
|
||||
|
||||
void appendGPIOinfo(Print& settingsScript) {
|
||||
void appendGPIOinfo(Print& settingsScript)
|
||||
{
|
||||
settingsScript.print(F("d.um_p=[-1")); // has to have 1 element
|
||||
if (i2c_sda > -1 && i2c_scl > -1) {
|
||||
settingsScript.printf_P(PSTR(",%d,%d"), i2c_sda, i2c_scl);
|
||||
@@ -580,7 +598,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
||||
printSetFormCheckbox(settingsScript,PSTR("OW"),wifiLock);
|
||||
printSetFormCheckbox(settingsScript,PSTR("AO"),aOtaEnabled);
|
||||
char tmp_buf[128];
|
||||
snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s (build %d)"),versionString,VERSION);
|
||||
fillWLEDVersion(tmp_buf,sizeof(tmp_buf));
|
||||
printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf);
|
||||
settingsScript.printf_P(PSTR("sd=\"%s\";"), serverDescription);
|
||||
}
|
||||
@@ -632,22 +650,6 @@ void getSettingsJS(byte subPage, Print& settingsScript)
|
||||
UsermodManager::appendConfigData(settingsScript);
|
||||
}
|
||||
|
||||
if (subPage == SUBPAGE_UPDATE) // update
|
||||
{
|
||||
char tmp_buf[128];
|
||||
snprintf_P(tmp_buf,sizeof(tmp_buf),PSTR("WLED %s<br>%s<br>(%s build %d)"),
|
||||
versionString,
|
||||
releaseString,
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
ESP.getChipModel(),
|
||||
#else
|
||||
"esp8266",
|
||||
#endif
|
||||
VERSION);
|
||||
|
||||
printSetClassElementHTML(settingsScript,PSTR("sip"),0,tmp_buf);
|
||||
}
|
||||
|
||||
if (subPage == SUBPAGE_2D) // 2D matrices
|
||||
{
|
||||
printSetFormValue(settingsScript,PSTR("SOMP"),strip.isMatrix);
|
||||
|
||||
Reference in New Issue
Block a user