Merge remote-tracking branch 'upstream/main' into usermod-libs

This commit is contained in:
Will Miles 2025-03-01 20:32:12 +00:00
commit e00789f838
21 changed files with 5908 additions and 342 deletions

View File

@ -19,6 +19,14 @@ jobs:
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
merge-multiple: true merge-multiple: true
- name: Show Files
run: ls -la
- name: "✏️ Generate release changelog"
id: changelog
uses: janheinrichmerker/action-github-changelog-generator@v2.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
sinceTag: v0.15.0
- name: Update Nightly Release - name: Update Nightly Release
uses: andelf/nightly-release@main uses: andelf/nightly-release@main
env: env:
@ -27,7 +35,13 @@ jobs:
tag_name: nightly tag_name: nightly
name: 'Nightly Release $$' name: 'Nightly Release $$'
prerelease: true prerelease: true
body: 'nightly' body: ${{ steps.changelog.outputs.changelog }}
files: | files: |
*.bin *.bin
*.bin.gz *.bin.gz
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v3
with:
repository: wled/WLED-WebInstaller
event-type: release-nightly
token: ${{ secrets.PAT_PUBLIC }}

13
.github/workflows/pr-merge.yaml vendored Normal file
View File

@ -0,0 +1,13 @@
name: Notify Discord on PR Merge
on:
pull_request:
types: [closed]
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Send Discord notification
if: github.event.pull_request.merged == true
run: |
curl -H "Content-Type: application/json" -d '{"content": "Pull Request #{{ github.event.pull_request.number }} merged by {{ github.actor }}"}' ${{ secrets.DISCORD_WEBHOOK_BETA_TESTERS }}

View File

@ -18,9 +18,16 @@ jobs:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v4
with: with:
merge-multiple: true merge-multiple: true
- name: "✏️ Generate release changelog"
id: changelog
uses: janheinrichmerker/action-github-changelog-generator@v2.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
sinceTag: v0.15.0
- name: Create draft release - name: Create draft release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
body: ${{ steps.changelog.outputs.changelog }}
draft: True draft: True
files: | files: |
*.bin *.bin

13
.github/workflows/test.yaml vendored Normal file
View File

@ -0,0 +1,13 @@
on:
workflow_dispatch:
jobs:
dispatch:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v3
with:
repository: wled/WLED-WebInstaller
event-type: release-nightly
token: ${{ secrets.PAT_PUBLIC }}

View File

@ -10,7 +10,7 @@ if node_ex is None:
else: else:
# Install the necessary node packages for the pre-build asset bundling script # Install the necessary node packages for the pre-build asset bundling script
print('\x1b[6;33;42m' + 'Installing node packages' + '\x1b[0m') print('\x1b[6;33;42m' + 'Installing node packages' + '\x1b[0m')
env.Execute("npm install") env.Execute("npm ci")
# Call the bundling script # Call the bundling script
exitCode = env.Execute("npm run build") exitCode = env.Execute("npm run build")
@ -18,4 +18,4 @@ else:
# If it failed, abort the build # If it failed, abort the build
if (exitCode): if (exitCode):
print('\x1b[0;31;43m' + 'npm run build fails check https://kno.wled.ge/advanced/compiling-wled/' + '\x1b[0m') print('\x1b[0;31;43m' + 'npm run build fails check https://kno.wled.ge/advanced/compiling-wled/' + '\x1b[0m')
exit(exitCode) exit(exitCode)

View File

@ -360,6 +360,7 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_4m1m} board_build.ldscript = ${common.ldscript_4m1m}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
monitor_filters = esp8266_exception_decoder monitor_filters = esp8266_exception_decoder
@ -369,12 +370,14 @@ extends = env:nodemcuv2
platform = ${esp8266.platform_compat} platform = ${esp8266.platform_compat}
platform_packages = ${esp8266.platform_packages_compat} platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9 ;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9
[env:nodemcuv2_160] [env:nodemcuv2_160]
extends = env:nodemcuv2 extends = env:nodemcuv2
board_build.f_cpu = 160000000L board_build.f_cpu = 160000000L
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_160\" #-DWLED_DISABLE_2D build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_160\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM2D
custom_usermods = audioreactive custom_usermods = audioreactive
[env:esp8266_2m] [env:esp8266_2m]
@ -384,6 +387,8 @@ platform_packages = ${common.platform_packages}
board_build.ldscript = ${common.ldscript_2m512k} board_build.ldscript = ${common.ldscript_2m512k}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\" build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\"
-D WLED_DISABLE_PARTICLESYSTEM2D
-D WLED_DISABLE_PARTICLESYSTEM1D
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
[env:esp8266_2m_compat] [env:esp8266_2m_compat]
@ -392,11 +397,15 @@ extends = env:esp8266_2m
platform = ${esp8266.platform_compat} platform = ${esp8266.platform_compat}
platform_packages = ${esp8266.platform_packages_compat} platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
[env:esp8266_2m_160] [env:esp8266_2m_160]
extends = env:esp8266_2m extends = env:esp8266_2m
board_build.f_cpu = 160000000L board_build.f_cpu = 160000000L
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_160\" build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_160\"
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
custom_usermods = audioreactive custom_usermods = audioreactive
[env:esp01_1m_full] [env:esp01_1m_full]
@ -407,6 +416,8 @@ board_build.ldscript = ${common.ldscript_1m128k}
build_unflags = ${common.build_unflags} build_unflags = ${common.build_unflags}
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01\" -D WLED_DISABLE_OTA build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01\" -D WLED_DISABLE_OTA
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
lib_deps = ${esp8266.lib_deps} lib_deps = ${esp8266.lib_deps}
[env:esp01_1m_full_compat] [env:esp01_1m_full_compat]
@ -415,12 +426,16 @@ extends = env:esp01_1m_full
platform = ${esp8266.platform_compat} platform = ${esp8266.platform_compat}
platform_packages = ${esp8266.platform_packages_compat} platform_packages = ${esp8266.platform_packages_compat}
build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP01_compat\" -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP01_compat\" -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
[env:esp01_1m_full_160] [env:esp01_1m_full_160]
extends = env:esp01_1m_full extends = env:esp01_1m_full
board_build.f_cpu = 160000000L board_build.f_cpu = 160000000L
build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01_160\" -D WLED_DISABLE_OTA build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01_160\" -D WLED_DISABLE_OTA
; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM
-D WLED_DISABLE_PARTICLESYSTEM1D
-D WLED_DISABLE_PARTICLESYSTEM2D
custom_usermods = audioreactive custom_usermods = audioreactive
[env:esp32dev] [env:esp32dev]

File diff suppressed because it is too large Load Diff

View File

@ -323,8 +323,35 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#define FX_MODE_WAVESINS 184 #define FX_MODE_WAVESINS 184
#define FX_MODE_ROCKTAVES 185 #define FX_MODE_ROCKTAVES 185
#define FX_MODE_2DAKEMI 186 #define FX_MODE_2DAKEMI 186
#define FX_MODE_PARTICLEVOLCANO 187
#define MODE_COUNT 187 #define FX_MODE_PARTICLEFIRE 188
#define FX_MODE_PARTICLEFIREWORKS 189
#define FX_MODE_PARTICLEVORTEX 190
#define FX_MODE_PARTICLEPERLIN 191
#define FX_MODE_PARTICLEPIT 192
#define FX_MODE_PARTICLEBOX 193
#define FX_MODE_PARTICLEATTRACTOR 194
#define FX_MODE_PARTICLEIMPACT 195
#define FX_MODE_PARTICLEWATERFALL 196
#define FX_MODE_PARTICLESPRAY 197
#define FX_MODE_PARTICLESGEQ 198
#define FX_MODE_PARTICLECENTERGEQ 199
#define FX_MODE_PARTICLEGHOSTRIDER 200
#define FX_MODE_PARTICLEBLOBS 201
#define FX_MODE_PSDRIP 202
#define FX_MODE_PSPINBALL 203
#define FX_MODE_PSDANCINGSHADOWS 204
#define FX_MODE_PSFIREWORKS1D 205
#define FX_MODE_PSSPARKLER 206
#define FX_MODE_PSHOURGLASS 207
#define FX_MODE_PS1DSPRAY 208
#define FX_MODE_PSBALANCE 209
#define FX_MODE_PSCHASE 210
#define FX_MODE_PSSTARBURST 211
#define FX_MODE_PS1DGEQ 212
#define FX_MODE_PSFIRE1D 213
#define FX_MODE_PS1DSONICSTREAM 214
#define MODE_COUNT 215
#define BLEND_STYLE_FADE 0x00 // universal #define BLEND_STYLE_FADE 0x00 // universal
@ -481,6 +508,7 @@ typedef struct Segment {
uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 blends possible) uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 blends possible)
unsigned long _start; // must accommodate millis() unsigned long _start; // must accommodate millis()
uint16_t _dur; uint16_t _dur;
// -> here is one byte of padding
Transition(uint16_t dur=750) Transition(uint16_t dur=750)
: _palT(CRGBPalette16(CRGB::Black)) : _palT(CRGBPalette16(CRGB::Black))
, _prevPaletteBlends(0) , _prevPaletteBlends(0)
@ -579,6 +607,7 @@ typedef struct Segment {
inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; } inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; }
#ifndef WLED_DISABLE_MODE_BLEND #ifndef WLED_DISABLE_MODE_BLEND
inline static void modeBlend(bool blend) { _modeBlend = blend; } inline static void modeBlend(bool blend) { _modeBlend = blend; }
inline static bool getmodeBlend(void) { return _modeBlend; }
#endif #endif
inline static unsigned vLength() { return Segment::_vLength; } inline static unsigned vLength() { return Segment::_vLength; }
inline static unsigned vWidth() { return Segment::_vWidth; } inline static unsigned vWidth() { return Segment::_vWidth; }
@ -627,6 +656,7 @@ typedef struct Segment {
uint8_t currentMode() const; // currently active effect/mode (while in transition) uint8_t currentMode() const; // currently active effect/mode (while in transition)
[[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition) [[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition)
CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal);
void loadOldPalette(); // loads old FX palette into _currentPalette
// 1D strip // 1D strip
[[gnu::hot]] uint16_t virtualLength() const; [[gnu::hot]] uint16_t virtualLength() const;
@ -1009,4 +1039,4 @@ class WS2812FX { // 96 bytes
extern const char JSON_mode_names[]; extern const char JSON_mode_names[];
extern const char JSON_palette_names[]; extern const char JSON_palette_names[];
#endif #endif

View File

@ -11,6 +11,7 @@
*/ */
#include "wled.h" #include "wled.h"
#include "FX.h" #include "FX.h"
#include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h?
#include "palettes.h" #include "palettes.h"
/* /*
@ -470,6 +471,12 @@ void Segment::beginDraw() {
} }
} }
// loads palette of the old FX during transitions (used by particle system)
void Segment::loadOldPalette(void) {
if(isInTransition())
loadPalette(_currentPalette, _t->_palTid);
}
// relies on WS2812FX::service() to call it for each frame // relies on WS2812FX::service() to call it for each frame
void Segment::handleRandomPalette() { void Segment::handleRandomPalette() {
// is it time to generate a new palette? // is it time to generate a new palette?
@ -1592,6 +1599,9 @@ void WS2812FX::service() {
_segment_index++; _segment_index++;
} }
Segment::setClippingRect(0, 0); // disable clipping for overlays Segment::setClippingRect(0, 0); // disable clipping for overlays
#if !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D))
servicePSmem(); // handle segment particle system memory
#endif
_isServicing = false; _isServicing = false;
_triggered = false; _triggered = false;
@ -1991,12 +2001,17 @@ bool WS2812FX::deserializeMap(unsigned n) {
if (!isFile || !requestJSONBufferLock(7)) return false; if (!isFile || !requestJSONBufferLock(7)) return false;
if (!readObjectFromFile(fileName, nullptr, pDoc)) { StaticJsonDocument<64> filter;
filter[F("width")] = true;
filter[F("height")] = true;
if (!readObjectFromFile(fileName, nullptr, pDoc, &filter)) {
DEBUG_PRINT(F("ERROR Invalid ledmap in ")); DEBUG_PRINTLN(fileName); DEBUG_PRINT(F("ERROR Invalid ledmap in ")); DEBUG_PRINTLN(fileName);
releaseJSONBufferLock(); releaseJSONBufferLock();
return false; // if file does not load properly then exit return false; // if file does not load properly then exit
} }
suspend();
JsonObject root = pDoc->as<JsonObject>(); JsonObject root = pDoc->as<JsonObject>();
// if we are loading default ledmap (at boot) set matrix width and height from the ledmap (compatible with WLED MM ledmaps) // if we are loading default ledmap (at boot) set matrix width and height from the ledmap (compatible with WLED MM ledmaps)
if (isMatrix && n == 0 && (!root[F("width")].isNull() || !root[F("height")].isNull())) { if (isMatrix && n == 0 && (!root[F("width")].isNull() || !root[F("height")].isNull())) {
@ -2009,16 +2024,52 @@ bool WS2812FX::deserializeMap(unsigned n) {
if (customMappingTable) { if (customMappingTable) {
DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName); DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName);
File f = WLED_FS.open(fileName, "r");
f.find("\"map\":[");
while (f.available()) { // f.position() < f.size() - 1
char number[32];
size_t numRead = f.readBytesUntil(',', number, sizeof(number)-1); // read a single number (may include array terminating "]" but not number separator ',')
number[numRead] = 0;
if (numRead > 0) {
char *end = strchr(number,']'); // we encountered end of array so stop processing if no digit found
bool foundDigit = (end == nullptr);
int i = 0;
if (end != nullptr) do {
if (number[i] >= '0' && number[i] <= '9') foundDigit = true;
if (foundDigit || &number[i++] == end) break;
} while (i < 32);
if (!foundDigit) break;
int index = atoi(number);
if (index < 0 || index > 16384) index = 0xFFFF;
customMappingTable[customMappingSize++] = index;
if (customMappingSize > getLengthTotal()) break;
} else break; // there was nothing to read, stop
}
currentLedmap = n;
f.close();
#ifdef WLED_DEBUG
DEBUG_PRINT(F("Loaded ledmap:"));
for (unsigned i=0; i<customMappingSize; i++) {
if (!(i%Segment::maxWidth)) DEBUG_PRINTLN();
DEBUG_PRINTF_P(PSTR("%4d,"), customMappingTable[i]);
}
DEBUG_PRINTLN();
#endif
/*
JsonArray map = root[F("map")]; JsonArray map = root[F("map")];
if (!map.isNull() && map.size()) { // not an empty map if (!map.isNull() && map.size()) { // not an empty map
customMappingSize = min((unsigned)map.size(), (unsigned)getLengthTotal()); customMappingSize = min((unsigned)map.size(), (unsigned)getLengthTotal());
for (unsigned i=0; i<customMappingSize; i++) customMappingTable[i] = (uint16_t) (map[i]<0 ? 0xFFFFU : map[i]); for (unsigned i=0; i<customMappingSize; i++) customMappingTable[i] = (uint16_t) (map[i]<0 ? 0xFFFFU : map[i]);
currentLedmap = n; currentLedmap = n;
} }
*/
} else { } else {
DEBUG_PRINTLN(F("ERROR LED map allocation error.")); DEBUG_PRINTLN(F("ERROR LED map allocation error."));
} }
resume();
releaseJSONBufferLock(); releaseJSONBufferLock();
return (customMappingSize > 0); return (customMappingSize > 0);
} }
@ -2036,4 +2087,4 @@ const char JSON_palette_names[] PROGMEM = R"=====([
"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf", "Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf",
"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", "Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide",
"Candy2","Traffic Light" "Candy2","Traffic Light"
])====="; ])=====";

2433
wled00/FXparticleSystem.cpp Normal file

File diff suppressed because it is too large Load Diff

418
wled00/FXparticleSystem.h Normal file
View File

@ -0,0 +1,418 @@
/*
FXparticleSystem.cpp
Particle system with functions for particle generation, particle movement and particle rendering to RGB matrix.
by DedeHai (Damian Schneider) 2013-2024
Copyright (c) 2024 Damian Schneider
Licensed under the EUPL v. 1.2 or later
*/
#ifdef WLED_DISABLE_2D
#define WLED_DISABLE_PARTICLESYSTEM2D
#endif
#if !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D)) // not both disabled
#include <stdint.h>
#include "wled.h"
#define PS_P_MAXSPEED 120 // maximum speed a particle can have (vx/vy is int8)
#define MAX_MEMIDLE 10 // max idle time (in frames) before memory is deallocated (if deallocated during an effect, it will crash!)
//#define WLED_DEBUG_PS // note: enabling debug uses ~3k of flash
#ifdef WLED_DEBUG_PS
#define PSPRINT(x) Serial.print(x)
#define PSPRINTLN(x) Serial.println(x)
#else
#define PSPRINT(x)
#define PSPRINTLN(x)
#endif
// memory and transition manager
struct partMem {
void* particleMemPointer; // pointer to particle memory
uint32_t buffersize; // buffer size in bytes
uint8_t particleType; // type of particles currently in memory: 0 = none, particle struct size otherwise (required for 1D<->2D transitions)
uint8_t id; // ID of segment this memory belongs to
uint8_t watchdog; // counter to handle deallocation
uint8_t inTransition; // to track PS to PS FX transitions (is set to new FX ID during transitions), not set if not both FX are PS FX
uint8_t currentFX; // current FX ID, is set when transition is complete, used to detect back and forth transitions
bool finalTransfer; // used to update buffer in rendering function after transition has ended
bool transferParticles; // if set, particles in buffer are transferred to new FX
};
void* particleMemoryManager(const uint32_t requestedParticles, size_t structSize, uint32_t &availableToPS, uint32_t numParticlesUsed, const uint8_t effectID); // update particle memory pointer, handles memory transitions
void particleHandover(void *buffer, size_t structSize, int32_t numParticles);
void updateUsedParticles(const uint32_t allocated, const uint32_t available, const uint8_t percentage, uint32_t &used);
bool segmentIsOverlay(void); // check if segment is fully overlapping with at least one underlying segment
partMem* getPartMem(void); // returns pointer to memory struct for current segment or nullptr
void updateRenderingBuffer(uint32_t requiredpixels, bool isFramebuffer, bool initialize); // allocate CRGB rendering buffer, update size if needed
void transferBuffer(uint32_t width, uint32_t height, bool useAdditiveTransfer = false); // transfer the buffer to the segment (supports 1D and 2D)
void servicePSmem(); // increments watchdog, frees memory if idle too long
// limit speed of particles (used in 1D and 2D)
static inline int32_t limitSpeed(const int32_t speed) {
return speed > PS_P_MAXSPEED ? PS_P_MAXSPEED : (speed < -PS_P_MAXSPEED ? -PS_P_MAXSPEED : speed); // note: this is slightly faster than using min/max at the cost of 50bytes of flash
}
#endif
#ifndef WLED_DISABLE_PARTICLESYSTEM2D
// memory allocation
#define ESP8266_MAXPARTICLES 300 // enough up to 20x20 pixels
#define ESP8266_MAXSOURCES 24
#define ESP32S2_MAXPARTICLES 1024 // enough up to 32x32 pixels
#define ESP32S2_MAXSOURCES 64
#define ESP32_MAXPARTICLES 2048 // enough up to 64x32 pixels
#define ESP32_MAXSOURCES 128
// particle dimensions (subpixel division)
#define PS_P_RADIUS 64 // subpixel size, each pixel is divided by this for particle movement (must be a power of 2)
#define PS_P_HALFRADIUS (PS_P_RADIUS >> 1)
#define PS_P_RADIUS_SHIFT 6 // shift for RADIUS
#define PS_P_SURFACE 12 // shift: 2^PS_P_SURFACE = (PS_P_RADIUS)^2
#define PS_P_MINHARDRADIUS 64 // minimum hard surface radius for collisions
#define PS_P_MINSURFACEHARDNESS 128 // minimum hardness used in collision impulse calculation, below this hardness, particles become sticky
// struct for PS settings (shared for 1D and 2D class)
typedef union {
struct{ // one byte bit field for 2D settings
bool wrapX : 1;
bool wrapY : 1;
bool bounceX : 1;
bool bounceY : 1;
bool killoutofbounds : 1; // if set, out of bound particles are killed immediately
bool useGravity : 1; // set to 1 if gravity is used, disables bounceY at the top
bool useCollisions : 1;
bool colorByAge : 1; // if set, particle hue is set by ttl value in render function
};
byte asByte; // access as a byte, order is: LSB is first entry in the list above
} PSsettings2D;
//struct for a single particle
typedef struct { // 10 bytes
int16_t x; // x position in particle system
int16_t y; // y position in particle system
uint16_t ttl; // time to live in frames
int8_t vx; // horizontal velocity
int8_t vy; // vertical velocity
uint8_t hue; // color hue
uint8_t sat; // particle color saturation
} PSparticle;
//struct for particle flags note: this is separate from the particle struct to save memory (ram alignment)
typedef union {
struct { // 1 byte
bool outofbounds : 1; // out of bounds flag, set to true if particle is outside of display area
bool collide : 1; // if set, particle takes part in collisions
bool perpetual : 1; // if set, particle does not age (TTL is not decremented in move function, it still dies from killoutofbounds)
bool custom1 : 1; // unused custom flags, can be used by FX to track particle states
bool custom2 : 1;
bool custom3 : 1;
bool custom4 : 1;
bool custom5 : 1;
};
byte asByte; // access as a byte, order is: LSB is first entry in the list above
} PSparticleFlags;
// struct for additional particle settings (option)
typedef struct { // 2 bytes
uint8_t size; // particle size, 255 means 10 pixels in diameter
uint8_t forcecounter; // counter for applying forces to individual particles
} PSadvancedParticle;
// struct for advanced particle size control (option)
typedef struct { // 8 bytes
uint8_t asymmetry; // asymmetrical size (0=symmetrical, 255 fully asymmetric)
uint8_t asymdir; // direction of asymmetry, 64 is x, 192 is y (0 and 128 is symmetrical)
uint8_t maxsize; // target size for growing
uint8_t minsize; // target size for shrinking
uint8_t sizecounter : 4; // counters used for size contol (grow/shrink/wobble)
uint8_t wobblecounter : 4;
uint8_t growspeed : 4;
uint8_t shrinkspeed : 4;
uint8_t wobblespeed : 4;
bool grow : 1; // flags
bool shrink : 1;
bool pulsate : 1; // grows & shrinks & grows & ...
bool wobble : 1; // alternate x and y size
} PSsizeControl;
//struct for a particle source (20 bytes)
typedef struct {
uint16_t minLife; // minimum ttl of emittet particles
uint16_t maxLife; // maximum ttl of emitted particles
PSparticle source; // use a particle as the emitter source (speed, position, color)
PSparticleFlags sourceFlags; // flags for the source particle
int8_t var; // variation of emitted speed (adds random(+/- var) to speed)
int8_t vx; // emitting speed
int8_t vy;
uint8_t size; // particle size (advanced property)
} PSsource;
// class uses approximately 60 bytes
class ParticleSystem2D {
public:
ParticleSystem2D(const uint32_t width, const uint32_t height, const uint32_t numberofparticles, const uint32_t numberofsources, const bool isadvanced = false, const bool sizecontrol = false); // constructor
// note: memory is allcated in the FX function, no deconstructor needed
void update(void); //update the particles according to set options and render to the matrix
void updateFire(const uint8_t intensity, const bool renderonly); // update function for fire, if renderonly is set, particles are not updated (required to fix transitions with frameskips)
void updateSystem(void); // call at the beginning of every FX, updates pointers and dimensions
void particleMoveUpdate(PSparticle &part, PSparticleFlags &partFlags, PSsettings2D *options = NULL, PSadvancedParticle *advancedproperties = NULL); // move function
// particle emitters
int32_t sprayEmit(const PSsource &emitter);
void flameEmit(const PSsource &emitter);
int32_t angleEmit(PSsource& emitter, const uint16_t angle, const int32_t speed);
//particle physics
void applyGravity(PSparticle &part); // applies gravity to single particle (use this for sources)
[[gnu::hot]] void applyForce(PSparticle &part, const int8_t xforce, const int8_t yforce, uint8_t &counter);
[[gnu::hot]] void applyForce(const uint32_t particleindex, const int8_t xforce, const int8_t yforce); // use this for advanced property particles
void applyForce(const int8_t xforce, const int8_t yforce); // apply a force to all particles
void applyAngleForce(PSparticle &part, const int8_t force, const uint16_t angle, uint8_t &counter);
void applyAngleForce(const uint32_t particleindex, const int8_t force, const uint16_t angle); // use this for advanced property particles
void applyAngleForce(const int8_t force, const uint16_t angle); // apply angular force to all particles
void applyFriction(PSparticle &part, const int32_t coefficient); // apply friction to specific particle
void applyFriction(const int32_t coefficient); // apply friction to all used particles
void pointAttractor(const uint32_t particleindex, PSparticle &attractor, const uint8_t strength, const bool swallow);
// set options note: inlining the set function uses more flash so dont optimize
void setUsedParticles(const uint8_t percentage); // set the percentage of particles used in the system, 255=100%
inline uint32_t getAvailableParticles(void) { return availableParticles; } // available particles in the buffer, use this to check if buffer changed during FX init
void setCollisionHardness(const uint8_t hardness); // hardness for particle collisions (255 means full hard)
void setWallHardness(const uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set
void setWallRoughness(const uint8_t roughness); // wall roughness randomizes wall collisions
void setMatrixSize(const uint32_t x, const uint32_t y);
void setWrapX(const bool enable);
void setWrapY(const bool enable);
void setBounceX(const bool enable);
void setBounceY(const bool enable);
void setKillOutOfBounds(const bool enable); // if enabled, particles outside of matrix instantly die
void setSaturation(const uint8_t sat); // set global color saturation
void setColorByAge(const bool enable);
void setMotionBlur(const uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero
void setSmearBlur(const uint8_t bluramount); // enable 2D smeared blurring of full frame
void setParticleSize(const uint8_t size);
void setGravity(const int8_t force = 8);
void enableParticleCollisions(const bool enable, const uint8_t hardness = 255);
PSparticle *particles; // pointer to particle array
PSparticleFlags *particleFlags; // pointer to particle flags array
PSsource *sources; // pointer to sources
PSadvancedParticle *advPartProps; // pointer to advanced particle properties (can be NULL)
PSsizeControl *advPartSize; // pointer to advanced particle size control (can be NULL)
uint8_t* PSdataEnd; // points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data
int32_t maxX, maxY; // particle system size i.e. width-1 / height-1 in subpixels, Note: all "max" variables must be signed to compare to coordinates (which are signed)
int32_t maxXpixel, maxYpixel; // last physical pixel that can be drawn to (FX can read this to read segment size if required), equal to width-1 / height-1
uint32_t numSources; // number of sources
uint32_t usedParticles; // number of particles used in animation, is relative to 'numParticles'
//note: some variables are 32bit for speed and code size at the cost of ram
private:
//rendering functions
void ParticleSys_render();
[[gnu::hot]] void renderParticle(const uint32_t particleindex, const uint32_t brightness, const CRGB& color, const bool wrapX, const bool wrapY);
//paricle physics applied by system if flags are set
void applyGravity(); // applies gravity to all particles
void handleCollisions();
[[gnu::hot]] void collideParticles(PSparticle &particle1, PSparticle &particle2, const int32_t dx, const int32_t dy, const int32_t collDistSq);
void fireParticleupdate();
//utility functions
void updatePSpointers(const bool isadvanced, const bool sizecontrol); // update the data pointers to current segment data space
bool updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control
void getParticleXYsize(PSadvancedParticle *advprops, PSsizeControl *advsize, uint32_t &xsize, uint32_t &ysize);
[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t &parallelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
// note: variables that are accessed often are 32bit for speed
PSsettings2D particlesettings; // settings used when updating particles (can also used by FX to move sources), do not edit properties directly, use functions above
uint32_t numParticles; // total number of particles allocated by this system note: during transitions, less are available, use availableParticles
uint32_t availableParticles; // number of particles available for use (can be more or less than numParticles, assigned by memory manager)
uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
int32_t collisionHardness;
uint32_t wallHardness;
uint32_t wallRoughness; // randomizes wall collisions
uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection (32bit for speed)
uint16_t collisionStartIdx; // particle array start index for collision detection
uint8_t fireIntesity = 0; // fire intensity, used for fire mode (flash use optimization, better than passing an argument to render function)
uint8_t fractionOfParticlesUsed; // percentage of particles used in the system (255=100%), used during transition updates
uint8_t forcecounter; // counter for globally applied forces
uint8_t gforcecounter; // counter for global gravity
int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards)
// global particle properties for basic particles
uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels, 255 = 10 pixels (note: this is also added to individual sized particles)
uint8_t motionBlur; // motion blur, values > 100 gives smoother animations. Note: motion blurring does not work if particlesize is > 0
uint8_t smearBlur; // 2D smeared blurring of full frame
uint8_t effectID; // ID of the effect that is using this particle system, used for transitions
uint32_t lastRender; // last time the particles were rendered, intermediate fix for speedup
};
void blur2D(CRGB *colorbuffer, const uint32_t xsize, uint32_t ysize, const uint32_t xblur, const uint32_t yblur, const uint32_t xstart = 0, uint32_t ystart = 0, const bool isparticle = false);
// initialization functions (not part of class)
bool initParticleSystem2D(ParticleSystem2D *&PartSys, const uint32_t requestedsources, const uint32_t additionalbytes = 0, const bool advanced = false, const bool sizecontrol = false);
uint32_t calculateNumberOfParticles2D(const uint32_t pixels, const bool advanced, const bool sizecontrol);
uint32_t calculateNumberOfSources2D(const uint32_t pixels, const uint32_t requestedsources);
bool allocateParticleSystemMemory2D(const uint32_t numparticles, const uint32_t numsources, const bool advanced, const bool sizecontrol, const uint32_t additionalbytes);
#endif // WLED_DISABLE_PARTICLESYSTEM2D
////////////////////////
// 1D Particle System //
////////////////////////
#ifndef WLED_DISABLE_PARTICLESYSTEM1D
// memory allocation
#define ESP8266_MAXPARTICLES_1D 450
#define ESP8266_MAXSOURCES_1D 16
#define ESP32S2_MAXPARTICLES_1D 1300
#define ESP32S2_MAXSOURCES_1D 32
#define ESP32_MAXPARTICLES_1D 2600
#define ESP32_MAXSOURCES_1D 64
// particle dimensions (subpixel division)
#define PS_P_RADIUS_1D 32 // subpixel size, each pixel is divided by this for particle movement, if this value is changed, also change the shift defines (next two lines)
#define PS_P_HALFRADIUS_1D (PS_P_RADIUS_1D >> 1)
#define PS_P_RADIUS_SHIFT_1D 5 // 1 << PS_P_RADIUS_SHIFT = PS_P_RADIUS
#define PS_P_SURFACE_1D 5 // shift: 2^PS_P_SURFACE = PS_P_RADIUS_1D
#define PS_P_MINHARDRADIUS_1D 32 // minimum hard surface radius note: do not change or hourglass effect will be broken
#define PS_P_MINSURFACEHARDNESS_1D 120 // minimum hardness used in collision impulse calculation
// struct for PS settings (shared for 1D and 2D class)
typedef union {
struct{
// one byte bit field for 1D settings
bool wrap : 1;
bool bounce : 1;
bool killoutofbounds : 1; // if set, out of bound particles are killed immediately
bool useGravity : 1; // set to 1 if gravity is used, disables bounceY at the top
bool useCollisions : 1;
bool colorByAge : 1; // if set, particle hue is set by ttl value in render function
bool colorByPosition : 1; // if set, particle hue is set by its position in the strip segment
bool unused : 1;
};
byte asByte; // access as a byte, order is: LSB is first entry in the list above
} PSsettings1D;
//struct for a single particle (8 bytes)
typedef struct {
int32_t x; // x position in particle system
uint16_t ttl; // time to live in frames
int8_t vx; // horizontal velocity
uint8_t hue; // color hue
} PSparticle1D;
//struct for particle flags
typedef union {
struct { // 1 byte
bool outofbounds : 1; // out of bounds flag, set to true if particle is outside of display area
bool collide : 1; // if set, particle takes part in collisions
bool perpetual : 1; // if set, particle does not age (TTL is not decremented in move function, it still dies from killoutofbounds)
bool reversegrav : 1; // if set, gravity is reversed on this particle
bool forcedirection : 1; // direction the force was applied, 1 is positive x-direction (used for collision stacking, similar to reversegrav) TODO: not used anymore, can be removed
bool fixed : 1; // if set, particle does not move (and collisions make other particles revert direction),
bool custom1 : 1; // unused custom flags, can be used by FX to track particle states
bool custom2 : 1;
};
byte asByte; // access as a byte, order is: LSB is first entry in the list above
} PSparticleFlags1D;
// struct for additional particle settings (optional)
typedef struct {
uint8_t sat; //color saturation
uint8_t size; // particle size, 255 means 10 pixels in diameter
uint8_t forcecounter;
} PSadvancedParticle1D;
//struct for a particle source (20 bytes)
typedef struct {
uint16_t minLife; // minimum ttl of emittet particles
uint16_t maxLife; // maximum ttl of emitted particles
PSparticle1D source; // use a particle as the emitter source (speed, position, color)
PSparticleFlags1D sourceFlags; // flags for the source particle
int8_t var; // variation of emitted speed (adds random(+/- var) to speed)
int8_t v; // emitting speed
uint8_t sat; // color saturation (advanced property)
uint8_t size; // particle size (advanced property)
// note: there is 3 bytes of padding added here
} PSsource1D;
class ParticleSystem1D
{
public:
ParticleSystem1D(const uint32_t length, const uint32_t numberofparticles, const uint32_t numberofsources, const bool isadvanced = false); // constructor
// note: memory is allcated in the FX function, no deconstructor needed
void update(void); //update the particles according to set options and render to the matrix
void updateSystem(void); // call at the beginning of every FX, updates pointers and dimensions
// particle emitters
int32_t sprayEmit(const PSsource1D &emitter);
void particleMoveUpdate(PSparticle1D &part, PSparticleFlags1D &partFlags, PSsettings1D *options = NULL, PSadvancedParticle1D *advancedproperties = NULL); // move function
//particle physics
[[gnu::hot]] void applyForce(PSparticle1D &part, const int8_t xforce, uint8_t &counter); //apply a force to a single particle
void applyForce(const int8_t xforce); // apply a force to all particles
void applyGravity(PSparticle1D &part, PSparticleFlags1D &partFlags); // applies gravity to single particle (use this for sources)
void applyFriction(const int32_t coefficient); // apply friction to all used particles
// set options
void setUsedParticles(const uint8_t percentage); // set the percentage of particles used in the system, 255=100%
inline uint32_t getAvailableParticles(void) { return availableParticles; } // available particles in the buffer, use this to check if buffer changed during FX init
void setWallHardness(const uint8_t hardness); // hardness for bouncing on the wall if bounceXY is set
void setSize(const uint32_t x); //set particle system size (= strip length)
void setWrap(const bool enable);
void setBounce(const bool enable);
void setKillOutOfBounds(const bool enable); // if enabled, particles outside of matrix instantly die
// void setSaturation(uint8_t sat); // set global color saturation
void setColorByAge(const bool enable);
void setColorByPosition(const bool enable);
void setMotionBlur(const uint8_t bluramount); // note: motion blur can only be used if 'particlesize' is set to zero
void setSmearBlur(const uint8_t bluramount); // enable 1D smeared blurring of full frame
void setParticleSize(const uint8_t size); //size 0 = 1 pixel, size 1 = 2 pixels, is overruled by advanced particle size
void setGravity(int8_t force = 8);
void enableParticleCollisions(bool enable, const uint8_t hardness = 255);
PSparticle1D *particles; // pointer to particle array
PSparticleFlags1D *particleFlags; // pointer to particle flags array
PSsource1D *sources; // pointer to sources
PSadvancedParticle1D *advPartProps; // pointer to advanced particle properties (can be NULL)
//PSsizeControl *advPartSize; // pointer to advanced particle size control (can be NULL)
uint8_t* PSdataEnd; // points to first available byte after the PSmemory, is set in setPointers(). use this for FX custom data
int32_t maxX; // particle system size i.e. width-1, Note: all "max" variables must be signed to compare to coordinates (which are signed)
int32_t maxXpixel; // last physical pixel that can be drawn to (FX can read this to read segment size if required), equal to width-1
uint32_t numSources; // number of sources
uint32_t usedParticles; // number of particles used in animation, is relative to 'numParticles'
private:
//rendering functions
void ParticleSys_render(void);
void renderParticle(const uint32_t particleindex, const uint32_t brightness, const CRGB &color, const bool wrap);
//paricle physics applied by system if flags are set
void applyGravity(); // applies gravity to all particles
void handleCollisions();
[[gnu::hot]] void collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, int32_t dx, int32_t relativeVx, const int32_t collisiondistance);
//utility functions
void updatePSpointers(const bool isadvanced); // update the data pointers to current segment data space
//void updateSize(PSadvancedParticle *advprops, PSsizeControl *advsize); // advanced size control
[[gnu::hot]] void bounce(int8_t &incomingspeed, int8_t &parallelspeed, int32_t &position, const uint32_t maxposition); // bounce on a wall
// note: variables that are accessed often are 32bit for speed
PSsettings1D particlesettings; // settings used when updating particles
uint32_t numParticles; // total number of particles allocated by this system note: never use more than this, even if more are available (only this many advanced particles are allocated)
uint32_t availableParticles; // number of particles available for use (can be more or less than numParticles, assigned by memory manager)
uint8_t fractionOfParticlesUsed; // percentage of particles used in the system (255=100%), used during transition updates
uint32_t emitIndex; // index to count through particles to emit so searching for dead pixels is faster
int32_t collisionHardness;
uint32_t particleHardRadius; // hard surface radius of a particle, used for collision detection
uint32_t wallHardness;
uint8_t gforcecounter; // counter for global gravity
int8_t gforce; // gravity strength, default is 8 (negative is allowed, positive is downwards)
uint8_t forcecounter; // counter for globally applied forces
uint16_t collisionStartIdx; // particle array start index for collision detection
//global particle properties for basic particles
uint8_t particlesize; // global particle size, 0 = 1 pixel, 1 = 2 pixels
uint8_t motionBlur; // enable motion blur, values > 100 gives smoother animations
uint8_t smearBlur; // smeared blurring of full frame
uint8_t effectID; // ID of the effect that is using this particle system, used for transitions
uint32_t lastRender; // last time the particles were rendered, intermediate fix for speedup
};
bool initParticleSystem1D(ParticleSystem1D *&PartSys, const uint32_t requestedsources, const uint8_t fractionofparticles = 255, const uint32_t additionalbytes = 0, const bool advanced = false);
uint32_t calculateNumberOfParticles1D(const uint32_t fraction, const bool isadvanced);
uint32_t calculateNumberOfSources1D(const uint32_t requestedsources);
bool allocateParticleSystemMemory1D(const uint32_t numparticles, const uint32_t numsources, const bool isadvanced, const uint32_t additionalbytes);
void blur1D(CRGB *colorbuffer, uint32_t size, uint32_t blur, uint32_t start);
#endif // WLED_DISABLE_PARTICLESYSTEM1D

View File

@ -20,11 +20,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
//long vid = doc[F("vid")]; // 2010020 //long vid = doc[F("vid")]; // 2010020
#ifdef WLED_USE_ETHERNET #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
JsonObject ethernet = doc[F("eth")]; JsonObject ethernet = doc[F("eth")];
CJSON(ethernetType, ethernet["type"]); CJSON(ethernetType, ethernet["type"]);
// NOTE: Ethernet configuration takes priority over other use of pins // NOTE: Ethernet configuration takes priority over other use of pins
WLED::instance().initEthernet(); initEthernet();
#endif #endif
JsonObject id = doc["id"]; JsonObject id = doc["id"];
@ -53,9 +53,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
JsonArray sn = wifi["sn"]; JsonArray sn = wifi["sn"];
char ssid[33] = ""; char ssid[33] = "";
char pass[65] = ""; char pass[65] = "";
char bssid[13] = "";
IPAddress nIP = (uint32_t)0U, nGW = (uint32_t)0U, nSN = (uint32_t)0x00FFFFFF; // little endian IPAddress nIP = (uint32_t)0U, nGW = (uint32_t)0U, nSN = (uint32_t)0x00FFFFFF; // little endian
getStringFromJson(ssid, wifi[F("ssid")], 33); getStringFromJson(ssid, wifi[F("ssid")], 33);
getStringFromJson(pass, wifi["psk"], 65); // password is not normally present but if it is, use it getStringFromJson(pass, wifi["psk"], 65); // password is not normally present but if it is, use it
getStringFromJson(bssid, wifi[F("bssid")], 13);
for (size_t i = 0; i < 4; i++) { for (size_t i = 0; i < 4; i++) {
CJSON(nIP[i], ip[i]); CJSON(nIP[i], ip[i]);
CJSON(nGW[i], gw[i]); CJSON(nGW[i], gw[i]);
@ -63,6 +65,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
} }
if (strlen(ssid) > 0) strlcpy(multiWiFi[n].clientSSID, ssid, 33); // this will keep old SSID intact if not present in JSON if (strlen(ssid) > 0) strlcpy(multiWiFi[n].clientSSID, ssid, 33); // this will keep old SSID intact if not present in JSON
if (strlen(pass) > 0) strlcpy(multiWiFi[n].clientPass, pass, 65); // this will keep old password intact if not present in JSON if (strlen(pass) > 0) strlcpy(multiWiFi[n].clientPass, pass, 65); // this will keep old password intact if not present in JSON
if (strlen(bssid) > 0) fillStr2MAC(multiWiFi[n].bssid, bssid);
multiWiFi[n].staticIP = nIP; multiWiFi[n].staticIP = nIP;
multiWiFi[n].staticGW = nGW; multiWiFi[n].staticGW = nGW;
multiWiFi[n].staticSN = nSN; multiWiFi[n].staticSN = nSN;
@ -669,8 +672,8 @@ void deserializeConfigFromFS() {
UsermodManager::readFromConfig(empty); UsermodManager::readFromConfig(empty);
serializeConfig(); serializeConfig();
// init Ethernet (in case default type is set at compile time) // init Ethernet (in case default type is set at compile time)
#ifdef WLED_USE_ETHERNET #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
WLED::instance().initEthernet(); initEthernet();
#endif #endif
return; return;
} }
@ -718,6 +721,9 @@ void serializeConfig() {
JsonObject wifi = nw_ins.createNestedObject(); JsonObject wifi = nw_ins.createNestedObject();
wifi[F("ssid")] = multiWiFi[n].clientSSID; wifi[F("ssid")] = multiWiFi[n].clientSSID;
wifi[F("pskl")] = strlen(multiWiFi[n].clientPass); wifi[F("pskl")] = strlen(multiWiFi[n].clientPass);
char bssid[13];
fillMAC2Str(bssid, multiWiFi[n].bssid);
wifi[F("bssid")] = bssid;
JsonArray wifi_ip = wifi.createNestedArray("ip"); JsonArray wifi_ip = wifi.createNestedArray("ip");
JsonArray wifi_gw = wifi.createNestedArray("gw"); JsonArray wifi_gw = wifi.createNestedArray("gw");
JsonArray wifi_sn = wifi.createNestedArray("sn"); JsonArray wifi_sn = wifi.createNestedArray("sn");
@ -753,7 +759,7 @@ void serializeConfig() {
wifi[F("txpwr")] = txPower; wifi[F("txpwr")] = txPower;
#endif #endif
#ifdef WLED_USE_ETHERNET #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
JsonObject ethernet = root.createNestedObject("eth"); JsonObject ethernet = root.createNestedObject("eth");
ethernet["type"] = ethernetType; ethernet["type"] = ethernetType;
if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) {
@ -1210,4 +1216,4 @@ void serializeConfigSec() {
if (f) serializeJson(root, f); if (f) serializeJson(root, f);
f.close(); f.close();
releaseJSONBufferLock(); releaseJSONBufferLock();
} }

View File

@ -47,7 +47,7 @@
scanLoops = 0; scanLoops = 0;
if (networks.length > 0) { if (networks.length > 0) {
let cs = d.querySelectorAll("#wifi_entries input[type=text]"); let cs = d.querySelectorAll("#wifi_entries input[type=text][name^=CS]");
for (let input of (cs||[])) { for (let input of (cs||[])) {
let found = false; let found = false;
let select = cE("select"); let select = cE("select");
@ -64,7 +64,7 @@
const option = cE("option"); const option = cE("option");
option.setAttribute("value", networks[i].ssid); option.setAttribute("value", networks[i].ssid);
option.textContent = `${networks[i].ssid} (${networks[i].rssi} dBm)`; option.textContent = `${networks[i].ssid} (${networks[i].rssi} dBm)`; // [${networks[i].bssid.replaceAll(':','')}]
if (networks[i].ssid === input.value) { if (networks[i].ssid === input.value) {
option.setAttribute("selected", "selected"); option.setAttribute("selected", "selected");
@ -109,12 +109,13 @@
gId("wifi_add").style.display = (i<maxNetworks) ? "inline":"none"; gId("wifi_add").style.display = (i<maxNetworks) ? "inline":"none";
gId("wifi_rem").style.display = (i>1) ? "inline":"none"; gId("wifi_rem").style.display = (i>1) ? "inline":"none";
} }
function addWiFi(ssid="",pass="",ip=0,gw=0,sn=0x00ffffff) { // little endian function addWiFi(ssid="",pass="",bssid="",ip=0,gw=0,sn=0x00ffffff) { // little endian
var i = gId("wifi_entries").childNodes.length; var i = gId("wifi_entries").childNodes.length;
if (i >= maxNetworks) return; if (i >= maxNetworks) return;
var b = `<div id="net${i}"><hr class="sml"> var b = `<div id="net${i}"><hr class="sml">
Network name (SSID${i==0?", empty to not connect":""}):<br><input type="text" id="CS${i}" name="CS${i}" maxlength="32" value="${ssid}" ${i>0?"required":""}><br> Network name (SSID${i==0?", empty to not connect":""}):<br><input type="text" id="CS${i}" name="CS${i}" maxlength="32" value="${ssid}" ${i>0?"required":""}><br>
Network password:<br><input type="password" name="PW${i}" maxlength="64" value="${pass}"><br> Network password:<br><input type="password" name="PW${i}" maxlength="64" value="${pass}"><br>
BSSID (optional):<br><input type="text" id="BS${i}" name="BS${i}" maxlength="12" value="${bssid}"><br>
Static IP (leave at 0.0.0.0 for DHCP)${i==0?"<br>Also used by Ethernet":""}:<br> Static IP (leave at 0.0.0.0 for DHCP)${i==0?"<br>Also used by Ethernet":""}:<br>
<input name="IP${i}0" type="number" class="s" min="0" max="255" value="${ip&0xFF}" required>.<input name="IP${i}1" type="number" class="s" min="0" max="255" value="${(ip>>8)&0xFF}" required>.<input name="IP${i}2" type="number" class="s" min="0" max="255" value="${(ip>>16)&0xFF}" required>.<input name="IP${i}3" type="number" class="s" min="0" max="255" value="${(ip>>24)&0xFF}" required><br> <input name="IP${i}0" type="number" class="s" min="0" max="255" value="${ip&0xFF}" required>.<input name="IP${i}1" type="number" class="s" min="0" max="255" value="${(ip>>8)&0xFF}" required>.<input name="IP${i}2" type="number" class="s" min="0" max="255" value="${(ip>>16)&0xFF}" required>.<input name="IP${i}3" type="number" class="s" min="0" max="255" value="${(ip>>24)&0xFF}" required><br>
Static gateway:<br> Static gateway:<br>

View File

@ -53,6 +53,7 @@ bool getJsonValue(const JsonVariant& element, DestType& destination, const Defau
typedef struct WiFiConfig { typedef struct WiFiConfig {
char clientSSID[33]; char clientSSID[33];
char clientPass[65]; char clientPass[65];
uint8_t bssid[6];
IPAddress staticIP; IPAddress staticIP;
IPAddress staticGW; IPAddress staticGW;
IPAddress staticSN; IPAddress staticSN;
@ -63,6 +64,7 @@ typedef struct WiFiConfig {
{ {
strncpy(clientSSID, ssid, 32); clientSSID[32] = 0; strncpy(clientSSID, ssid, 32); clientSSID[32] = 0;
strncpy(clientPass, pass, 64); clientPass[64] = 0; strncpy(clientPass, pass, 64); clientPass[64] = 0;
memset(bssid, 0, sizeof(bssid));
} }
} wifi_config; } wifi_config;
@ -203,14 +205,14 @@ void sendArtnetPollReply(ArtPollReply* reply, IPAddress ipAddress, uint16_t port
bool handleFileRead(AsyncWebServerRequest*, String path); bool handleFileRead(AsyncWebServerRequest*, String path);
bool writeObjectToFileUsingId(const char* file, uint16_t id, const JsonDocument* content); bool writeObjectToFileUsingId(const char* file, uint16_t id, const JsonDocument* content);
bool writeObjectToFile(const char* file, const char* key, const JsonDocument* content); bool writeObjectToFile(const char* file, const char* key, const JsonDocument* content);
bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest); bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest, const JsonDocument* filter = nullptr);
bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest); bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest, const JsonDocument* filter = nullptr);
void updateFSInfo(); void updateFSInfo();
void closeFile(); void closeFile();
inline bool writeObjectToFileUsingId(const String &file, uint16_t id, const JsonDocument* content) { return writeObjectToFileUsingId(file.c_str(), id, content); }; 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 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) { return readObjectFromFileUsingId(file.c_str(), id, dest); }; 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) { return readObjectFromFile(file.c_str(), key, dest); }; inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest, const JsonDocument* filter = nullptr) { return readObjectFromFile(file.c_str(), key, dest); };
//hue.cpp //hue.cpp
void handleHue(); void handleHue();
@ -360,7 +362,12 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs
#endif #endif
//network.cpp //network.cpp
int getSignalQuality(int rssi); bool initEthernet(); // result is informational
int getSignalQuality(int rssi);
void fillMAC2Str(char *str, const uint8_t *mac);
void fillStr2MAC(uint8_t *mac, const char *str);
int findWiFi(bool doScan = false);
bool isWiFiConfigured();
void WiFiEvent(WiFiEvent_t event); void WiFiEvent(WiFiEvent_t event);
//um_manager.cpp //um_manager.cpp
@ -478,6 +485,7 @@ void userLoop();
#include "soc/wdev_reg.h" #include "soc/wdev_reg.h"
#define HW_RND_REGISTER REG_READ(WDEV_RND_REG) #define HW_RND_REGISTER REG_READ(WDEV_RND_REG)
#endif #endif
#define hex2int(a) (((a)>='0' && (a)<='9') ? (a)-'0' : ((a)>='A' && (a)<='F') ? (a)-'A'+10 : ((a)>='a' && (a)<='f') ? (a)-'a'+10 : 0)
[[gnu::pure]] int getNumVal(const String* req, uint16_t pos); [[gnu::pure]] int getNumVal(const String* req, uint16_t pos);
void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255);
bool getVal(JsonVariant elem, byte* val, byte vmin=0, byte vmax=255); // getVal supports inc/decrementing and random ("X~Y(r|[w]~[-][Z])" form) bool getVal(JsonVariant elem, byte* val, byte vmin=0, byte vmax=255); // getVal supports inc/decrementing and random ("X~Y(r|[w]~[-][Z])" form)

View File

@ -325,15 +325,15 @@ bool writeObjectToFile(const char* file, const char* key, const JsonDocument* co
return true; return true;
} }
bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest) bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest, const JsonDocument* filter)
{ {
char objKey[10]; char objKey[10];
sprintf(objKey, "\"%d\":", id); sprintf(objKey, "\"%d\":", id);
return readObjectFromFile(file, objKey, dest); return readObjectFromFile(file, objKey, dest, filter);
} }
//if the key is a nullptr, deserialize entire object //if the key is a nullptr, deserialize entire object
bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest) bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest, const JsonDocument* filter)
{ {
if (doCloseFile) closeFile(); if (doCloseFile) closeFile();
#ifdef WLED_DEBUG_FS #ifdef WLED_DEBUG_FS
@ -352,7 +352,8 @@ bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest)
return false; return false;
} }
deserializeJson(*dest, f); if (filter) deserializeJson(*dest, f, DeserializationOption::Filter(*filter));
else deserializeJson(*dest, f);
f.close(); f.close();
DEBUGFS_PRINTF("Read, took %d ms\n", millis() - s); DEBUGFS_PRINTF("Read, took %d ms\n", millis() - s);

View File

@ -3,7 +3,7 @@
#include "wled_ethernet.h" #include "wled_ethernet.h"
#ifdef WLED_USE_ETHERNET #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
// The following six pins are neither configurable nor // The following six pins are neither configurable nor
// can they be re-assigned through IOMUX / GPIO matrix. // can they be re-assigned through IOMUX / GPIO matrix.
// See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-ethernet-kit-v1.1.html#ip101gri-phy-interface // See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-ethernet-kit-v1.1.html#ip101gri-phy-interface
@ -146,6 +146,101 @@ const ethernet_settings ethernetBoards[] = {
ETH_CLOCK_GPIO0_OUT // eth_clk_mode ETH_CLOCK_GPIO0_OUT // eth_clk_mode
} }
}; };
bool initEthernet()
{
static bool successfullyConfiguredEthernet = false;
if (successfullyConfiguredEthernet) {
// DEBUG_PRINTLN(F("initE: ETH already successfully configured, ignoring"));
return false;
}
if (ethernetType == WLED_ETH_NONE) {
return false;
}
if (ethernetType >= WLED_NUM_ETH_TYPES) {
DEBUG_PRINTF_P(PSTR("initE: Ignoring attempt for invalid ethernetType (%d)\n"), ethernetType);
return false;
}
DEBUG_PRINTF_P(PSTR("initE: Attempting ETH config: %d\n"), ethernetType);
// Ethernet initialization should only succeed once -- else reboot required
ethernet_settings es = ethernetBoards[ethernetType];
managed_pin_type pinsToAllocate[10] = {
// first six pins are non-configurable
esp32_nonconfigurable_ethernet_pins[0],
esp32_nonconfigurable_ethernet_pins[1],
esp32_nonconfigurable_ethernet_pins[2],
esp32_nonconfigurable_ethernet_pins[3],
esp32_nonconfigurable_ethernet_pins[4],
esp32_nonconfigurable_ethernet_pins[5],
{ (int8_t)es.eth_mdc, true }, // [6] = MDC is output and mandatory
{ (int8_t)es.eth_mdio, true }, // [7] = MDIO is bidirectional and mandatory
{ (int8_t)es.eth_power, true }, // [8] = optional pin, not all boards use
{ ((int8_t)0xFE), false }, // [9] = replaced with eth_clk_mode, mandatory
};
// update the clock pin....
if (es.eth_clk_mode == ETH_CLOCK_GPIO0_IN) {
pinsToAllocate[9].pin = 0;
pinsToAllocate[9].isOutput = false;
} else if (es.eth_clk_mode == ETH_CLOCK_GPIO0_OUT) {
pinsToAllocate[9].pin = 0;
pinsToAllocate[9].isOutput = true;
} else if (es.eth_clk_mode == ETH_CLOCK_GPIO16_OUT) {
pinsToAllocate[9].pin = 16;
pinsToAllocate[9].isOutput = true;
} else if (es.eth_clk_mode == ETH_CLOCK_GPIO17_OUT) {
pinsToAllocate[9].pin = 17;
pinsToAllocate[9].isOutput = true;
} else {
DEBUG_PRINTF_P(PSTR("initE: Failing due to invalid eth_clk_mode (%d)\n"), es.eth_clk_mode);
return false;
}
if (!PinManager::allocateMultiplePins(pinsToAllocate, 10, PinOwner::Ethernet)) {
DEBUG_PRINTLN(F("initE: Failed to allocate ethernet pins"));
return false;
}
/*
For LAN8720 the most correct way is to perform clean reset each time before init
applying LOW to power or nRST pin for at least 100 us (please refer to datasheet, page 59)
ESP_IDF > V4 implements it (150 us, lan87xx_reset_hw(esp_eth_phy_t *phy) function in
/components/esp_eth/src/esp_eth_phy_lan87xx.c, line 280)
but ESP_IDF < V4 does not. Lets do it:
[not always needed, might be relevant in some EMI situations at startup and for hot resets]
*/
#if ESP_IDF_VERSION_MAJOR==3
if(es.eth_power>0 && es.eth_type==ETH_PHY_LAN8720) {
pinMode(es.eth_power, OUTPUT);
digitalWrite(es.eth_power, 0);
delayMicroseconds(150);
digitalWrite(es.eth_power, 1);
delayMicroseconds(10);
}
#endif
if (!ETH.begin(
(uint8_t) es.eth_address,
(int) es.eth_power,
(int) es.eth_mdc,
(int) es.eth_mdio,
(eth_phy_type_t) es.eth_type,
(eth_clock_mode_t) es.eth_clk_mode
)) {
DEBUG_PRINTLN(F("initC: ETH.begin() failed"));
// de-allocate the allocated pins
for (managed_pin_type mpt : pinsToAllocate) {
PinManager::deallocatePin(mpt.pin, PinOwner::Ethernet);
}
return false;
}
successfullyConfiguredEthernet = true;
DEBUG_PRINTLN(F("initC: *** Ethernet successfully configured! ***"));
return true;
}
#endif #endif
@ -170,19 +265,136 @@ int getSignalQuality(int rssi)
} }
void fillMAC2Str(char *str, const uint8_t *mac) {
sprintf_P(str, PSTR("%02x%02x%02x%02x%02x%02x"), MAC2STR(mac));
byte nul = 0;
for (int i = 0; i < 6; i++) nul |= *mac++; // do we have 0
if (!nul) str[0] = '\0'; // empty string
}
void fillStr2MAC(uint8_t *mac, const char *str) {
for (int i = 0; i < 6; i++) *mac++ = 0; // clear
if (!str) return; // null string
uint64_t MAC = strtoull(str, nullptr, 16);
for (int i = 0; i < 6; i++) { *--mac = MAC & 0xFF; MAC >>= 8; }
}
// performs asynchronous scan for available networks (which may take couple of seconds to finish)
// returns configured WiFi ID with the strongest signal (or default if no configured networks available)
int findWiFi(bool doScan) {
if (multiWiFi.size() <= 1) {
DEBUG_PRINTF_P(PSTR("WiFi: Defaulf SSID (%s) used.\n"), multiWiFi[0].clientSSID);
return 0;
}
int status = WiFi.scanComplete(); // complete scan may take as much as several seconds (usually <6s with not very crowded air)
if (doScan || status == WIFI_SCAN_FAILED) {
DEBUG_PRINTF_P(PSTR("WiFi: Scan started. @ %lus\n"), millis()/1000);
WiFi.scanNetworks(true); // start scanning in asynchronous mode (will delete old scan)
} else if (status >= 0) { // status contains number of found networks (including duplicate SSIDs with different BSSID)
DEBUG_PRINTF_P(PSTR("WiFi: Found %d SSIDs. @ %lus\n"), status, millis()/1000);
int rssi = -9999;
int selected = selectedWiFi;
for (int o = 0; o < status; o++) {
DEBUG_PRINTF_P(PSTR(" SSID: %s (BSSID: %s) RSSI: %ddB\n"), WiFi.SSID(o).c_str(), WiFi.BSSIDstr(o).c_str(), WiFi.RSSI(o));
for (unsigned n = 0; n < multiWiFi.size(); n++)
if (!strcmp(WiFi.SSID(o).c_str(), multiWiFi[n].clientSSID)) {
bool foundBSSID = memcmp(multiWiFi[n].bssid, WiFi.BSSID(o), 6) == 0;
// find the WiFi with the strongest signal (but keep priority of entry if signal difference is not big)
if (foundBSSID || (n < selected && WiFi.RSSI(o) > rssi-10) || WiFi.RSSI(o) > rssi) {
rssi = foundBSSID ? 0 : WiFi.RSSI(o); // RSSI is only ever negative
selected = n;
}
break;
}
}
DEBUG_PRINTF_P(PSTR("WiFi: Selected SSID: %s RSSI: %ddB\n"), multiWiFi[selected].clientSSID, rssi);
return selected;
}
//DEBUG_PRINT(F("WiFi scan running."));
return status; // scan is still running or there was an error
}
bool isWiFiConfigured() {
return multiWiFi.size() > 1 || (strlen(multiWiFi[0].clientSSID) >= 1 && strcmp_P(multiWiFi[0].clientSSID, PSTR(DEFAULT_CLIENT_SSID)) != 0);
}
#if defined(ESP8266)
#define ARDUINO_EVENT_WIFI_AP_STADISCONNECTED WIFI_EVENT_SOFTAPMODE_STADISCONNECTED
#define ARDUINO_EVENT_WIFI_AP_STACONNECTED WIFI_EVENT_SOFTAPMODE_STACONNECTED
#define ARDUINO_EVENT_WIFI_STA_GOT_IP WIFI_EVENT_STAMODE_GOT_IP
#define ARDUINO_EVENT_WIFI_STA_CONNECTED WIFI_EVENT_STAMODE_CONNECTED
#define ARDUINO_EVENT_WIFI_STA_DISCONNECTED WIFI_EVENT_STAMODE_DISCONNECTED
#elif defined(ARDUINO_ARCH_ESP32) && !defined(ESP_ARDUINO_VERSION_MAJOR) //ESP_IDF_VERSION_MAJOR==3
// not strictly IDF v3 but Arduino core related
#define ARDUINO_EVENT_WIFI_AP_STADISCONNECTED SYSTEM_EVENT_AP_STADISCONNECTED
#define ARDUINO_EVENT_WIFI_AP_STACONNECTED SYSTEM_EVENT_AP_STACONNECTED
#define ARDUINO_EVENT_WIFI_STA_GOT_IP SYSTEM_EVENT_STA_GOT_IP
#define ARDUINO_EVENT_WIFI_STA_CONNECTED SYSTEM_EVENT_STA_CONNECTED
#define ARDUINO_EVENT_WIFI_STA_DISCONNECTED SYSTEM_EVENT_STA_DISCONNECTED
#define ARDUINO_EVENT_WIFI_AP_START SYSTEM_EVENT_AP_START
#define ARDUINO_EVENT_WIFI_AP_STOP SYSTEM_EVENT_AP_STOP
#define ARDUINO_EVENT_WIFI_SCAN_DONE SYSTEM_EVENT_SCAN_DONE
#define ARDUINO_EVENT_ETH_START SYSTEM_EVENT_ETH_START
#define ARDUINO_EVENT_ETH_CONNECTED SYSTEM_EVENT_ETH_CONNECTED
#define ARDUINO_EVENT_ETH_DISCONNECTED SYSTEM_EVENT_ETH_DISCONNECTED
#endif
//handle Ethernet connection event //handle Ethernet connection event
void WiFiEvent(WiFiEvent_t event) void WiFiEvent(WiFiEvent_t event)
{ {
switch (event) { switch (event) {
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
case SYSTEM_EVENT_ETH_START: // AP client disconnected
DEBUG_PRINTLN(F("ETH Started")); if (--apClients == 0 && isWiFiConfigured()) forceReconnect = true; // no clients reconnect WiFi if awailable
DEBUG_PRINTF_P(PSTR("WiFi-E: AP Client Disconnected (%d) @ %lus.\n"), (int)apClients, millis()/1000);
break; break;
case SYSTEM_EVENT_ETH_CONNECTED: case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
// AP client connected
apClients++;
DEBUG_PRINTF_P(PSTR("WiFi-E: AP Client Connected (%d) @ %lus.\n"), (int)apClients, millis()/1000);
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
DEBUG_PRINT(F("WiFi-E: IP address: ")); DEBUG_PRINTLN(Network.localIP());
break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
// followed by IDLE and SCAN_DONE
DEBUG_PRINTF_P(PSTR("WiFi-E: Connected! @ %lus\n"), millis()/1000);
wasConnected = true;
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
if (wasConnected && interfacesInited) {
DEBUG_PRINTF_P(PSTR("WiFi-E: Disconnected! @ %lus\n"), millis()/1000);
if (interfacesInited && multiWiFi.size() > 1 && WiFi.scanComplete() >= 0) {
findWiFi(true); // reinit WiFi scan
forceReconnect = true;
}
interfacesInited = false;
}
break;
#ifdef ARDUINO_ARCH_ESP32
case ARDUINO_EVENT_WIFI_SCAN_DONE:
// also triggered when connected to selected SSID
DEBUG_PRINTLN(F("WiFi-E: SSID scan completed."));
break;
case ARDUINO_EVENT_WIFI_AP_START:
DEBUG_PRINTLN(F("WiFi-E: AP Started"));
break;
case ARDUINO_EVENT_WIFI_AP_STOP:
DEBUG_PRINTLN(F("WiFi-E: AP Stopped"));
break;
#if defined(WLED_USE_ETHERNET)
case ARDUINO_EVENT_ETH_START:
DEBUG_PRINTLN(F("ETH-E: Started"));
break;
case ARDUINO_EVENT_ETH_CONNECTED:
{ {
DEBUG_PRINTLN(F("ETH Connected")); DEBUG_PRINTLN(F("ETH-E: Connected"));
if (!apActive) { if (!apActive) {
WiFi.disconnect(true); WiFi.disconnect(true); // disable WiFi entirely
} }
if (multiWiFi[0].staticIP != (uint32_t)0x00000000 && multiWiFi[0].staticGW != (uint32_t)0x00000000) { if (multiWiFi[0].staticIP != (uint32_t)0x00000000 && multiWiFi[0].staticGW != (uint32_t)0x00000000) {
ETH.config(multiWiFi[0].staticIP, multiWiFi[0].staticGW, multiWiFi[0].staticSN, dnsAddress); ETH.config(multiWiFi[0].staticIP, multiWiFi[0].staticGW, multiWiFi[0].staticSN, dnsAddress);
@ -196,18 +408,20 @@ void WiFiEvent(WiFiEvent_t event)
showWelcomePage = false; showWelcomePage = false;
break; break;
} }
case SYSTEM_EVENT_ETH_DISCONNECTED: case ARDUINO_EVENT_ETH_DISCONNECTED:
DEBUG_PRINTLN(F("ETH Disconnected")); DEBUG_PRINTLN(F("ETH-E: Disconnected"));
// This doesn't really affect ethernet per se, // This doesn't really affect ethernet per se,
// as it's only configured once. Rather, it // as it's only configured once. Rather, it
// may be necessary to reconnect the WiFi when // may be necessary to reconnect the WiFi when
// ethernet disconnects, as a way to provide // ethernet disconnects, as a way to provide
// alternative access to the device. // alternative access to the device.
if (interfacesInited && WiFi.scanComplete() >= 0) findWiFi(true); // reinit WiFi scan
forceReconnect = true; forceReconnect = true;
break; break;
#endif #endif
#endif
default: default:
DEBUG_PRINTF_P(PSTR("Network event: %d\n"), (int)event); DEBUG_PRINTF_P(PSTR("WiFi-E: Event %d\n"), (int)event);
break; break;
} }
} }

View File

@ -23,6 +23,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
for (size_t n = 0; n < WLED_MAX_WIFI_COUNT; n++) { for (size_t n = 0; n < WLED_MAX_WIFI_COUNT; n++) {
char cs[4] = "CS"; cs[2] = 48+n; cs[3] = 0; //client SSID char cs[4] = "CS"; cs[2] = 48+n; cs[3] = 0; //client SSID
char pw[4] = "PW"; pw[2] = 48+n; pw[3] = 0; //client password char pw[4] = "PW"; pw[2] = 48+n; pw[3] = 0; //client password
char bs[4] = "BS"; bs[2] = 48+n; bs[3] = 0; //BSSID
char ip[5] = "IP"; ip[2] = 48+n; ip[4] = 0; //IP address char ip[5] = "IP"; ip[2] = 48+n; ip[4] = 0; //IP address
char gw[5] = "GW"; gw[2] = 48+n; gw[4] = 0; //GW address char gw[5] = "GW"; gw[2] = 48+n; gw[4] = 0; //GW address
char sn[5] = "SN"; sn[2] = 48+n; sn[4] = 0; //subnet mask char sn[5] = "SN"; sn[2] = 48+n; sn[4] = 0; //subnet mask
@ -39,6 +40,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strlcpy(multiWiFi[n].clientPass, request->arg(pw).c_str(), 65); strlcpy(multiWiFi[n].clientPass, request->arg(pw).c_str(), 65);
forceReconnect = true; forceReconnect = true;
} }
fillStr2MAC(multiWiFi[n].bssid, request->arg(bs).c_str());
for (size_t i = 0; i < 4; i++) { for (size_t i = 0; i < 4; i++) {
ip[3] = 48+i; ip[3] = 48+i;
gw[3] = 48+i; gw[3] = 48+i;
@ -93,9 +95,9 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strlwr(linked_remote); //Normalize MAC format to lowercase strlwr(linked_remote); //Normalize MAC format to lowercase
#endif #endif
#ifdef WLED_USE_ETHERNET #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
ethernetType = request->arg(F("ETH")).toInt(); ethernetType = request->arg(F("ETH")).toInt();
WLED::instance().initEthernet(); initEthernet();
#endif #endif
} }

View File

@ -530,6 +530,8 @@ um_data_t* simulateSound(uint8_t simulationId)
static const char s_ledmap_tmpl[] PROGMEM = "ledmap%d.json"; static const char s_ledmap_tmpl[] PROGMEM = "ledmap%d.json";
// enumerate all ledmapX.json files on FS and extract ledmap names if existing // enumerate all ledmapX.json files on FS and extract ledmap names if existing
void enumerateLedmaps() { void enumerateLedmaps() {
StaticJsonDocument<64> filter;
filter["n"] = true;
ledMaps = 1; ledMaps = 1;
for (size_t i=1; i<WLED_MAX_LEDMAPS; i++) { for (size_t i=1; i<WLED_MAX_LEDMAPS; i++) {
char fileName[33] = "/"; char fileName[33] = "/";
@ -548,7 +550,7 @@ void enumerateLedmaps() {
#ifndef ESP8266 #ifndef ESP8266
if (requestJSONBufferLock(21)) { if (requestJSONBufferLock(21)) {
if (readObjectFromFile(fileName, nullptr, pDoc)) { if (readObjectFromFile(fileName, nullptr, pDoc, &filter)) {
size_t len = 0; size_t len = 0;
JsonObject root = pDoc->as<JsonObject>(); JsonObject root = pDoc->as<JsonObject>();
if (!root["n"].isNull()) { if (!root["n"].isNull()) {

View File

@ -601,146 +601,6 @@ void WLED::initAP(bool resetAP)
apActive = true; apActive = true;
} }
bool WLED::initEthernet()
{
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
static bool successfullyConfiguredEthernet = false;
if (successfullyConfiguredEthernet) {
// DEBUG_PRINTLN(F("initE: ETH already successfully configured, ignoring"));
return false;
}
if (ethernetType == WLED_ETH_NONE) {
return false;
}
if (ethernetType >= WLED_NUM_ETH_TYPES) {
DEBUG_PRINTF_P(PSTR("initE: Ignoring attempt for invalid ethernetType (%d)\n"), ethernetType);
return false;
}
DEBUG_PRINTF_P(PSTR("initE: Attempting ETH config: %d\n"), ethernetType);
// Ethernet initialization should only succeed once -- else reboot required
ethernet_settings es = ethernetBoards[ethernetType];
managed_pin_type pinsToAllocate[10] = {
// first six pins are non-configurable
esp32_nonconfigurable_ethernet_pins[0],
esp32_nonconfigurable_ethernet_pins[1],
esp32_nonconfigurable_ethernet_pins[2],
esp32_nonconfigurable_ethernet_pins[3],
esp32_nonconfigurable_ethernet_pins[4],
esp32_nonconfigurable_ethernet_pins[5],
{ (int8_t)es.eth_mdc, true }, // [6] = MDC is output and mandatory
{ (int8_t)es.eth_mdio, true }, // [7] = MDIO is bidirectional and mandatory
{ (int8_t)es.eth_power, true }, // [8] = optional pin, not all boards use
{ ((int8_t)0xFE), false }, // [9] = replaced with eth_clk_mode, mandatory
};
// update the clock pin....
if (es.eth_clk_mode == ETH_CLOCK_GPIO0_IN) {
pinsToAllocate[9].pin = 0;
pinsToAllocate[9].isOutput = false;
} else if (es.eth_clk_mode == ETH_CLOCK_GPIO0_OUT) {
pinsToAllocate[9].pin = 0;
pinsToAllocate[9].isOutput = true;
} else if (es.eth_clk_mode == ETH_CLOCK_GPIO16_OUT) {
pinsToAllocate[9].pin = 16;
pinsToAllocate[9].isOutput = true;
} else if (es.eth_clk_mode == ETH_CLOCK_GPIO17_OUT) {
pinsToAllocate[9].pin = 17;
pinsToAllocate[9].isOutput = true;
} else {
DEBUG_PRINTF_P(PSTR("initE: Failing due to invalid eth_clk_mode (%d)\n"), es.eth_clk_mode);
return false;
}
if (!PinManager::allocateMultiplePins(pinsToAllocate, 10, PinOwner::Ethernet)) {
DEBUG_PRINTLN(F("initE: Failed to allocate ethernet pins"));
return false;
}
/*
For LAN8720 the most correct way is to perform clean reset each time before init
applying LOW to power or nRST pin for at least 100 us (please refer to datasheet, page 59)
ESP_IDF > V4 implements it (150 us, lan87xx_reset_hw(esp_eth_phy_t *phy) function in
/components/esp_eth/src/esp_eth_phy_lan87xx.c, line 280)
but ESP_IDF < V4 does not. Lets do it:
[not always needed, might be relevant in some EMI situations at startup and for hot resets]
*/
#if ESP_IDF_VERSION_MAJOR==3
if(es.eth_power>0 && es.eth_type==ETH_PHY_LAN8720) {
pinMode(es.eth_power, OUTPUT);
digitalWrite(es.eth_power, 0);
delayMicroseconds(150);
digitalWrite(es.eth_power, 1);
delayMicroseconds(10);
}
#endif
if (!ETH.begin(
(uint8_t) es.eth_address,
(int) es.eth_power,
(int) es.eth_mdc,
(int) es.eth_mdio,
(eth_phy_type_t) es.eth_type,
(eth_clock_mode_t) es.eth_clk_mode
)) {
DEBUG_PRINTLN(F("initC: ETH.begin() failed"));
// de-allocate the allocated pins
for (managed_pin_type mpt : pinsToAllocate) {
PinManager::deallocatePin(mpt.pin, PinOwner::Ethernet);
}
return false;
}
successfullyConfiguredEthernet = true;
DEBUG_PRINTLN(F("initC: *** Ethernet successfully configured! ***"));
return true;
#else
return false; // Ethernet not enabled for build
#endif
}
// performs asynchronous scan for available networks (which may take couple of seconds to finish)
// returns configured WiFi ID with the strongest signal (or default if no configured networks available)
int8_t WLED::findWiFi(bool doScan) {
if (multiWiFi.size() <= 1) {
DEBUG_PRINTLN(F("Defaulf WiFi used."));
return 0;
}
if (doScan) WiFi.scanDelete(); // restart scan
int status = WiFi.scanComplete(); // complete scan may take as much as several seconds (usually <3s with not very crowded air)
if (status == WIFI_SCAN_FAILED) {
DEBUG_PRINTLN(F("WiFi scan started."));
WiFi.scanNetworks(true); // start scanning in asynchronous mode
} else if (status >= 0) { // status contains number of found networks
DEBUG_PRINT(F("WiFi scan completed: ")); DEBUG_PRINTLN(status);
int rssi = -9999;
unsigned selected = selectedWiFi;
for (int o = 0; o < status; o++) {
DEBUG_PRINT(F(" WiFi available: ")); DEBUG_PRINT(WiFi.SSID(o));
DEBUG_PRINT(F(" RSSI: ")); DEBUG_PRINT(WiFi.RSSI(o)); DEBUG_PRINTLN(F("dB"));
for (unsigned n = 0; n < multiWiFi.size(); n++)
if (!strcmp(WiFi.SSID(o).c_str(), multiWiFi[n].clientSSID)) {
// find the WiFi with the strongest signal (but keep priority of entry if signal difference is not big)
if ((n < selected && WiFi.RSSI(o) > rssi-10) || WiFi.RSSI(o) > rssi) {
rssi = WiFi.RSSI(o);
selected = n;
}
break;
}
}
DEBUG_PRINT(F("Selected: ")); DEBUG_PRINT(multiWiFi[selected].clientSSID);
DEBUG_PRINT(F(" RSSI: ")); DEBUG_PRINT(rssi); DEBUG_PRINTLN(F("dB"));
return selected;
}
//DEBUG_PRINT(F("WiFi scan running."));
return status; // scan is still running or there was an error
}
void WLED::initConnection() void WLED::initConnection()
{ {
DEBUG_PRINTF_P(PSTR("initConnection() called @ %lus.\n"), millis()/1000); DEBUG_PRINTF_P(PSTR("initConnection() called @ %lus.\n"), millis()/1000);

View File

@ -359,7 +359,7 @@ WLED_GLOBAL wifi_options_t wifiOpt _INIT_N(({0, 1, false, AP_BEHAVIOR_BOOT_NO_CO
#define noWifiSleep wifiOpt.noWifiSleep #define noWifiSleep wifiOpt.noWifiSleep
#define force802_3g wifiOpt.force802_3g #define force802_3g wifiOpt.force802_3g
#else #else
WLED_GLOBAL uint8_t selectedWiFi _INIT(0); WLED_GLOBAL int8_t selectedWiFi _INIT(0);
WLED_GLOBAL byte apChannel _INIT(1); // 2.4GHz WiFi AP channel (1-13) WLED_GLOBAL byte apChannel _INIT(1); // 2.4GHz WiFi AP channel (1-13)
WLED_GLOBAL byte apHide _INIT(0); // hidden AP SSID WLED_GLOBAL byte apHide _INIT(0); // hidden AP SSID
WLED_GLOBAL byte apBehavior _INIT(AP_BEHAVIOR_BOOT_NO_CONN); // access point opens when no connection after boot by default WLED_GLOBAL byte apBehavior _INIT(AP_BEHAVIOR_BOOT_NO_CONN); // access point opens when no connection after boot by default
@ -377,9 +377,9 @@ WLED_GLOBAL uint8_t txPower _INIT(WIFI_POWER_8_5dBm);
WLED_GLOBAL uint8_t txPower _INIT(WIFI_POWER_19_5dBm); WLED_GLOBAL uint8_t txPower _INIT(WIFI_POWER_19_5dBm);
#endif #endif
#endif #endif
#define WLED_WIFI_CONFIGURED (strlen(multiWiFi[0].clientSSID) >= 1 && strcmp(multiWiFi[0].clientSSID, DEFAULT_CLIENT_SSID) != 0) #define WLED_WIFI_CONFIGURED isWiFiConfigured()
#ifdef WLED_USE_ETHERNET #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
#ifdef WLED_ETH_DEFAULT // default ethernet board type if specified #ifdef WLED_ETH_DEFAULT // default ethernet board type if specified
WLED_GLOBAL int ethernetType _INIT(WLED_ETH_DEFAULT); // ethernet board type WLED_GLOBAL int ethernetType _INIT(WLED_ETH_DEFAULT); // ethernet board type
#else #else
@ -582,6 +582,7 @@ WLED_GLOBAL uint16_t userVar0 _INIT(0), userVar1 _INIT(0); //available for use i
// internal global variable declarations // internal global variable declarations
// wifi // wifi
WLED_GLOBAL bool apActive _INIT(false); WLED_GLOBAL bool apActive _INIT(false);
WLED_GLOBAL byte apClients _INIT(0);
WLED_GLOBAL bool forceReconnect _INIT(false); WLED_GLOBAL bool forceReconnect _INIT(false);
WLED_GLOBAL unsigned long lastReconnectAttempt _INIT(0); WLED_GLOBAL unsigned long lastReconnectAttempt _INIT(0);
WLED_GLOBAL bool interfacesInited _INIT(false); WLED_GLOBAL bool interfacesInited _INIT(false);
@ -1049,11 +1050,9 @@ public:
void beginStrip(); void beginStrip();
void handleConnection(); void handleConnection();
bool initEthernet(); // result is informational
void initAP(bool resetAP = false); void initAP(bool resetAP = false);
void initConnection(); void initConnection();
void initInterfaces(); void initInterfaces();
int8_t findWiFi(bool doScan = false);
#if defined(STATUSLED) #if defined(STATUSLED)
void handleStatusLED(); void handleStatusLED();
#endif #endif

View File

@ -110,7 +110,7 @@ void appendGPIOinfo(Print& settingsScript) {
settingsScript.print(hardwareTX); // debug output (TX) pin settingsScript.print(hardwareTX); // debug output (TX) pin
firstPin = false; firstPin = false;
#endif #endif
#ifdef WLED_USE_ETHERNET #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) {
if (!firstPin) settingsScript.print(','); if (!firstPin) settingsScript.print(',');
for (unsigned p=0; p<WLED_ETH_RSVD_PINS_COUNT; p++) { settingsScript.printf("%d,",esp32_nonconfigurable_ethernet_pins[p].pin); } for (unsigned p=0; p<WLED_ETH_RSVD_PINS_COUNT; p++) { settingsScript.printf("%d,",esp32_nonconfigurable_ethernet_pins[p].pin); }
@ -178,9 +178,12 @@ void getSettingsJS(byte subPage, Print& settingsScript)
char fpass[l+1]; //fill password field with *** char fpass[l+1]; //fill password field with ***
fpass[l] = 0; fpass[l] = 0;
memset(fpass,'*',l); memset(fpass,'*',l);
settingsScript.printf_P(PSTR("addWiFi(\"%s\",\"%s\",0x%X,0x%X,0x%X);"), char bssid[13];
fillMAC2Str(bssid, multiWiFi[n].bssid);
settingsScript.printf_P(PSTR("addWiFi(\"%s\",\"%s\",\"%s\",0x%X,0x%X,0x%X);"),
multiWiFi[n].clientSSID, multiWiFi[n].clientSSID,
fpass, fpass,
bssid,
(uint32_t) multiWiFi[n].staticIP, // explicit cast required as this is a struct (uint32_t) multiWiFi[n].staticIP, // explicit cast required as this is a struct
(uint32_t) multiWiFi[n].staticGW, (uint32_t) multiWiFi[n].staticGW,
(uint32_t) multiWiFi[n].staticSN); (uint32_t) multiWiFi[n].staticSN);
@ -219,7 +222,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
settingsScript.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting settingsScript.print(F("toggle('ESPNOW');")); // hide ESP-NOW setting
#endif #endif
#ifdef WLED_USE_ETHERNET #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET)
printSetFormValue(settingsScript,PSTR("ETH"),ethernetType); printSetFormValue(settingsScript,PSTR("ETH"),ethernetType);
#else #else
//hide ethernet setting if not compiled in //hide ethernet setting if not compiled in