From 2f6f0d2f3e3ca7c016d7f507a9a5433564484de0 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 18 May 2024 15:17:08 +0200 Subject: [PATCH 01/12] WLED 0.14.4 --- CHANGELOG.md | 4 + package-lock.json | 4 +- package.json | 2 +- wled00/html_other.h | 66 ++++----- wled00/html_settings.h | 322 ++++++++++++++++++++--------------------- wled00/wled.h | 2 +- 6 files changed, 202 insertions(+), 198 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1fe5cbdd..5613f2d0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## WLED changelog +#### Build 2405180 +- WLED 0.14.4 release +- Fix for #3978 + #### Build 2404040 - WLED 0.14.3 release - Fix for transition 0 (#3854, #3832, #3720) diff --git a/package-lock.json b/package-lock.json index a7330143f..7b57cebc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.14.3", + "version": "0.14.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.14.3", + "version": "0.14.4", "license": "ISC", "dependencies": { "clean-css": "^4.2.3", diff --git a/package.json b/package.json index 3bd73b76d..8171059d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.3", + "version": "0.14.4", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/wled00/html_other.h b/wled00/html_other.h index 3f9402615..0d7c39db6 100644 --- a/wled00/html_other.h +++ b/wled00/html_other.h @@ -43,45 +43,45 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()====="; // Autogenerated from wled00/data/update.htm, do not edit!! const uint16_t PAGE_update_length = 613; const uint8_t PAGE_update[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x75, 0x53, 0xcb, 0x6e, 0xdb, 0x30, - 0x10, 0xbc, 0xeb, 0x2b, 0x18, 0x9e, 0x6c, 0xa0, 0x21, 0xfb, 0xba, 0x34, 0x95, 0x94, 0xd6, 0x4d, - 0x50, 0x04, 0x28, 0x90, 0x00, 0x49, 0x5a, 0xf4, 0x54, 0x50, 0xe4, 0xca, 0x62, 0x4d, 0x91, 0x0a, - 0xb9, 0xb2, 0x61, 0x14, 0xf9, 0xf7, 0xae, 0x28, 0x3b, 0x2d, 0xfa, 0xb8, 0x08, 0xa2, 0x76, 0x76, - 0xb8, 0x3b, 0x33, 0x2a, 0x4f, 0x2e, 0xae, 0x3f, 0xdc, 0x7d, 0xbd, 0xb9, 0x64, 0x1d, 0xf6, 0xae, - 0x2e, 0x0f, 0x4f, 0x50, 0xa6, 0x2e, 0x7b, 0x40, 0xc5, 0x74, 0xf0, 0x08, 0x1e, 0x2b, 0xbe, 0xb3, - 0x06, 0xbb, 0xca, 0xc0, 0xd6, 0x6a, 0x38, 0xcd, 0x07, 0xce, 0xbc, 0xea, 0xa1, 0xe2, 0x5b, 0x0b, - 0xbb, 0x21, 0x44, 0xe4, 0x75, 0x51, 0xa2, 0x45, 0x07, 0xf5, 0x97, 0x4f, 0x97, 0x17, 0xec, 0x7e, - 0x30, 0x0a, 0xa1, 0x94, 0xf3, 0xa7, 0x32, 0xe9, 0x68, 0x07, 0xac, 0x8b, 0x76, 0xf4, 0x1a, 0x6d, - 0xf0, 0x6c, 0xb5, 0x58, 0xfe, 0xd8, 0x59, 0x6f, 0xc2, 0x4e, 0x74, 0x36, 0x61, 0x88, 0x7b, 0xd1, - 0x28, 0xbd, 0x59, 0x2c, 0x1f, 0x9f, 0x20, 0xf7, 0x04, 0x31, 0x41, 0x8f, 0x3d, 0x4d, 0x20, 0xd6, - 0x80, 0x97, 0x0e, 0xa6, 0xd7, 0xd5, 0xfe, 0xca, 0x2c, 0xf8, 0xd8, 0xf2, 0xa5, 0x48, 0xb8, 0x77, - 0x20, 0x8c, 0x4d, 0x83, 0x53, 0xfb, 0x8a, 0xfb, 0xe0, 0x81, 0x3f, 0xfb, 0x6f, 0x4b, 0x9f, 0xd6, - 0x7f, 0xf7, 0x34, 0x2e, 0xe8, 0x0d, 0x7f, 0x2c, 0x4a, 0x79, 0x18, 0xf1, 0x30, 0x2a, 0x4b, 0x51, - 0x57, 0x5c, 0x26, 0x40, 0xb4, 0x7e, 0x9d, 0x64, 0x12, 0xdf, 0xd3, 0xf9, 0x50, 0xbd, 0xe1, 0xf5, - 0x6f, 0xc8, 0x89, 0xaa, 0x2e, 0xde, 0xd9, 0x7e, 0x12, 0x80, 0x8d, 0xd1, 0x2d, 0xf8, 0x4c, 0xaf, - 0x53, 0xe2, 0xcb, 0xb7, 0x84, 0xcc, 0x88, 0x52, 0xce, 0x92, 0x36, 0xc1, 0xec, 0x59, 0xf0, 0x2e, - 0x28, 0x53, 0xf1, 0x8f, 0x80, 0x9f, 0x17, 0x4b, 0xa2, 0xeb, 0x5e, 0xd6, 0x45, 0x96, 0xec, 0x36, - 0xb4, 0xb8, 0x53, 0x11, 0x9e, 0xb4, 0xa3, 0x4a, 0xd9, 0x86, 0xd8, 0x33, 0xf2, 0xa2, 0x0b, 0xd4, - 0x73, 0x73, 0x7d, 0x7b, 0xc7, 0x99, 0xca, 0xf2, 0x54, 0x5c, 0xc8, 0x31, 0x03, 0x39, 0xb3, 0x54, - 0x23, 0x41, 0x58, 0x01, 0x24, 0xdd, 0x7e, 0x20, 0x57, 0xfa, 0xd1, 0xa1, 0x1d, 0x54, 0x44, 0x39, - 0x11, 0x9c, 0x12, 0x4c, 0x71, 0xba, 0x3a, 0x8d, 0x4d, 0x6f, 0xc9, 0xce, 0xfb, 0xe9, 0xe6, 0x2b, - 0x9f, 0x50, 0x39, 0x07, 0x86, 0x6d, 0x21, 0x26, 0xa2, 0x3c, 0x63, 0x65, 0x1a, 0x94, 0x67, 0x85, - 0x76, 0x2a, 0xa5, 0x8a, 0x27, 0x3b, 0xf0, 0xfa, 0xb9, 0x78, 0xf1, 0x5a, 0xbc, 0xa2, 0x55, 0xa8, - 0x42, 0x2b, 0xc4, 0xfa, 0x22, 0xec, 0xf2, 0x0a, 0x0c, 0x3b, 0x60, 0x8e, 0xee, 0x4f, 0xc8, 0x1a, - 0xeb, 0x55, 0xdc, 0x53, 0xbf, 0x62, 0x45, 0x17, 0xa1, 0xad, 0x78, 0x87, 0x38, 0xa4, 0x33, 0x29, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x75, 0x53, 0x5d, 0x6f, 0xd4, 0x30, + 0x10, 0x7c, 0xcf, 0xaf, 0x70, 0xfd, 0x74, 0x27, 0x51, 0x1b, 0x50, 0x5f, 0x28, 0x49, 0x0a, 0x47, + 0x2b, 0x54, 0x09, 0xa9, 0x95, 0xda, 0x82, 0x78, 0x42, 0x8e, 0xbd, 0xb9, 0x98, 0x73, 0xec, 0xd4, + 0xde, 0xdc, 0xe9, 0x84, 0xfa, 0xdf, 0xd9, 0x38, 0x77, 0x05, 0xf1, 0xf1, 0x12, 0xc5, 0xd9, 0xd9, + 0xf1, 0xee, 0xcc, 0xa4, 0x3c, 0xb9, 0xbc, 0xf9, 0x70, 0xff, 0xf5, 0xf6, 0x8a, 0x75, 0xd8, 0xbb, + 0xba, 0x3c, 0x3c, 0x41, 0x99, 0xba, 0xec, 0x01, 0x15, 0xd3, 0xc1, 0x23, 0x78, 0xac, 0xf8, 0xce, + 0x1a, 0xec, 0x2a, 0x03, 0x5b, 0xab, 0xe1, 0x34, 0x1f, 0x38, 0xf3, 0xaa, 0x87, 0x8a, 0x6f, 0x2d, + 0xec, 0x86, 0x10, 0x91, 0xd7, 0x45, 0x89, 0x16, 0x1d, 0xd4, 0x5f, 0x3e, 0x5d, 0x5d, 0xb2, 0x87, + 0xc1, 0x28, 0x84, 0x52, 0xce, 0x9f, 0xca, 0xa4, 0xa3, 0x1d, 0xb0, 0x2e, 0xda, 0xd1, 0x6b, 0xb4, + 0xc1, 0xb3, 0xd5, 0x62, 0xf9, 0x63, 0x67, 0xbd, 0x09, 0x3b, 0xd1, 0xd9, 0x84, 0x21, 0xee, 0x45, + 0xa3, 0xf4, 0x66, 0xb1, 0x7c, 0x7a, 0x86, 0x3c, 0x10, 0xc4, 0x04, 0x3d, 0xf6, 0x34, 0x81, 0x58, + 0x03, 0x5e, 0x39, 0x98, 0x5e, 0x57, 0xfb, 0x6b, 0xb3, 0xe0, 0x63, 0xcb, 0x97, 0x22, 0xe1, 0xde, + 0x81, 0x30, 0x36, 0x0d, 0x4e, 0xed, 0x2b, 0xee, 0x83, 0x07, 0xfe, 0xe2, 0xbf, 0x2d, 0x7d, 0x5a, + 0xff, 0xdd, 0xd3, 0xb8, 0xa0, 0x37, 0xfc, 0xa9, 0x28, 0xe5, 0x61, 0xc4, 0xc3, 0xa8, 0x2c, 0x45, + 0x5d, 0x71, 0x99, 0x00, 0xd1, 0xfa, 0x75, 0x92, 0x49, 0x7c, 0x4f, 0x17, 0x43, 0xf5, 0x86, 0xd7, + 0xbf, 0x21, 0x27, 0xaa, 0xba, 0x78, 0x67, 0xfb, 0x49, 0x00, 0x36, 0x46, 0xb7, 0xe0, 0x33, 0xbd, + 0x4e, 0x89, 0x2f, 0xdf, 0x12, 0x32, 0x23, 0x4a, 0x39, 0x4b, 0xda, 0x04, 0xb3, 0x67, 0xc1, 0xbb, + 0xa0, 0x4c, 0xc5, 0x3f, 0x02, 0x7e, 0x5e, 0x2c, 0x89, 0xae, 0x7b, 0x5d, 0x17, 0x59, 0xb2, 0xbb, + 0xd0, 0xe2, 0x4e, 0x45, 0x78, 0xd6, 0x8e, 0x2a, 0x65, 0x1b, 0x62, 0xcf, 0xc8, 0x8b, 0x2e, 0x50, + 0xcf, 0xed, 0xcd, 0xdd, 0x3d, 0x67, 0x2a, 0xcb, 0x53, 0x71, 0x21, 0xc7, 0x0c, 0xe4, 0xcc, 0x52, + 0x8d, 0x04, 0x61, 0x05, 0x90, 0x74, 0xfb, 0x81, 0x5c, 0xe9, 0x47, 0x87, 0x76, 0x50, 0x11, 0xe5, + 0x44, 0x70, 0x4a, 0x30, 0xc5, 0xe9, 0xea, 0x34, 0x36, 0xbd, 0x25, 0x3b, 0x1f, 0xa6, 0x9b, 0xaf, + 0x7d, 0x42, 0xe5, 0x1c, 0x18, 0xb6, 0x85, 0x98, 0x88, 0xf2, 0x9c, 0x95, 0x69, 0x50, 0x9e, 0x15, + 0xda, 0xa9, 0x94, 0x2a, 0x9e, 0xec, 0xc0, 0xeb, 0x97, 0xe2, 0xd5, 0x99, 0x38, 0xa3, 0x55, 0xa8, + 0x42, 0x2b, 0xc4, 0xfa, 0x32, 0xec, 0xf2, 0x0a, 0x0c, 0x3b, 0x60, 0x8e, 0xee, 0x4f, 0xc8, 0x1a, + 0xeb, 0x55, 0xdc, 0x53, 0xbf, 0x62, 0x45, 0x17, 0xa1, 0xad, 0x78, 0x87, 0x38, 0xa4, 0x73, 0x29, 0xd7, 0x16, 0xbb, 0xb1, 0x11, 0x3a, 0xf4, 0xf2, 0xbd, 0x8d, 0x3a, 0x84, 0xb0, 0xb1, 0x20, 0xa7, 0x7d, 0x65, 0x04, 0x07, 0x2a, 0x41, 0xe2, 0x0c, 0x55, 0x24, 0xb3, 0x2a, 0xfe, 0xad, 0x71, 0xca, 0x6f, 0x48, 0x13, 0xdb, 0xaf, 0x59, 0x91, 0x1d, 0x38, 0xf2, 0xd0, 0x17, 0x91, 0x3a, 0x0b, 0xce, - 0x24, 0x61, 0xc3, 0x81, 0xf6, 0x48, 0xf1, 0x27, 0xb5, 0x48, 0xdb, 0xf5, 0x79, 0xd6, 0xbe, 0x6a, - 0x69, 0xc2, 0xd3, 0xf4, 0x30, 0x92, 0xae, 0x53, 0x42, 0xa5, 0xca, 0x3b, 0x94, 0xd6, 0x0f, 0x23, - 0xb2, 0x59, 0xab, 0xd6, 0x3a, 0x38, 0xa6, 0xf9, 0xa8, 0x68, 0x84, 0x87, 0xd1, 0x46, 0x30, 0x33, + 0x24, 0x61, 0xc3, 0x81, 0xf6, 0x48, 0xf1, 0x27, 0xb5, 0x48, 0xdb, 0xf5, 0x45, 0xd6, 0xbe, 0x6a, + 0x69, 0xc2, 0xd3, 0xf4, 0x38, 0x92, 0xae, 0x53, 0x42, 0xa5, 0xca, 0x3b, 0x94, 0xd6, 0x0f, 0x23, + 0xb2, 0x59, 0xab, 0xd6, 0x3a, 0x38, 0xa6, 0xf9, 0xa8, 0x68, 0x84, 0xc7, 0xd1, 0x46, 0x30, 0x33, 0xba, 0x19, 0x11, 0x29, 0x90, 0x33, 0x7c, 0xd6, 0x90, 0xc8, 0x66, 0x9b, 0x4e, 0x4a, 0x39, 0x97, 0xff, 0x01, 0x9d, 0x0f, 0x93, 0xf0, 0xda, 0x59, 0xbd, 0xa9, 0xf8, 0x6a, 0xd2, 0x7d, 0x45, 0x39, 0xff, 0xd5, 0x94, 0x0d, 0xaa, 0x4b, 0x63, 0xb7, 0x45, 0xf6, 0x71, 0x4a, 0x29, 0xd1, 0xd4, 0x99, - 0x9d, 0xa2, 0x27, 0x84, 0x20, 0x70, 0x26, 0xbf, 0xc9, 0xcb, 0x32, 0x13, 0x98, 0x0f, 0xc8, 0xb4, - 0x0b, 0x74, 0x08, 0x91, 0x66, 0x6d, 0x23, 0xa4, 0x2e, 0xfb, 0x31, 0xa8, 0x35, 0xb0, 0xb3, 0x65, - 0x29, 0x89, 0x6f, 0x5a, 0x77, 0x8a, 0xdc, 0x94, 0xbf, 0xe9, 0xc7, 0xfe, 0x09, 0x81, 0xae, 0xc2, - 0xc6, 0xee, 0x03, 0x00, 0x00 + 0x9d, 0xa2, 0x27, 0x84, 0x20, 0x70, 0x26, 0xbf, 0xcd, 0xcb, 0x32, 0x13, 0x98, 0x0f, 0xc8, 0xb4, + 0x0b, 0x74, 0x08, 0x91, 0x66, 0x6d, 0x23, 0xa4, 0x2e, 0xfb, 0x31, 0xa8, 0x35, 0xb0, 0xf3, 0x65, + 0x29, 0x89, 0x6f, 0x5a, 0x77, 0x8a, 0xdc, 0x94, 0xbf, 0xe9, 0xc7, 0xfe, 0x09, 0xb5, 0x43, 0x19, + 0xed, 0xee, 0x03, 0x00, 0x00 }; diff --git a/wled00/html_settings.h b/wled00/html_settings.h index a1bd61e9e..26cd29517 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -1607,168 +1607,168 @@ const uint8_t PAGE_settings_time[] PROGMEM = { // Autogenerated from wled00/data/settings_sec.htm, do not edit!! -const uint16_t PAGE_settings_sec_length = 2548; +const uint16_t PAGE_settings_sec_length = 2549; const uint8_t PAGE_settings_sec[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x9d, 0x58, 0x6d, 0x53, 0xdb, 0x48, - 0x12, 0xfe, 0xee, 0x5f, 0x31, 0x9e, 0xad, 0xca, 0x4a, 0x17, 0x21, 0x03, 0x49, 0x6d, 0x25, 0x60, - 0x99, 0x83, 0x40, 0x36, 0x5c, 0x41, 0xa0, 0xb0, 0xd9, 0xdc, 0x55, 0x2e, 0x95, 0x92, 0xa5, 0xb1, - 0x35, 0xb1, 0xac, 0xd1, 0xce, 0x8c, 0x70, 0x7c, 0xd9, 0xfc, 0xf7, 0x7b, 0x7a, 0x24, 0xf9, 0x85, - 0x40, 0x72, 0xb9, 0x0f, 0x60, 0x69, 0x5e, 0x7a, 0xba, 0x9f, 0xee, 0x7e, 0xba, 0x47, 0xfd, 0xee, - 0xe9, 0xd5, 0xab, 0xd1, 0xbf, 0xae, 0xcf, 0x58, 0x66, 0xe7, 0xf9, 0xa0, 0x4f, 0xff, 0x59, 0x1e, - 0x17, 0xd3, 0x88, 0x8b, 0x82, 0xe3, 0x5d, 0xc4, 0xe9, 0xa0, 0x3f, 0x17, 0x36, 0x66, 0x9d, 0x44, - 0x15, 0x56, 0x14, 0x36, 0xe2, 0x0b, 0x99, 0xda, 0x2c, 0x4a, 0xc5, 0x9d, 0x4c, 0xc4, 0x8e, 0x7b, - 0x09, 0x64, 0x21, 0xad, 0x8c, 0xf3, 0x1d, 0x93, 0xc4, 0xb9, 0x88, 0xf6, 0x82, 0x79, 0xfc, 0x59, - 0xce, 0xab, 0xf9, 0xea, 0xbd, 0x32, 0x42, 0xbb, 0x97, 0x78, 0x8c, 0xf7, 0x42, 0x71, 0xd6, 0x29, - 0xe2, 0xb9, 0x88, 0xf8, 0x9d, 0x14, 0x8b, 0x52, 0x69, 0xcb, 0x9b, 0x53, 0x92, 0x2c, 0xd6, 0x46, - 0xe0, 0x90, 0xca, 0x4e, 0x76, 0x5e, 0x60, 0xd4, 0x4a, 0x9b, 0x8b, 0xc1, 0xa5, 0x34, 0x09, 0x1b, - 0x0a, 0x6b, 0x65, 0x31, 0x35, 0xfd, 0x5e, 0x3d, 0xd8, 0x37, 0x89, 0x96, 0xa5, 0x1d, 0x74, 0xee, - 0x62, 0xcd, 0x72, 0x95, 0xc8, 0x32, 0xb0, 0x72, 0x2e, 0x54, 0x65, 0x83, 0x34, 0x4a, 0x55, 0x52, - 0xcd, 0xa1, 0x6e, 0x80, 0x89, 0xa8, 0xbb, 0x47, 0x3f, 0xa5, 0x56, 0x56, 0x45, 0x3c, 0xb3, 0xb6, - 0x3c, 0xe0, 0x87, 0x93, 0xaa, 0x48, 0xac, 0x54, 0x05, 0x7b, 0xe3, 0xf9, 0x5f, 0x16, 0xb2, 0x48, - 0xd5, 0x22, 0x54, 0xa5, 0x28, 0x3c, 0xb7, 0xc0, 0x1c, 0xf4, 0x7a, 0xb3, 0x42, 0x85, 0x8b, 0x5c, - 0xa4, 0xe1, 0x54, 0xf4, 0x26, 0x22, 0xb6, 0x95, 0x16, 0xa6, 0x67, 0x1a, 0x25, 0x7a, 0xbf, 0x18, - 0x91, 0x54, 0x5a, 0xda, 0xe5, 0x4e, 0x3b, 0xc4, 0xfd, 0xaf, 0x2b, 0xa1, 0x27, 0xf7, 0x84, 0x4e, - 0x85, 0xbd, 0xbd, 0xb9, 0xf0, 0x78, 0x6f, 0xbd, 0x38, 0xe0, 0x1f, 0x8d, 0xc8, 0x27, 0x9b, 0xbb, - 0x6e, 0x1f, 0xdb, 0x55, 0x95, 0x69, 0x6c, 0xc5, 0x83, 0x7b, 0xa6, 0xe7, 0xa9, 0x67, 0xfd, 0x2f, - 0x5a, 0x40, 0xbf, 0x82, 0x91, 0xb2, 0xf6, 0x2c, 0x17, 0x64, 0xfa, 0xc9, 0xd2, 0x4d, 0xad, 0x97, - 0x4a, 0x73, 0x35, 0xfe, 0xb4, 0xb1, 0xd8, 0x3e, 0x79, 0xc2, 0xd5, 0xf8, 0x93, 0x48, 0x2c, 0x8f, - 0x22, 0xbb, 0x2c, 0x85, 0x9a, 0xd0, 0x58, 0xf7, 0x58, 0xeb, 0x78, 0x19, 0x4a, 0xe3, 0x7e, 0xb7, - 0x24, 0xe4, 0x2a, 0x4e, 0xff, 0x31, 0xf4, 0x6c, 0x20, 0xa2, 0xee, 0xae, 0xff, 0x25, 0x17, 0x96, - 0xa9, 0x28, 0x0d, 0x13, 0x0d, 0x78, 0x44, 0x73, 0xac, 0xc7, 0x6b, 0xbf, 0x70, 0xff, 0x50, 0x85, - 0x30, 0xf7, 0xd8, 0x5a, 0x2d, 0xc7, 0x95, 0x15, 0x98, 0xd0, 0x09, 0x0f, 0xac, 0x1f, 0xdc, 0x1f, - 0xa7, 0xb3, 0x79, 0xc0, 0xad, 0xf8, 0x6c, 0x7b, 0x9f, 0xe2, 0xbb, 0xb8, 0x15, 0xf0, 0xcd, 0xc2, - 0xd8, 0x2c, 0x0b, 0x88, 0x10, 0x7e, 0x90, 0x86, 0x63, 0x95, 0x2e, 0xc3, 0xb8, 0x04, 0x4e, 0xe9, - 0xab, 0x4c, 0xe6, 0xa9, 0xa7, 0x68, 0x7d, 0x9c, 0xa6, 0x67, 0x77, 0xd0, 0xe2, 0x42, 0x1a, 0x44, - 0xab, 0xd0, 0x1e, 0x27, 0x9d, 0x79, 0xe0, 0xf9, 0xd1, 0xe0, 0xcb, 0xef, 0xc2, 0xfe, 0xe1, 0xf9, - 0x01, 0x64, 0x9e, 0x24, 0xb3, 0xd7, 0x32, 0x17, 0x14, 0x84, 0x1e, 0x21, 0xc8, 0xc7, 0xc9, 0x2c, - 0x99, 0x4c, 0xb9, 0xff, 0xe8, 0x6c, 0x09, 0xef, 0x0b, 0x0b, 0xbf, 0xf9, 0x5f, 0x1f, 0x3e, 0x47, - 0x68, 0xad, 0x34, 0xcc, 0xc3, 0x39, 0x48, 0x15, 0xa3, 0x72, 0x11, 0xe6, 0x6a, 0xea, 0xf1, 0x33, - 0x1a, 0x67, 0x0d, 0x78, 0x70, 0x3d, 0x9b, 0x40, 0xb4, 0x83, 0x01, 0xb9, 0xa1, 0x01, 0xd7, 0x45, - 0x33, 0x0e, 0xf4, 0xb1, 0x71, 0x22, 0xa7, 0x95, 0x8e, 0x1d, 0xda, 0x35, 0x0c, 0x6c, 0x12, 0x4b, - 0x8a, 0xc2, 0x7f, 0x17, 0xe7, 0x45, 0xa2, 0xe6, 0x25, 0x40, 0x17, 0xac, 0x8c, 0xa7, 0x82, 0x21, - 0x26, 0xe2, 0x2e, 0x62, 0x61, 0xc3, 0x41, 0x26, 0x53, 0x8b, 0x91, 0x8a, 0x8d, 0xad, 0x7d, 0xb4, - 0xe7, 0x7f, 0xa1, 0xe4, 0x50, 0x91, 0xb3, 0xc2, 0xd2, 0x84, 0x73, 0x8b, 0x2c, 0xa0, 0xf2, 0x9b, - 0xd1, 0xe5, 0x45, 0x64, 0x61, 0x4b, 0x92, 0xc7, 0xc6, 0x90, 0x21, 0x64, 0x95, 0x27, 0x8e, 0x1a, - 0x53, 0x0e, 0x38, 0x49, 0x83, 0x17, 0x92, 0x5c, 0xc4, 0x7a, 0x54, 0xa7, 0x96, 0xd7, 0xa4, 0x98, - 0xf3, 0x8d, 0x5d, 0xc2, 0xc8, 0xb8, 0x90, 0x73, 0xa7, 0x6f, 0xc4, 0x0b, 0x55, 0x90, 0x65, 0xf5, - 0x8a, 0x08, 0x70, 0xb5, 0x9b, 0xbc, 0x56, 0x41, 0x04, 0xf8, 0xe6, 0x79, 0x5a, 0xcc, 0xd5, 0x1d, - 0x05, 0x86, 0x3b, 0x08, 0xc0, 0xee, 0xbf, 0xdc, 0xdd, 0xdd, 0x30, 0xa7, 0x2a, 0x09, 0x34, 0xf2, - 0x05, 0xd9, 0xd3, 0x1a, 0x53, 0x88, 0x05, 0xfb, 0xe7, 0xe5, 0xc5, 0x1b, 0xe4, 0xe9, 0x8d, 0xf8, - 0xb3, 0x12, 0xc6, 0x1e, 0x7e, 0xc7, 0xf1, 0x1b, 0x47, 0x6f, 0xa0, 0x93, 0x49, 0x83, 0xd3, 0x4d, - 0x09, 0x4f, 0x89, 0x11, 0xe2, 0x2e, 0x70, 0x23, 0xc6, 0x22, 0xcd, 0xcd, 0x20, 0x7a, 0x4e, 0x5a, - 0xf8, 0xdf, 0xf5, 0xf3, 0x5a, 0xae, 0xdd, 0x12, 0x4c, 0x32, 0x92, 0x59, 0xd0, 0x6d, 0x05, 0xd4, - 0x9c, 0x72, 0x7d, 0x35, 0x1c, 0xf1, 0x60, 0x23, 0x9f, 0x9d, 0x72, 0xbe, 0x7f, 0x48, 0x16, 0x15, - 0xce, 0xa2, 0xd7, 0x4a, 0xcf, 0x4f, 0xe1, 0xd1, 0xc3, 0x26, 0x3b, 0x8b, 0x26, 0xb8, 0x3d, 0x4e, - 0x7e, 0x06, 0xac, 0x21, 0x05, 0x8e, 0x79, 0xbf, 0xfb, 0x81, 0xe2, 0x9f, 0x32, 0x03, 0x73, 0x85, - 0x8f, 0xf1, 0xbb, 0x38, 0xaf, 0xc0, 0xa5, 0x3c, 0xe8, 0xee, 0xad, 0xa1, 0x4b, 0x32, 0x91, 0xcc, - 0xde, 0x56, 0xf3, 0x75, 0xbe, 0x77, 0xbd, 0xae, 0x20, 0x53, 0xc2, 0x99, 0x58, 0x86, 0x70, 0x59, - 0x92, 0x79, 0xbd, 0xf7, 0xbb, 0x3b, 0x2f, 0x3f, 0xf4, 0x7c, 0x24, 0xfd, 0x7b, 0x7e, 0x02, 0xbd, - 0x4d, 0x19, 0x27, 0x94, 0x8a, 0xa3, 0x78, 0x8c, 0xff, 0x67, 0x60, 0x7c, 0x98, 0xca, 0x87, 0x99, - 0x9c, 0x58, 0xfc, 0xbe, 0x42, 0x09, 0xd0, 0x2a, 0xc7, 0xd3, 0x71, 0x4e, 0xef, 0xd7, 0x31, 0x88, - 0x9d, 0xc6, 0xe3, 0xd2, 0x5c, 0xa8, 0x64, 0x46, 0x5b, 0xc0, 0xf2, 0x2e, 0x99, 0x87, 0x8d, 0xa4, - 0x6b, 0x44, 0xea, 0x6d, 0xd9, 0x3c, 0x9c, 0xaa, 0x45, 0xe1, 0xe4, 0xc2, 0x31, 0xfc, 0x8d, 0x9a, - 0xd3, 0x02, 0xb0, 0x8c, 0x5a, 0x5c, 0x08, 0x77, 0x80, 0x7b, 0x76, 0xab, 0xdd, 0xd3, 0x8d, 0x9c, - 0x66, 0xab, 0xe1, 0x66, 0xef, 0x39, 0x1c, 0xa6, 0x69, 0xf0, 0x54, 0x50, 0x26, 0xf0, 0x0f, 0x08, - 0xe6, 0x24, 0xaf, 0x52, 0x61, 0xbc, 0x95, 0x75, 0xbe, 0xff, 0xd7, 0x5f, 0xcd, 0x1b, 0xd2, 0x96, - 0x7e, 0x4f, 0xc5, 0x24, 0xae, 0x72, 0x8b, 0xe4, 0x47, 0x4e, 0x6c, 0xa4, 0xcb, 0x76, 0xae, 0x03, - 0x2a, 0x7b, 0x8f, 0x71, 0xc0, 0xc5, 0x45, 0x1d, 0x48, 0x9c, 0x6a, 0xc1, 0x47, 0xfe, 0xd4, 0x12, - 0xc5, 0x3e, 0xb4, 0xc2, 0x7f, 0xea, 0xf1, 0x77, 0x17, 0x67, 0xa7, 0x20, 0x53, 0x93, 0x1e, 0x71, - 0xe4, 0x0f, 0x56, 0x9b, 0xd4, 0xdf, 0x38, 0x6f, 0xe8, 0xd5, 0xa4, 0x69, 0xa3, 0x86, 0xe6, 0x51, - 0x96, 0x5c, 0xee, 0x1c, 0xca, 0x89, 0xc7, 0xc9, 0xbf, 0x07, 0x44, 0xc5, 0xa1, 0x2b, 0x55, 0x89, - 0xca, 0x7d, 0x57, 0xbd, 0x76, 0x03, 0xcf, 0x95, 0xb7, 0x88, 0x56, 0xe7, 0x43, 0xab, 0x34, 0xa0, - 0x24, 0x2d, 0xce, 0xad, 0x98, 0x53, 0x9c, 0x27, 0xe7, 0x25, 0x77, 0x36, 0xd7, 0xcb, 0xb0, 0x7b, - 0x5e, 0x82, 0x58, 0xc8, 0x2e, 0x76, 0xa9, 0x52, 0x11, 0xb2, 0x6b, 0xa4, 0xb0, 0x11, 0x4c, 0x90, - 0x43, 0x19, 0x29, 0xc9, 0xce, 0xaf, 0x41, 0x1d, 0xc1, 0x96, 0x44, 0xb3, 0x2d, 0x31, 0x70, 0xd2, - 0x10, 0xa3, 0x22, 0x37, 0xc2, 0xa9, 0x2d, 0x48, 0xb5, 0xd8, 0x66, 0x04, 0x56, 0xa0, 0x22, 0xec, - 0xc8, 0xd1, 0x03, 0x78, 0x7b, 0x81, 0x08, 0x11, 0x8c, 0xe6, 0x9d, 0xb4, 0x19, 0x82, 0x9b, 0xfb, - 0x47, 0x3b, 0x7b, 0x07, 0x77, 0x4a, 0xa6, 0x6c, 0xd7, 0x0f, 0x4d, 0x99, 0x4b, 0xeb, 0x46, 0x91, - 0xa4, 0xc0, 0x79, 0x6a, 0xb3, 0xc1, 0xfe, 0x93, 0x27, 0xde, 0xaa, 0x20, 0xaf, 0xad, 0x0d, 0x1a, - 0x6b, 0x6b, 0x2b, 0x6c, 0x98, 0x29, 0x63, 0xe9, 0xa8, 0xa7, 0xc8, 0x2a, 0x6a, 0x12, 0x8e, 0x00, - 0xe9, 0xd3, 0xfa, 0xf1, 0x80, 0x03, 0x6e, 0x08, 0x7d, 0xaa, 0x90, 0x0f, 0xfe, 0x57, 0xec, 0x80, - 0xc8, 0x2d, 0x22, 0xbf, 0xe7, 0xc7, 0x4c, 0x8b, 0xc9, 0x2a, 0x01, 0xb7, 0x17, 0xb6, 0x16, 0x21, - 0x5d, 0xbf, 0x21, 0xfb, 0xff, 0x45, 0xca, 0x7a, 0xf1, 0x5a, 0x12, 0x21, 0xeb, 0x4a, 0xe5, 0x37, - 0x85, 0xbf, 0x67, 0xc2, 0x4f, 0xe6, 0xa8, 0x8c, 0x7e, 0xe3, 0x2e, 0x16, 0x83, 0x5a, 0xf5, 0x34, - 0x1c, 0x4e, 0xc2, 0xd8, 0x05, 0x49, 0xf4, 0xc0, 0x16, 0x91, 0xf0, 0xcd, 0x30, 0x6a, 0x56, 0xac, - 0x32, 0x9b, 0xc0, 0x3c, 0x6a, 0x01, 0x05, 0x2c, 0xc0, 0xc5, 0x61, 0xe8, 0x50, 0xb2, 0x5f, 0x3b, - 0xfd, 0x5e, 0xd3, 0x2a, 0xf5, 0x1d, 0x65, 0x0f, 0xfe, 0x2e, 0xe7, 0x04, 0x22, 0xab, 0x74, 0x0e, - 0xee, 0x75, 0x2c, 0x9e, 0x18, 0x18, 0x70, 0x88, 0x85, 0x6e, 0x41, 0xbf, 0x57, 0x77, 0x7e, 0x54, - 0x63, 0x51, 0xba, 0xc8, 0x96, 0x88, 0x23, 0x78, 0xd1, 0x90, 0x4d, 0x40, 0x53, 0x1d, 0x26, 0xf1, - 0x4e, 0x4f, 0x1f, 0x0d, 0x67, 0x75, 0x27, 0x37, 0x9c, 0x70, 0x86, 0x16, 0x2e, 0x53, 0x98, 0x29, - 0xe1, 0x38, 0x2c, 0x4d, 0xe5, 0x1d, 0x73, 0x5c, 0x1f, 0xa1, 0xf4, 0x40, 0xb7, 0xc5, 0xf6, 0x58, - 0x26, 0xf2, 0xf2, 0x84, 0x0f, 0x3a, 0x7d, 0x60, 0x6b, 0x61, 0x15, 0x75, 0x01, 0x11, 0xaf, 0x5f, - 0x38, 0x4e, 0x4d, 0x10, 0x5c, 0xb3, 0x88, 0xbf, 0xa1, 0x63, 0x8f, 0xfa, 0xbd, 0x7a, 0x02, 0xaa, - 0x41, 0xc4, 0xe0, 0xe1, 0x3d, 0x9d, 0xd5, 0xa6, 0x13, 0xda, 0x44, 0x84, 0xb6, 0xde, 0xb7, 0xb5, - 0xc3, 0x54, 0xe3, 0xb9, 0x84, 0x8e, 0xc3, 0xf8, 0x4e, 0xac, 0x97, 0x64, 0xba, 0x15, 0x9f, 0xed, - 0x0f, 0x3a, 0xc3, 0xa6, 0xc1, 0x63, 0x4f, 0xd8, 0xad, 0xeb, 0xbf, 0x88, 0x2e, 0xaa, 0x12, 0xd8, - 0xec, 0x0f, 0xda, 0x5e, 0x94, 0x5d, 0x9f, 0xbf, 0x3d, 0x60, 0x7d, 0x59, 0x94, 0x95, 0x6d, 0x44, - 0x97, 0x30, 0x6e, 0xa1, 0x74, 0xca, 0x1d, 0x48, 0x98, 0x5f, 0x35, 0xbb, 0xee, 0xd9, 0xc8, 0xff, - 0xe0, 0xf1, 0x39, 0xc0, 0x8a, 0x3f, 0xd7, 0xe9, 0x50, 0xbf, 0xc9, 0x62, 0xe3, 0x4d, 0x15, 0x60, - 0x31, 0x22, 0x96, 0x88, 0xaf, 0x79, 0x1c, 0x05, 0xca, 0x87, 0x2c, 0x04, 0x19, 0x92, 0x18, 0x33, - 0x8e, 0xbb, 0xff, 0x86, 0x63, 0xe8, 0xf0, 0x39, 0xd2, 0x1c, 0x05, 0x18, 0x3d, 0xaf, 0x96, 0x09, - 0x67, 0xae, 0x43, 0xc6, 0x89, 0x9b, 0x69, 0x1f, 0xb3, 0xe7, 0x2c, 0x95, 0x53, 0x69, 0x19, 0x96, - 0x8d, 0x41, 0xec, 0xc0, 0x44, 0x03, 0xfe, 0x0d, 0x97, 0x2c, 0x62, 0x8d, 0xb6, 0xbf, 0xf3, 0xe4, - 0x97, 0x97, 0x2f, 0x5e, 0xbc, 0x38, 0x64, 0xb7, 0x85, 0x28, 0x12, 0xbd, 0x2c, 0xad, 0x48, 0x99, - 0xd5, 0x71, 0x61, 0xe6, 0xd2, 0x18, 0x04, 0x60, 0xc8, 0x4e, 0xd0, 0x82, 0x68, 0x90, 0x6e, 0x61, - 0xd9, 0x22, 0x13, 0x44, 0xa4, 0x39, 0xda, 0x48, 0xea, 0x62, 0x60, 0x64, 0xc0, 0x52, 0xc5, 0xde, - 0x5e, 0x8d, 0x18, 0xaa, 0x03, 0x5b, 0xaa, 0x4a, 0xb3, 0x71, 0x5c, 0xcc, 0x30, 0x49, 0x13, 0x4a, - 0x07, 0x6c, 0x78, 0x7e, 0x19, 0x30, 0x61, 0x93, 0x90, 0x95, 0xb2, 0xe8, 0x76, 0x5a, 0x97, 0xea, - 0x01, 0x15, 0x11, 0xb6, 0x90, 0x1a, 0xd2, 0x8c, 0x61, 0xde, 0xd5, 0xe8, 0xd8, 0x67, 0x46, 0x4d, - 0x2c, 0xf4, 0x12, 0xac, 0x6e, 0x82, 0xef, 0xa1, 0xed, 0xe0, 0x19, 0xab, 0xcf, 0x6d, 0x18, 0xbe, - 0xbd, 0x72, 0x11, 0xa5, 0x07, 0xd7, 0x30, 0xa8, 0xcc, 0x34, 0xcc, 0x7f, 0xd4, 0x3f, 0xf5, 0x8e, - 0xab, 0xeb, 0x2d, 0x5f, 0x3c, 0xdb, 0x6f, 0x70, 0x19, 0x29, 0xe0, 0x46, 0xf7, 0x15, 0x06, 0x35, - 0x02, 0x86, 0x68, 0x67, 0x6d, 0xcb, 0xcf, 0xd0, 0xef, 0x1a, 0x74, 0x0e, 0x64, 0x1c, 0x2b, 0x04, - 0xa1, 0xa3, 0x58, 0x9c, 0x1b, 0xd5, 0x20, 0x6d, 0x33, 0x81, 0x56, 0x4e, 0x6b, 0x40, 0xc2, 0xda, - 0xe3, 0xba, 0x4e, 0xab, 0x51, 0x26, 0x56, 0x23, 0xd4, 0xac, 0x55, 0x79, 0xca, 0xc6, 0x82, 0xae, - 0x3d, 0xc5, 0x14, 0x62, 0x1c, 0x96, 0x38, 0x0e, 0xad, 0x7a, 0x73, 0x78, 0x1a, 0xd2, 0xb6, 0xfe, - 0x78, 0xd0, 0x39, 0x95, 0xa6, 0xd5, 0xa6, 0x5e, 0x57, 0x28, 0x0b, 0xdf, 0x13, 0xc8, 0x01, 0x53, - 0x38, 0x52, 0x2f, 0x24, 0xf0, 0x8e, 0x0b, 0x86, 0x00, 0x41, 0xe4, 0x43, 0x8f, 0x04, 0x2f, 0x60, - 0x2c, 0xf8, 0x36, 0x63, 0xf5, 0x7d, 0x6d, 0x85, 0x26, 0xa1, 0x3e, 0x76, 0x86, 0xf6, 0xe5, 0x3a, - 0x9a, 0x29, 0x3f, 0x10, 0x66, 0x75, 0x7b, 0x49, 0x98, 0x23, 0xf7, 0x97, 0xb5, 0x76, 0xee, 0x6c, - 0x39, 0x71, 0xc7, 0xe7, 0xe4, 0x26, 0x2c, 0x4b, 0x6b, 0x95, 0xd2, 0x6e, 0xbf, 0x27, 0x6b, 0xd4, - 0x4f, 0x45, 0xb1, 0x64, 0x71, 0x92, 0x90, 0xfb, 0x80, 0xc9, 0x3b, 0xf9, 0x5a, 0xb2, 0x96, 0xc6, - 0x68, 0x37, 0xed, 0x14, 0xe9, 0x0f, 0x7c, 0x78, 0xf5, 0xae, 0xf1, 0x21, 0xfd, 0xbd, 0x06, 0x2b, - 0x2a, 0x4d, 0x90, 0x43, 0xce, 0x0f, 0x36, 0xde, 0x0c, 0x1b, 0xdf, 0x1d, 0xe7, 0xf9, 0xfa, 0xd8, - 0xb8, 0x48, 0x59, 0xc3, 0xd1, 0x88, 0x2d, 0xcc, 0x00, 0x70, 0x41, 0x81, 0xd1, 0x62, 0xab, 0x07, - 0xff, 0x57, 0x0e, 0x1c, 0x6f, 0x20, 0xed, 0x80, 0x03, 0xba, 0x50, 0x03, 0x01, 0x61, 0xe1, 0xde, - 0x99, 0x43, 0x5f, 0x52, 0x3c, 0x24, 0x82, 0xba, 0x78, 0xb0, 0x65, 0xdd, 0xb1, 0xb7, 0xf1, 0x4e, - 0x6c, 0x93, 0x3d, 0x1b, 0x0c, 0xdb, 0xf8, 0xae, 0x49, 0x06, 0xf4, 0xf2, 0x6c, 0xf0, 0x03, 0x4a, - 0xbc, 0x25, 0x76, 0xeb, 0x5c, 0xc6, 0x45, 0x15, 0xe7, 0xce, 0x21, 0xed, 0xd6, 0x15, 0xd7, 0xe9, - 0xc1, 0x59, 0x1d, 0xbb, 0xc7, 0x3a, 0xad, 0x64, 0xa1, 0xb0, 0xe8, 0x51, 0xec, 0x1a, 0x76, 0x3a, - 0xbe, 0xe2, 0x2b, 0x9d, 0x88, 0x39, 0xab, 0x12, 0xc4, 0x77, 0x83, 0xe6, 0x5a, 0xe9, 0x46, 0xa9, - 0xb8, 0x85, 0x68, 0x6c, 0x71, 0x9b, 0x29, 0x66, 0x35, 0xc3, 0x35, 0x75, 0x94, 0x75, 0xa8, 0x38, - 0x46, 0xbc, 0xd7, 0x40, 0x8d, 0x2a, 0x47, 0x3a, 0xb7, 0xdd, 0x11, 0xf2, 0xae, 0x29, 0x93, 0xad, - 0xf0, 0xe6, 0xbd, 0xdf, 0x8b, 0x57, 0x2e, 0x18, 0x74, 0x9a, 0xf3, 0x56, 0x93, 0x2e, 0x3e, 0x37, - 0xb4, 0x76, 0x77, 0xa7, 0xc6, 0xdb, 0xae, 0x2d, 0x76, 0xc1, 0x56, 0xda, 0x88, 0xd7, 0xe7, 0x0d, - 0x58, 0x0b, 0x5d, 0xe7, 0x61, 0xec, 0x7e, 0xdd, 0xb8, 0x52, 0xb8, 0xa2, 0x4b, 0x52, 0x82, 0x7b, - 0x5a, 0xfb, 0xbf, 0x0e, 0x6e, 0xdd, 0xb2, 0x15, 0xa2, 0x75, 0x38, 0xae, 0x78, 0xea, 0x71, 0x28, - 0x5a, 0x2b, 0x59, 0x83, 0x06, 0xa0, 0x69, 0x90, 0xe8, 0xac, 0xa1, 0x20, 0xbc, 0x5a, 0x18, 0xb6, - 0xae, 0x7c, 0x5b, 0x60, 0xb4, 0x58, 0x6c, 0xaf, 0x58, 0x23, 0xd2, 0x79, 0x18, 0x92, 0xfd, 0xc7, - 0x31, 0x79, 0xa4, 0x5a, 0x3e, 0x88, 0xc9, 0x7e, 0xb0, 0xa1, 0xfc, 0xb7, 0x80, 0x6c, 0xe0, 0x41, - 0xb9, 0xd3, 0x79, 0x38, 0x79, 0x6a, 0x13, 0xa8, 0x2c, 0x34, 0xb8, 0xf4, 0xb6, 0x6f, 0xb8, 0x2e, - 0x1d, 0xaf, 0xfe, 0x38, 0xbb, 0x79, 0x77, 0x73, 0x3e, 0x3a, 0xab, 0x6b, 0x05, 0x28, 0x56, 0x53, - 0x5d, 0x79, 0x70, 0x47, 0xe8, 0x1c, 0xd1, 0xa1, 0x9b, 0x70, 0x4d, 0xaf, 0xdb, 0xf2, 0xe6, 0x31, - 0x11, 0xc5, 0x9f, 0x15, 0x2a, 0x08, 0xca, 0xdd, 0x64, 0x93, 0x3a, 0x18, 0xf8, 0x5b, 0x8b, 0x1d, - 0x47, 0x86, 0xcd, 0x6d, 0xdb, 0x1d, 0x77, 0x36, 0xbc, 0x0e, 0x9b, 0x8c, 0x7c, 0xfd, 0x00, 0xc5, - 0x07, 0x2b, 0xba, 0x36, 0x8e, 0x10, 0x89, 0x74, 0xc7, 0x94, 0xf3, 0x29, 0xea, 0x51, 0xd8, 0xe6, - 0xcb, 0xf1, 0x18, 0xb7, 0xdc, 0x36, 0x49, 0x9a, 0x44, 0x68, 0x3f, 0x23, 0xa1, 0xe0, 0x66, 0xd5, - 0x38, 0xc4, 0xd5, 0xbd, 0x77, 0x2c, 0x75, 0xa2, 0x94, 0x9a, 0x49, 0xd1, 0xa3, 0x1e, 0xbc, 0x87, - 0x3a, 0x1d, 0xeb, 0x29, 0x7d, 0xee, 0xfa, 0x38, 0xce, 0x51, 0x22, 0xf9, 0x80, 0x86, 0x29, 0x08, - 0x3a, 0xec, 0x4e, 0x68, 0xa2, 0x19, 0xb6, 0x1b, 0xee, 0x3d, 0x0f, 0x9f, 0xad, 0x88, 0xea, 0x67, - 0xc4, 0x2f, 0xe4, 0x4c, 0xf6, 0xdc, 0x9d, 0x8d, 0xda, 0x57, 0xa5, 0xcd, 0x0e, 0xa8, 0x70, 0x27, - 0xd1, 0x22, 0x95, 0x14, 0xa1, 0x9d, 0xfb, 0x87, 0x6f, 0x2e, 0x45, 0xa9, 0x16, 0x74, 0xfb, 0x04, - 0xf9, 0x49, 0x51, 0x73, 0xa8, 0x29, 0x45, 0x22, 0x41, 0x38, 0x16, 0xe5, 0x60, 0xb6, 0xca, 0xdb, - 0xce, 0x31, 0xcb, 0x2a, 0x54, 0x0b, 0x37, 0xea, 0x0a, 0x22, 0x78, 0x1f, 0x77, 0x2e, 0xbd, 0x54, - 0x85, 0x40, 0xa1, 0x52, 0x8c, 0xfa, 0x3c, 0xc0, 0x05, 0x66, 0xac, 0xbf, 0x13, 0xb9, 0xfb, 0x47, - 0xb7, 0xb5, 0xa8, 0xe3, 0x25, 0x3e, 0xdb, 0xdf, 0xdd, 0xfb, 0x6d, 0x67, 0x7f, 0x77, 0xff, 0x39, - 0x7b, 0x95, 0x69, 0xdc, 0xb4, 0x25, 0xa8, 0x73, 0x98, 0x64, 0x0b, 0xfa, 0x4e, 0xd1, 0xd4, 0xa8, - 0x0b, 0x54, 0x2f, 0x5c, 0xfd, 0x00, 0x3b, 0x94, 0xaa, 0x4b, 0xec, 0x4f, 0x81, 0x31, 0xce, 0xd5, - 0xb8, 0x37, 0xc7, 0x15, 0x5d, 0xe8, 0xde, 0xc5, 0xf9, 0xab, 0xb3, 0xb7, 0xc3, 0xb3, 0x6f, 0xe1, - 0xef, 0x5c, 0x9e, 0x8f, 0x58, 0x5e, 0x9f, 0xe4, 0x0c, 0x44, 0x5d, 0x6b, 0x15, 0x1d, 0x0a, 0x0d, - 0xab, 0x60, 0x86, 0x31, 0xa8, 0x8e, 0x60, 0x53, 0xdc, 0x95, 0x8b, 0x96, 0x06, 0x8c, 0x2c, 0x39, - 0x25, 0xac, 0xfb, 0x9e, 0xc0, 0xdc, 0x27, 0x02, 0xe2, 0x79, 0x5a, 0x52, 0x93, 0x2a, 0x25, 0x09, - 0x51, 0x44, 0xfd, 0x0d, 0xe6, 0xbb, 0x5d, 0xec, 0xb7, 0x4d, 0x6c, 0xe7, 0xa7, 0xba, 0xd8, 0x1e, - 0x95, 0x1a, 0xfc, 0x50, 0xf3, 0x4e, 0x9d, 0x3c, 0x7d, 0xd9, 0xfd, 0x2f, 0x9b, 0x42, 0x3b, 0x5f, - 0xe9, 0x15, 0x00, 0x00 + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x9d, 0x58, 0x6d, 0x73, 0xdb, 0xb8, + 0x11, 0xfe, 0xce, 0x5f, 0x01, 0xe1, 0x66, 0x52, 0xb2, 0xa1, 0x29, 0xdb, 0x97, 0xb9, 0xc9, 0xd9, + 0xa2, 0x5c, 0x3b, 0x76, 0x1a, 0x77, 0xec, 0xd8, 0x63, 0xc9, 0x97, 0x76, 0xae, 0x37, 0x19, 0x8a, + 0x84, 0x44, 0x44, 0x14, 0xc1, 0x03, 0x40, 0x2b, 0x6a, 0x2e, 0xff, 0xbd, 0xcf, 0x82, 0xa4, 0x5e, + 0x1c, 0xfb, 0xae, 0xd7, 0x0f, 0xb6, 0x48, 0xbc, 0x2c, 0x76, 0x9f, 0xdd, 0x7d, 0x76, 0xc1, 0x41, + 0xef, 0xfc, 0xe6, 0xcd, 0xf8, 0x5f, 0xb7, 0x17, 0x2c, 0xb7, 0x8b, 0x62, 0x38, 0xa0, 0xff, 0xac, + 0x48, 0xca, 0x59, 0xcc, 0x45, 0xc9, 0xf1, 0x2e, 0x92, 0x6c, 0x38, 0x58, 0x08, 0x9b, 0x30, 0x2f, + 0x55, 0xa5, 0x15, 0xa5, 0x8d, 0xf9, 0x52, 0x66, 0x36, 0x8f, 0x33, 0xf1, 0x20, 0x53, 0xb1, 0xe7, + 0x5e, 0x42, 0x59, 0x4a, 0x2b, 0x93, 0x62, 0xcf, 0xa4, 0x49, 0x21, 0xe2, 0x83, 0x70, 0x91, 0x7c, + 0x96, 0x8b, 0x7a, 0xb1, 0x7e, 0xaf, 0x8d, 0xd0, 0xee, 0x25, 0x99, 0xe0, 0xbd, 0x54, 0x9c, 0x79, + 0x65, 0xb2, 0x10, 0x31, 0x7f, 0x90, 0x62, 0x59, 0x29, 0x6d, 0x79, 0x7b, 0x4a, 0x9a, 0x27, 0xda, + 0x08, 0x1c, 0x52, 0xdb, 0xe9, 0xde, 0x6b, 0x8c, 0x5a, 0x69, 0x0b, 0x31, 0xbc, 0x96, 0x26, 0x65, + 0x23, 0x61, 0xad, 0x2c, 0x67, 0x66, 0xd0, 0x6f, 0x06, 0x07, 0x26, 0xd5, 0xb2, 0xb2, 0x43, 0xef, + 0x21, 0xd1, 0xac, 0x50, 0xa9, 0xac, 0x42, 0x2b, 0x17, 0x42, 0xd5, 0x36, 0xcc, 0xe2, 0x4c, 0xa5, + 0xf5, 0x02, 0xea, 0x86, 0x98, 0x88, 0x7b, 0x07, 0xf4, 0x53, 0x69, 0x65, 0x55, 0xcc, 0x73, 0x6b, + 0xab, 0x23, 0x7e, 0x3c, 0xad, 0xcb, 0xd4, 0x4a, 0x55, 0xb2, 0x77, 0x7e, 0xf0, 0x65, 0x29, 0xcb, + 0x4c, 0x2d, 0x23, 0x55, 0x89, 0xd2, 0x77, 0x0b, 0xcc, 0x51, 0xbf, 0x3f, 0x2f, 0x55, 0xb4, 0x2c, + 0x44, 0x16, 0xcd, 0x44, 0x7f, 0x2a, 0x12, 0x5b, 0x6b, 0x61, 0xfa, 0xa6, 0x55, 0xa2, 0xff, 0x9d, + 0x11, 0x69, 0xad, 0xa5, 0x5d, 0xed, 0x75, 0x43, 0x3c, 0xf8, 0xba, 0x16, 0x7a, 0xf6, 0x48, 0xe8, + 0x4c, 0xd8, 0xfb, 0xbb, 0x2b, 0x9f, 0xf7, 0x37, 0x8b, 0x43, 0xfe, 0xd1, 0x88, 0x62, 0xba, 0xbd, + 0xeb, 0xfe, 0xb9, 0x5d, 0x75, 0x95, 0x25, 0x56, 0x3c, 0xb9, 0x67, 0x76, 0x99, 0xf9, 0x36, 0xf8, + 0xa2, 0x05, 0xf4, 0x2b, 0x19, 0x29, 0x6b, 0x2f, 0x0a, 0x41, 0xa6, 0x9f, 0xad, 0xdc, 0xd4, 0x66, + 0xa9, 0x34, 0x37, 0x93, 0x4f, 0x5b, 0x8b, 0xed, 0x8b, 0x17, 0x5c, 0x4d, 0x3e, 0x89, 0xd4, 0xf2, + 0x38, 0xb6, 0xab, 0x4a, 0xa8, 0x29, 0x8d, 0xf5, 0x4e, 0xb5, 0x4e, 0x56, 0x91, 0x34, 0xee, 0x77, + 0x47, 0x42, 0xa1, 0x92, 0xec, 0x1f, 0x23, 0xdf, 0x86, 0x22, 0xee, 0xed, 0x07, 0x5f, 0x0a, 0x61, + 0x99, 0x8a, 0xb3, 0x28, 0xd5, 0x80, 0x47, 0xb4, 0xc7, 0xfa, 0xbc, 0xf1, 0x0b, 0x0f, 0x8e, 0x55, + 0x04, 0x73, 0x4f, 0xad, 0xd5, 0x72, 0x52, 0x5b, 0x81, 0x09, 0x9d, 0xf2, 0xd0, 0x06, 0xe1, 0xe3, + 0x71, 0x3a, 0x9b, 0x87, 0xdc, 0x8a, 0xcf, 0xb6, 0xff, 0x29, 0x79, 0x48, 0x3a, 0x01, 0xdf, 0x2c, + 0x4c, 0xcc, 0xaa, 0x84, 0x08, 0x11, 0x84, 0x59, 0x34, 0x51, 0xd9, 0x2a, 0x4a, 0x2a, 0xe0, 0x94, + 0xbd, 0xc9, 0x65, 0x91, 0xf9, 0x8a, 0xd6, 0x27, 0x59, 0x76, 0xf1, 0x00, 0x2d, 0xae, 0xa4, 0x41, + 0xb4, 0x0a, 0xed, 0x73, 0xd2, 0x99, 0x87, 0x7e, 0x10, 0x0f, 0xbf, 0xfc, 0x5d, 0xd8, 0x9f, 0xfc, + 0x20, 0x84, 0xcc, 0xb3, 0x74, 0xfe, 0x56, 0x16, 0x82, 0x82, 0xd0, 0x27, 0x04, 0xf9, 0x24, 0x9d, + 0xa7, 0xd3, 0x19, 0x0f, 0x9e, 0x9d, 0xad, 0xe0, 0x7d, 0x61, 0xe1, 0xb7, 0xe0, 0xeb, 0xd3, 0xe7, + 0x08, 0xad, 0x95, 0x86, 0x79, 0x38, 0x07, 0xa9, 0x62, 0x54, 0x21, 0xa2, 0x42, 0xcd, 0x7c, 0x7e, + 0x41, 0xe3, 0xac, 0x05, 0x0f, 0xae, 0x67, 0x53, 0x88, 0x76, 0x30, 0x20, 0x37, 0x34, 0xe0, 0xba, + 0x6a, 0xc7, 0x81, 0x3e, 0x36, 0x4e, 0xe5, 0xac, 0xd6, 0x89, 0x43, 0xbb, 0x81, 0x81, 0x4d, 0x13, + 0x49, 0x51, 0xf8, 0xef, 0xf2, 0xb2, 0x4c, 0xd5, 0xa2, 0x02, 0xe8, 0x82, 0x55, 0xc9, 0x4c, 0x30, + 0xc4, 0x44, 0xd2, 0x43, 0x2c, 0x6c, 0x39, 0xc8, 0xe4, 0x6a, 0x39, 0x56, 0x89, 0xb1, 0x8d, 0x8f, + 0x0e, 0x82, 0x2f, 0x94, 0x1c, 0x2a, 0x76, 0x56, 0x58, 0x9a, 0x70, 0x6e, 0x91, 0x25, 0x54, 0x7e, + 0x37, 0xbe, 0xbe, 0x8a, 0x2d, 0x6c, 0x49, 0x8b, 0xc4, 0x18, 0x32, 0x84, 0xac, 0xf2, 0xc5, 0x49, + 0x6b, 0xca, 0x11, 0x27, 0x69, 0xf0, 0x42, 0x5a, 0x88, 0x44, 0x8f, 0x9b, 0xd4, 0xf2, 0xdb, 0x14, + 0x73, 0xbe, 0xb1, 0x2b, 0x18, 0x99, 0x94, 0x72, 0xe1, 0xf4, 0x8d, 0x79, 0xa9, 0x4a, 0xb2, 0xac, + 0x59, 0x11, 0x03, 0xae, 0x6e, 0x93, 0xdf, 0x29, 0x88, 0x00, 0xdf, 0x3e, 0x4f, 0x8b, 0x85, 0x7a, + 0xa0, 0xc0, 0x70, 0x07, 0x01, 0xd8, 0xc3, 0x1f, 0xf7, 0xf7, 0xb7, 0xcc, 0xa9, 0x2b, 0x02, 0x8d, + 0x7c, 0x41, 0xf6, 0x74, 0xc6, 0x94, 0x62, 0xc9, 0xfe, 0x79, 0x7d, 0xf5, 0x0e, 0x79, 0x7a, 0x27, + 0x7e, 0xad, 0x85, 0xb1, 0xc7, 0xbf, 0xe3, 0xf8, 0xad, 0xa3, 0xb7, 0xd0, 0xc9, 0xa5, 0xc1, 0xe9, + 0xa6, 0x82, 0xa7, 0xc4, 0x18, 0x71, 0x17, 0xba, 0x11, 0x63, 0x91, 0xe6, 0x66, 0x18, 0xbf, 0x22, + 0x2d, 0x82, 0xdf, 0xf5, 0xf3, 0x46, 0xae, 0xdd, 0x11, 0x4c, 0x32, 0xd2, 0x79, 0xd8, 0xeb, 0x04, + 0x34, 0x9c, 0x72, 0x7b, 0x33, 0x1a, 0xf3, 0x70, 0x2b, 0x9f, 0x9d, 0x72, 0x41, 0x70, 0x4c, 0x16, + 0x95, 0xce, 0xa2, 0xb7, 0x4a, 0x2f, 0xce, 0xe1, 0xd1, 0xe3, 0x36, 0x3b, 0xcb, 0x36, 0xb8, 0x7d, + 0x4e, 0x7e, 0x06, 0xac, 0x11, 0x05, 0x8e, 0xf9, 0x79, 0xff, 0x17, 0x8a, 0x7f, 0xca, 0x0c, 0xcc, + 0x95, 0x01, 0xc6, 0x1f, 0x92, 0xa2, 0x06, 0x97, 0xf2, 0xb0, 0x77, 0xb0, 0x81, 0x2e, 0xcd, 0x45, + 0x3a, 0x7f, 0x5f, 0x2f, 0x36, 0xf9, 0xde, 0xf3, 0x7b, 0x82, 0x4c, 0x89, 0xe6, 0x62, 0x15, 0xc1, + 0x65, 0x69, 0xee, 0xf7, 0x7f, 0xde, 0xdf, 0xfb, 0xf1, 0x97, 0x7e, 0x80, 0xa4, 0xff, 0x99, 0x9f, + 0x41, 0x6f, 0x53, 0x25, 0x29, 0xa5, 0xe2, 0x38, 0x99, 0xe0, 0xff, 0x05, 0x18, 0x1f, 0xa6, 0xf2, + 0x51, 0x2e, 0xa7, 0x16, 0xbf, 0x6f, 0x50, 0x02, 0xb4, 0x2a, 0xf0, 0x74, 0x5a, 0xd0, 0xfb, 0x6d, + 0x02, 0x62, 0xa7, 0xf1, 0xa4, 0x32, 0x57, 0x2a, 0x9d, 0xd3, 0x16, 0xb0, 0xbc, 0x4b, 0xe6, 0x51, + 0x2b, 0xe9, 0x16, 0x91, 0x7a, 0x5f, 0xb5, 0x0f, 0xe7, 0x6a, 0x59, 0x3a, 0xb9, 0x70, 0x0c, 0x7f, + 0xa7, 0x16, 0xb4, 0x00, 0x2c, 0xa3, 0x96, 0x57, 0xc2, 0x1d, 0xe0, 0x9e, 0xdd, 0x6a, 0xf7, 0x74, + 0x27, 0x67, 0xf9, 0x7a, 0xb8, 0xdd, 0x7b, 0x09, 0x87, 0x69, 0x1a, 0x3c, 0x17, 0x94, 0x09, 0xfc, + 0x17, 0x04, 0x73, 0x5a, 0xd4, 0x99, 0x30, 0xfe, 0xda, 0xba, 0x20, 0xf8, 0xed, 0xb7, 0xf6, 0x0d, + 0x69, 0x4b, 0xbf, 0xe7, 0x62, 0x9a, 0xd4, 0x85, 0x45, 0xf2, 0x23, 0x27, 0xb6, 0xd2, 0x65, 0x37, + 0xd7, 0x01, 0x95, 0x7d, 0xc4, 0x38, 0xe0, 0xe2, 0xb2, 0x09, 0x24, 0x4e, 0xb5, 0xe0, 0x23, 0x7f, + 0x69, 0x89, 0x62, 0x9f, 0x5a, 0x11, 0xbc, 0xf4, 0xf9, 0x87, 0xab, 0x8b, 0x73, 0x90, 0xa9, 0xc9, + 0x4e, 0x38, 0xf2, 0x07, 0xab, 0x4d, 0x16, 0x6c, 0x9d, 0x37, 0xf2, 0x1b, 0xd2, 0xb4, 0x71, 0x4b, + 0xf3, 0x28, 0x4b, 0x2e, 0x77, 0x8e, 0xe5, 0xd4, 0xe7, 0xe4, 0xdf, 0x23, 0xa2, 0xe2, 0xc8, 0x95, + 0xaa, 0x54, 0x15, 0x81, 0xab, 0x5e, 0xfb, 0xa1, 0xef, 0xca, 0x5b, 0x4c, 0xab, 0x8b, 0x91, 0x55, + 0x1a, 0x50, 0x92, 0x16, 0x97, 0x56, 0x2c, 0x28, 0xce, 0xd3, 0xcb, 0x8a, 0x3b, 0x9b, 0x9b, 0x65, + 0xd8, 0xbd, 0xa8, 0x40, 0x2c, 0x64, 0x17, 0xbb, 0x56, 0x99, 0x88, 0xd8, 0x2d, 0x52, 0xd8, 0x08, + 0x26, 0xc8, 0xa1, 0x8c, 0x94, 0x64, 0x97, 0xb7, 0xa0, 0x8e, 0x70, 0x47, 0xa2, 0xd9, 0x95, 0x18, + 0x3a, 0x69, 0x88, 0x51, 0x51, 0x18, 0xe1, 0xd4, 0x16, 0xa4, 0x5a, 0x62, 0x73, 0x02, 0x2b, 0x54, + 0x31, 0x76, 0x14, 0xe8, 0x01, 0xfc, 0x83, 0x50, 0x44, 0x08, 0x46, 0xf3, 0x41, 0xda, 0x1c, 0xc1, + 0xcd, 0x83, 0x93, 0xbd, 0x83, 0xa3, 0x07, 0x25, 0x33, 0xb6, 0x1f, 0x44, 0xa6, 0x2a, 0xa4, 0x75, + 0xa3, 0x48, 0x52, 0xe0, 0x3c, 0xb3, 0xf9, 0xf0, 0xf0, 0xc5, 0x0b, 0x7f, 0x5d, 0x90, 0x37, 0xd6, + 0x86, 0xad, 0xb5, 0x8d, 0x15, 0x36, 0xca, 0x95, 0xb1, 0x74, 0xd4, 0x4b, 0x64, 0x15, 0x35, 0x09, + 0x27, 0x80, 0xf4, 0x65, 0xf3, 0x78, 0xc4, 0x01, 0x37, 0x84, 0xbe, 0x54, 0xc8, 0x87, 0xe0, 0x2b, + 0x76, 0x40, 0xe4, 0x0e, 0x91, 0x3f, 0xf2, 0x63, 0xae, 0xc5, 0x74, 0x9d, 0x80, 0xbb, 0x0b, 0x3b, + 0x8b, 0x90, 0xae, 0xdf, 0x90, 0xfd, 0xff, 0x22, 0x65, 0xb3, 0x78, 0x23, 0x89, 0x90, 0x75, 0xa5, + 0xf2, 0x9b, 0xc2, 0xdf, 0x37, 0xd1, 0x27, 0x73, 0x52, 0xc5, 0x3f, 0x70, 0x17, 0x8b, 0x61, 0xa3, + 0x7a, 0x16, 0x8d, 0xa6, 0x51, 0xe2, 0x82, 0x24, 0x7e, 0x62, 0x8b, 0x48, 0xf9, 0x76, 0x18, 0xb5, + 0x2b, 0xd6, 0x99, 0x4d, 0x60, 0x9e, 0x74, 0x80, 0x02, 0x16, 0xe0, 0xe2, 0x30, 0x74, 0x28, 0xd9, + 0xaf, 0xde, 0xa0, 0xdf, 0xb6, 0x4a, 0x03, 0x47, 0xd9, 0xc3, 0xbf, 0xc9, 0x05, 0x81, 0xc8, 0x6a, + 0x5d, 0x80, 0x7b, 0x1d, 0x8b, 0xa7, 0x06, 0x06, 0x1c, 0x63, 0xa1, 0x5b, 0x30, 0xe8, 0x37, 0x9d, + 0x1f, 0xd5, 0x58, 0x94, 0x2e, 0xb2, 0x25, 0xe6, 0x08, 0x5e, 0x34, 0x64, 0x53, 0xd0, 0x94, 0xc7, + 0x24, 0xde, 0xe9, 0xe9, 0xa3, 0xe1, 0xac, 0xe9, 0xe4, 0x46, 0x53, 0xce, 0xd0, 0xc2, 0xe5, 0x0a, + 0x33, 0x15, 0x1c, 0x87, 0xa5, 0x99, 0x7c, 0x60, 0x8e, 0xeb, 0x63, 0x94, 0x1e, 0xe8, 0xb6, 0xdc, + 0x1d, 0xcb, 0x45, 0x51, 0x9d, 0xf1, 0xa1, 0x37, 0x00, 0xb6, 0x16, 0x56, 0x51, 0x17, 0x10, 0xf3, + 0xe6, 0x85, 0xe3, 0xd4, 0x14, 0xc1, 0x35, 0x8f, 0xf9, 0x3b, 0x3a, 0xf6, 0x64, 0xd0, 0x6f, 0x26, + 0xa0, 0x1a, 0x44, 0x0c, 0x9f, 0xde, 0xe3, 0xad, 0x37, 0x9d, 0xd1, 0x26, 0x22, 0xb4, 0xcd, 0xbe, + 0x9d, 0x1d, 0xa6, 0x9e, 0x2c, 0x24, 0x74, 0x1c, 0x25, 0x0f, 0x62, 0xb3, 0x24, 0xd7, 0x9d, 0xf8, + 0xfc, 0x70, 0xe8, 0x8d, 0xda, 0x06, 0x8f, 0xbd, 0x60, 0xf7, 0xae, 0xff, 0x22, 0xba, 0xa8, 0x2b, + 0x60, 0x73, 0x38, 0xec, 0x7a, 0x51, 0x76, 0x7b, 0xf9, 0xfe, 0x88, 0x0d, 0x64, 0x59, 0xd5, 0xb6, + 0x15, 0x5d, 0xc1, 0xb8, 0xa5, 0xd2, 0x19, 0x77, 0x20, 0x61, 0x7e, 0xdd, 0xec, 0xba, 0x67, 0x23, + 0xff, 0x83, 0xc7, 0x57, 0x00, 0x2b, 0xf9, 0xdc, 0xa4, 0x43, 0xf3, 0x26, 0xcb, 0xad, 0x37, 0x55, + 0x82, 0xc5, 0x88, 0x58, 0x62, 0xbe, 0xe1, 0x71, 0x14, 0xa8, 0x00, 0xb2, 0x10, 0x64, 0x48, 0x62, + 0xcc, 0x38, 0xee, 0xfe, 0x2b, 0x8e, 0xa1, 0xc3, 0x17, 0x48, 0x73, 0x14, 0x60, 0xf4, 0xbc, 0x5a, + 0xa6, 0x9c, 0xb9, 0x0e, 0x19, 0x27, 0x6e, 0xa7, 0x7d, 0xc2, 0x5e, 0xb1, 0x4c, 0xce, 0xa4, 0x65, + 0x58, 0x36, 0x01, 0xb1, 0x03, 0x13, 0x0d, 0xf8, 0xb7, 0x5c, 0xb2, 0x4c, 0x34, 0xda, 0x7e, 0xef, + 0xc5, 0x77, 0x3f, 0xbe, 0x7e, 0xfd, 0xfa, 0x98, 0xdd, 0x97, 0xa2, 0x4c, 0xf5, 0xaa, 0xb2, 0x22, + 0x63, 0x56, 0x27, 0xa5, 0x59, 0x48, 0x63, 0x10, 0x80, 0x11, 0x3b, 0x43, 0x0b, 0xa2, 0x41, 0xba, + 0xa5, 0x65, 0xcb, 0x5c, 0x10, 0x91, 0x16, 0x68, 0x23, 0xa9, 0x8b, 0x81, 0x91, 0x21, 0xcb, 0x14, + 0x7b, 0x7f, 0x33, 0x66, 0xa8, 0x0e, 0x6c, 0xa5, 0x6a, 0xcd, 0x26, 0x49, 0x39, 0xc7, 0x24, 0x4d, + 0x28, 0x1d, 0xb2, 0xd1, 0xe5, 0x75, 0xc8, 0x84, 0x4d, 0x23, 0x56, 0xc9, 0xb2, 0xe7, 0x75, 0x2e, + 0xd5, 0x43, 0x2a, 0x22, 0x6c, 0x29, 0x35, 0xa4, 0x19, 0xc3, 0xfc, 0x9b, 0xf1, 0x69, 0xc0, 0x8c, + 0x9a, 0x5a, 0xe8, 0x25, 0x58, 0xd3, 0x04, 0x3f, 0x42, 0xdb, 0xc1, 0x33, 0x51, 0x9f, 0xbb, 0x30, + 0x7c, 0x7f, 0xe3, 0x22, 0x4a, 0x0f, 0x6f, 0x61, 0x50, 0x95, 0x6b, 0x98, 0xff, 0xac, 0x7f, 0x9a, + 0x1d, 0x37, 0xb7, 0x3b, 0xbe, 0xf8, 0xfe, 0xb0, 0xc5, 0x65, 0xac, 0x80, 0x1b, 0xdd, 0x57, 0x18, + 0xd4, 0x08, 0x19, 0xa2, 0x9d, 0x75, 0x2d, 0x3f, 0x43, 0xbf, 0x6b, 0xd0, 0x39, 0x90, 0x71, 0xac, + 0x14, 0x84, 0x8e, 0x62, 0x49, 0x61, 0x54, 0x8b, 0xb4, 0xcd, 0x05, 0x5a, 0x39, 0xad, 0x01, 0x09, + 0xeb, 0x8e, 0xeb, 0x39, 0xad, 0xc6, 0xb9, 0x58, 0x8f, 0x50, 0xb3, 0x56, 0x17, 0x19, 0x9b, 0x08, + 0xba, 0xf6, 0x94, 0x33, 0x88, 0x71, 0x58, 0xe2, 0x38, 0xb4, 0xea, 0xed, 0xe1, 0x59, 0x44, 0xdb, + 0x06, 0x93, 0xa1, 0x77, 0x2e, 0x4d, 0xa7, 0x4d, 0xb3, 0xae, 0x54, 0x16, 0xbe, 0x27, 0x90, 0x43, + 0xa6, 0x70, 0xa4, 0x5e, 0x4a, 0xe0, 0x9d, 0x94, 0x0c, 0x01, 0x82, 0xc8, 0x87, 0x1e, 0x29, 0x5e, + 0xc0, 0x58, 0xf0, 0x6d, 0xce, 0x9a, 0xfb, 0xda, 0x1a, 0x4d, 0x42, 0x7d, 0xe2, 0x0c, 0x1d, 0xc8, + 0x4d, 0x34, 0x53, 0x7e, 0x20, 0xcc, 0x9a, 0xf6, 0x92, 0x30, 0x47, 0xee, 0xaf, 0x1a, 0xed, 0xdc, + 0xd9, 0x72, 0xea, 0x8e, 0x2f, 0xc8, 0x4d, 0x58, 0x96, 0x35, 0x2a, 0x65, 0xbd, 0x41, 0x5f, 0x36, + 0xa8, 0x9f, 0x8b, 0x72, 0xc5, 0x92, 0x34, 0x25, 0xf7, 0x01, 0x93, 0x0f, 0xf2, 0xad, 0x64, 0x1d, + 0x8d, 0xd1, 0x6e, 0xda, 0x29, 0xb2, 0x3f, 0xf0, 0xe1, 0xcd, 0x87, 0xd6, 0x87, 0xf4, 0xf7, 0x16, + 0xac, 0xa8, 0x34, 0x41, 0x0e, 0x39, 0x7f, 0xb0, 0xf1, 0x6e, 0xd4, 0xfa, 0xee, 0xb4, 0x28, 0x36, + 0xc7, 0x26, 0x65, 0xc6, 0x5a, 0x8e, 0x46, 0x6c, 0x61, 0x06, 0x80, 0x0b, 0x0a, 0x8c, 0x0e, 0x5b, + 0x3d, 0xfc, 0xbf, 0x72, 0xe0, 0x74, 0x0b, 0x69, 0x07, 0x1c, 0xd0, 0x85, 0x1a, 0x08, 0x08, 0x0b, + 0xf7, 0xce, 0x1d, 0xfa, 0x92, 0xe2, 0x21, 0x15, 0xd4, 0xc5, 0x83, 0x2d, 0x9b, 0x8e, 0xbd, 0x8b, + 0x77, 0x62, 0x9b, 0xfc, 0xfb, 0xe1, 0xa8, 0x8b, 0xef, 0x86, 0x64, 0x40, 0x2f, 0xdf, 0x0f, 0xff, + 0x80, 0x12, 0xef, 0x89, 0xdd, 0xbc, 0xeb, 0xa4, 0xac, 0x93, 0xc2, 0x39, 0xa4, 0xdb, 0xba, 0xe6, + 0x3a, 0x3d, 0xbc, 0x68, 0x62, 0xf7, 0x54, 0x67, 0xb5, 0x2c, 0x15, 0x16, 0x3d, 0x8b, 0x5d, 0xcb, + 0x4e, 0xa7, 0x37, 0x7c, 0xad, 0x13, 0x31, 0x67, 0x5d, 0x81, 0xf8, 0xee, 0xd0, 0x5c, 0x2b, 0xdd, + 0x2a, 0x95, 0x74, 0x10, 0x4d, 0x2c, 0x6e, 0x33, 0xe5, 0xbc, 0x61, 0xb8, 0xb6, 0x8e, 0x32, 0x8f, + 0x8a, 0x63, 0xcc, 0xfb, 0x2d, 0xd4, 0xa8, 0x72, 0xa4, 0x73, 0xd7, 0x1d, 0x21, 0xef, 0xda, 0x32, + 0xd9, 0x09, 0x6f, 0xdf, 0x07, 0xfd, 0x64, 0xed, 0x82, 0xa1, 0xd7, 0x9e, 0xb7, 0x9e, 0x74, 0xf1, + 0xb9, 0xa5, 0xb5, 0xbb, 0x3b, 0xb5, 0xde, 0x76, 0x6d, 0xb1, 0x0b, 0xb6, 0xca, 0xc6, 0xbc, 0x39, + 0x6f, 0xc8, 0x3a, 0xe8, 0xbc, 0xa7, 0xb1, 0xfb, 0xcb, 0xd6, 0x95, 0xc2, 0x15, 0x5d, 0x92, 0x12, + 0x3e, 0xd2, 0x3a, 0xf8, 0xcb, 0xf0, 0xde, 0x2d, 0x5b, 0x23, 0xda, 0x84, 0xe3, 0x9a, 0xa7, 0x9e, + 0x87, 0xa2, 0xb3, 0x92, 0xb5, 0x68, 0x00, 0x9a, 0x16, 0x09, 0x6f, 0x03, 0x05, 0xe1, 0xd5, 0xc1, + 0xb0, 0x73, 0xe5, 0xdb, 0x01, 0xa3, 0xc3, 0x62, 0x77, 0xc5, 0x06, 0x11, 0xef, 0x69, 0x48, 0x0e, + 0x9f, 0xc7, 0xe4, 0x99, 0x6a, 0xf9, 0x24, 0x26, 0x87, 0xe1, 0x96, 0xf2, 0xdf, 0x02, 0xb2, 0x85, + 0x07, 0xe5, 0x8e, 0xf7, 0x74, 0xf2, 0x34, 0x26, 0x50, 0x59, 0x68, 0x71, 0xe9, 0xef, 0xde, 0x70, + 0x5d, 0x3a, 0xde, 0xfc, 0x74, 0x71, 0xf7, 0xe1, 0xee, 0x72, 0x7c, 0xd1, 0xd4, 0x0a, 0x50, 0xac, + 0xa6, 0xba, 0xf2, 0xe4, 0x8e, 0xc8, 0x39, 0xc2, 0xa3, 0x9b, 0x70, 0x43, 0xaf, 0xbb, 0xf2, 0x16, + 0x09, 0x11, 0xc5, 0xaf, 0x35, 0x2a, 0x08, 0xca, 0xdd, 0x74, 0x9b, 0x3a, 0x18, 0xf8, 0x5b, 0x8b, + 0x3d, 0x47, 0x86, 0xed, 0x6d, 0xdb, 0x1d, 0x77, 0x31, 0xba, 0x8d, 0xda, 0x8c, 0x7c, 0xfb, 0x04, + 0xc5, 0x87, 0x6b, 0xba, 0x36, 0x8e, 0x10, 0x89, 0x74, 0x27, 0x94, 0xf3, 0x19, 0xea, 0x51, 0xd4, + 0xe5, 0xcb, 0xe9, 0x04, 0xb7, 0xdc, 0x2e, 0x49, 0xda, 0x44, 0xe8, 0x3e, 0x23, 0xa1, 0xe0, 0xe6, + 0xf5, 0x24, 0xc2, 0xd5, 0xbd, 0x7f, 0x2a, 0x75, 0xaa, 0x94, 0x9a, 0x4b, 0xd1, 0xa7, 0x1e, 0xbc, + 0x8f, 0x3a, 0x9d, 0xe8, 0x19, 0x7d, 0xee, 0xfa, 0x38, 0x29, 0x50, 0x22, 0xf9, 0x90, 0x86, 0x29, + 0x08, 0x3c, 0xf6, 0x20, 0x34, 0xd1, 0x0c, 0xdb, 0x8f, 0x0e, 0x5e, 0x45, 0xaf, 0xd6, 0x44, 0xf5, + 0x67, 0xc4, 0x2f, 0xe5, 0x5c, 0xf6, 0xdd, 0x9d, 0x8d, 0xda, 0x57, 0xa5, 0xcd, 0x1e, 0xa8, 0x70, + 0x2f, 0xd5, 0x22, 0x93, 0x14, 0xa1, 0xde, 0xe3, 0xc3, 0xb7, 0x97, 0xa2, 0x54, 0x0b, 0xba, 0x7d, + 0x82, 0xfc, 0xa4, 0x68, 0x38, 0xd4, 0x54, 0x22, 0x95, 0x20, 0x1c, 0x8b, 0x72, 0x30, 0x5f, 0xe7, + 0xad, 0x77, 0xca, 0xf2, 0x1a, 0xd5, 0xc2, 0x8d, 0xba, 0x82, 0x08, 0xde, 0xc7, 0x9d, 0x4b, 0xaf, + 0x54, 0x29, 0x50, 0xa8, 0x14, 0xa3, 0x3e, 0x0f, 0x70, 0x81, 0x19, 0x9b, 0xef, 0x44, 0xee, 0xfe, + 0xd1, 0xeb, 0x2c, 0xf2, 0xfc, 0x34, 0x60, 0x87, 0xfb, 0x07, 0x3f, 0xec, 0x1d, 0xee, 0x1f, 0xbe, + 0x62, 0x6f, 0x72, 0x8d, 0x9b, 0xb6, 0x04, 0x75, 0x8e, 0xd2, 0x7c, 0x49, 0xdf, 0x29, 0xda, 0x1a, + 0x75, 0x85, 0xea, 0x85, 0xab, 0x1f, 0x60, 0x87, 0x52, 0x4d, 0x89, 0xfd, 0x53, 0x60, 0x4c, 0x0a, + 0x35, 0xe9, 0x2f, 0x70, 0x45, 0x17, 0xba, 0x7f, 0x75, 0xf9, 0xe6, 0xe2, 0xfd, 0xe8, 0xe2, 0x5b, + 0xf8, 0xbd, 0xeb, 0xcb, 0x31, 0x2b, 0x9a, 0x93, 0x9c, 0x81, 0xa8, 0x6b, 0x9d, 0xa2, 0x23, 0xa1, + 0x61, 0x15, 0xcc, 0x30, 0x06, 0xd5, 0x11, 0x6c, 0x8a, 0xbb, 0x72, 0xd9, 0xd1, 0x80, 0x91, 0x15, + 0xa7, 0x84, 0x75, 0xdf, 0x13, 0x98, 0xfb, 0x44, 0x40, 0x3c, 0x4f, 0x4b, 0x1a, 0x52, 0xa5, 0x24, + 0x21, 0x8a, 0x68, 0xbe, 0xc1, 0xfc, 0x6e, 0x17, 0xfb, 0x6d, 0x13, 0xeb, 0xfd, 0xa9, 0x2e, 0xb6, + 0x4f, 0xa5, 0x06, 0x3f, 0xd4, 0xbc, 0x53, 0x27, 0x4f, 0x5f, 0x76, 0xff, 0x0b, 0x1c, 0x5a, 0x5c, + 0xe0, 0xe9, 0x15, 0x00, 0x00 }; diff --git a/wled00/wled.h b/wled00/wled.h index 5834025c6..5d4cca4e9 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2404040 +#define VERSION 2405180 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG From a1c348b36551587cc5917085562216ea31b369ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 05:40:22 +0000 Subject: [PATCH 02/12] --- updated-dependencies: - dependency-name: requests dependency-type: indirect ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c56efad49..9446fad07 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,7 +40,7 @@ pyelftools==0.29 # via platformio pyserial==3.5 # via platformio -requests==2.31.0 +requests==2.32.0 # via platformio semantic-version==2.10.0 # via platformio From 9cfb56d9c6cfc3303dceab09b6af0959cc5e43e8 Mon Sep 17 00:00:00 2001 From: wesleygas Date: Tue, 4 Jun 2024 21:39:31 -0300 Subject: [PATCH 03/12] Add LD2410 sensor usermod --- usermods/LD2410_v2/readme.md | 33 ++++ usermods/LD2410_v2/usermod_ld2410.h | 237 ++++++++++++++++++++++++++++ wled00/const.h | 1 + wled00/usermods_list.cpp | 8 + 4 files changed, 279 insertions(+) create mode 100644 usermods/LD2410_v2/readme.md create mode 100644 usermods/LD2410_v2/usermod_ld2410.h diff --git a/usermods/LD2410_v2/readme.md b/usermods/LD2410_v2/readme.md new file mode 100644 index 000000000..af6cf014d --- /dev/null +++ b/usermods/LD2410_v2/readme.md @@ -0,0 +1,33 @@ +# BH1750 usermod + +> This usermod requires a second UART and was only tested on the ESP32 + + +This usermod will read from a LD2410 movement/presence sensor. + +The movement and presence state are displayed in both the Info section of the web UI, as well as published to the `/movement` and `/stationary` MQTT topics respectively. + +## Dependencies +- Libraries + - `ncmreynolds/ld2410@^0.1.3` + - This must be added under `lib_deps` in your `platformio.ini` (or `platformio_override.ini`). +- Data is published over MQTT - make sure you've enabled the MQTT sync interface. + +## Compilation + +To enable, compile with `USERMOD_LD2410` defined (e.g. in `platformio_override.ini`) +```ini +[env:usermod_USERMOD_LD2410_esp32dev] +extends = env:esp32dev +build_flags = + ${common.build_flags_esp32} + -D USERMOD_LD2410 +lib_deps = + ${esp32.lib_deps} + ncmreynolds/ld2410@^0.1.3 +``` + +### Configuration Options +The Usermod screen allows you to: +- enable/disable the usermod +- Configure the RX/TX pins \ No newline at end of file diff --git a/usermods/LD2410_v2/usermod_ld2410.h b/usermods/LD2410_v2/usermod_ld2410.h new file mode 100644 index 000000000..4d96b32ff --- /dev/null +++ b/usermods/LD2410_v2/usermod_ld2410.h @@ -0,0 +1,237 @@ +#warning **** Included USERMOD_LD2410 **** + +#ifndef WLED_ENABLE_MQTT +#error "This user mod requires MQTT to be enabled." +#endif + +#pragma once + +#include "wled.h" +#include + +class LD2410Usermod : public Usermod { + + private: + + bool enabled = true; + bool initDone = false; + bool sensorFound = false; + unsigned long lastTime = 0; + unsigned long last_mqtt_sent = 0; + + int8_t default_uart_rx = 19; + int8_t default_uart_tx = 18; + + + String mqttMovementTopic = F(""); + String mqttStationaryTopic = F(""); + bool mqttInitialized = false; + bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages + + + ld2410 radar; + bool stationary_detected = false; + bool last_stationary_state = false; + bool movement_detected = false; + bool last_movement_state = false; + + // These config variables have defaults set inside readFromConfig() + int8_t uart_rx_pin; + int8_t uart_tx_pin; + + // string that are used multiple time (this will save some flash memory) + static const char _name[]; + static const char _enabled[]; + + void publishMqtt(const char* topic, const char* state, bool retain); // example for publishing MQTT message + + void _mqttInitialize() + { + mqttMovementTopic = String(mqttDeviceTopic) + F("/ld2410/movement"); + mqttStationaryTopic = String(mqttDeviceTopic) + F("/ld2410/stationary"); + if (HomeAssistantDiscovery){ + _createMqttSensor(F("Movement"), mqttMovementTopic, F("motion"), F("")); + _createMqttSensor(F("Stationary"), mqttStationaryTopic, F("occupancy"), F("")); + } + } + + // Create an MQTT Sensor for Home Assistant Discovery purposes, this includes a pointer to the topic that is published to in the Loop. + void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement) + { + String t = String(F("homeassistant/binary_sensor/")) + mqttClientID + F("/") + name + F("/config"); + + StaticJsonDocument<600> doc; + + doc[F("name")] = String(serverDescription) + F(" Module"); + doc[F("state_topic")] = topic; + doc[F("unique_id")] = String(mqttClientID) + name; + if (unitOfMeasurement != "") + doc[F("unit_of_measurement")] = unitOfMeasurement; + if (deviceClass != "") + doc[F("device_class")] = deviceClass; + doc[F("expire_after")] = 1800; + doc[F("payload_off")] = "OFF"; + doc[F("payload_on")] = "ON"; + + JsonObject device = doc.createNestedObject(F("device")); // attach the sensor to the same device + device[F("name")] = serverDescription; + device[F("identifiers")] = "wled-sensor-" + String(mqttClientID); + device[F("manufacturer")] = F("WLED"); + device[F("model")] = F("FOSS"); + device[F("sw_version")] = versionString; + + String temp; + serializeJson(doc, temp); + DEBUG_PRINTLN(t); + DEBUG_PRINTLN(temp); + + mqtt->publish(t.c_str(), 0, true, temp.c_str()); + } + + public: + + inline bool isEnabled() { return enabled; } + + void setup() { + Serial1.begin(256000, SERIAL_8N1, uart_rx_pin, uart_tx_pin); + Serial.print(F("\nLD2410 radar sensor initialising: ")); + if(radar.begin(Serial1)){ + Serial.println(F("OK")); + } else { + Serial.println(F("not connected")); + } + initDone = true; + } + + + void loop() { + // NOTE: on very long strips strip.isUpdating() may always return true so update accordingly + if (!enabled || strip.isUpdating()) return; + radar.read(); + unsigned long curr_time = millis(); + if(curr_time - lastTime > 1000) //Try to Report every 1000ms + { + lastTime = curr_time; + sensorFound = radar.isConnected(); + if(!sensorFound) return; + stationary_detected = radar.presenceDetected(); + if(stationary_detected != last_stationary_state){ + if (WLED_MQTT_CONNECTED){ + publishMqtt("/ld2410/stationary", stationary_detected ? "ON":"OFF", false); + last_stationary_state = stationary_detected; + } + } + movement_detected = radar.movingTargetDetected(); + if(movement_detected != last_movement_state){ + if (WLED_MQTT_CONNECTED){ + publishMqtt("/ld2410/movement", movement_detected ? "ON":"OFF", false); + last_movement_state = movement_detected; + } + } + // If there hasn't been any activity, send current state to confirm sensor is alive + if(curr_time - last_mqtt_sent > 1000*60*5 && WLED_MQTT_CONNECTED){ + publishMqtt("/ld2410/stationary", stationary_detected ? "ON":"OFF", false); + publishMqtt("/ld2410/movement", movement_detected ? "ON":"OFF", false); + } + } + } + + + void addToJsonInfo(JsonObject& root) + { + // if "u" object does not exist yet wee need to create it + JsonObject user = root[F("u")]; + if (user.isNull()) user = root.createNestedObject(F("u")); + + JsonArray ld2410_sta_json = user.createNestedArray(F("LD2410 Stationary")); + JsonArray ld2410_mov_json = user.createNestedArray(F("LD2410 Movement")); + if (!enabled){ + ld2410_sta_json.add(F("disabled")); + ld2410_mov_json.add(F("disabled")); + } else if(!sensorFound){ + ld2410_sta_json.add(F("LD2410")); + ld2410_sta_json.add(" Not Found"); + } else { + ld2410_sta_json.add("Sta "); + ld2410_sta_json.add(stationary_detected ? "ON":"OFF"); + ld2410_mov_json.add("Mov "); + ld2410_mov_json.add(movement_detected ? "ON":"OFF"); + } + } + + void addToConfig(JsonObject& root) + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = enabled; + //save these vars persistently whenever settings are saved + top["uart_rx_pin"] = default_uart_rx; + top["uart_tx_pin"] = default_uart_tx; + } + + + bool readFromConfig(JsonObject& root) + { + // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor + // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) + + JsonObject top = root[FPSTR(_name)]; + + bool configComplete = !top.isNull(); + if (!configComplete) + { + DEBUG_PRINT(FPSTR(_name)); + DEBUG_PRINT(F("LD2410")); + DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); + return false; + } + + configComplete &= getJsonValue(top["uart_rx_pin"], uart_rx_pin, default_uart_rx); + configComplete &= getJsonValue(top["uart_tx_pin"], uart_tx_pin, default_uart_tx); + + return configComplete; + } + + +#ifndef WLED_DISABLE_MQTT + /** + * onMqttConnect() is called when MQTT connection is established + */ + void onMqttConnect(bool sessionPresent) { + // do any MQTT related initialisation here + if(!radar.isConnected()) return; + publishMqtt("/ld2410/status", "I am alive!", false); + if (!mqttInitialized) + { + _mqttInitialize(); + mqttInitialized = true; + } + } +#endif + + uint16_t getId() + { + return USERMOD_ID_LD2410; + } +}; + + +// add more strings here to reduce flash memory usage +const char LD2410Usermod::_name[] PROGMEM = "LD2410Usermod"; +const char LD2410Usermod::_enabled[] PROGMEM = "enabled"; + + +// implementation of non-inline member methods + +void LD2410Usermod::publishMqtt(const char* topic, const char* state, bool retain) +{ +#ifndef WLED_DISABLE_MQTT + //Check if MQTT Connected, otherwise it will crash + if (WLED_MQTT_CONNECTED) { + last_mqtt_sent = millis(); + char subuf[64]; + strcpy(subuf, mqttDeviceTopic); + strcat(subuf, topic); + mqtt->publish(subuf, 0, retain, state); + } +#endif +} diff --git a/wled00/const.h b/wled00/const.h index 801d7e45d..9a30000ba 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -181,6 +181,7 @@ #define USERMOD_ID_BME68X 49 //Usermod "usermod_bme68x.h #define USERMOD_ID_INA226 50 //Usermod "usermod_ina226.h" #define USERMOD_ID_AHT10 51 //Usermod "usermod_aht10.h" +#define USERMOD_ID_LD2410 52 //Usermod "usermod_ld2410.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 45824ff3b..a39aa5f41 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -230,6 +230,10 @@ #include "../usermods/INA226_v2/usermod_ina226.h" #endif +#ifdef USERMOD_LD2410 +#include "../usermods/LD2410_v2/usermod_ld2410.h" +#endif + void registerUsermods() { /* @@ -446,4 +450,8 @@ void registerUsermods() #ifdef USERMOD_INA226 usermods.add(new UsermodINA226()); #endif + + #ifdef USERMOD_LD2410 + usermods.add(new LD2410Usermod()); + #endif } From 8a1df9afee00016ec2f53020dc1973b62e759ca1 Mon Sep 17 00:00:00 2001 From: xkvmoto <37225828+xkvmoto@users.noreply.github.com> Date: Fri, 7 Jun 2024 19:50:45 +0200 Subject: [PATCH 04/12] Update usermod_sn_photoresistor.h Allow to configure the analog input pin for the Photoresistor. --- usermods/SN_Photoresistor/usermod_sn_photoresistor.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/usermods/SN_Photoresistor/usermod_sn_photoresistor.h b/usermods/SN_Photoresistor/usermod_sn_photoresistor.h index c09b5d907..45cdb66ad 100644 --- a/usermods/SN_Photoresistor/usermod_sn_photoresistor.h +++ b/usermods/SN_Photoresistor/usermod_sn_photoresistor.h @@ -3,7 +3,9 @@ #include "wled.h" //Pin defaults for QuinLed Dig-Uno (A0) +#ifndef PHOTORESISTOR_PIN #define PHOTORESISTOR_PIN A0 +#endif // the frequency to check photoresistor, 10 seconds #ifndef USERMOD_SN_PHOTORESISTOR_MEASUREMENT_INTERVAL @@ -207,4 +209,4 @@ const char Usermod_SN_Photoresistor::_readInterval[] PROGMEM = "read-interval-s" const char Usermod_SN_Photoresistor::_referenceVoltage[] PROGMEM = "supplied-voltage"; const char Usermod_SN_Photoresistor::_resistorValue[] PROGMEM = "resistor-value"; const char Usermod_SN_Photoresistor::_adcPrecision[] PROGMEM = "adc-precision"; -const char Usermod_SN_Photoresistor::_offset[] PROGMEM = "offset"; \ No newline at end of file +const char Usermod_SN_Photoresistor::_offset[] PROGMEM = "offset"; From 017169f1a10e879335fbf12a920ddf516d165976 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Jun 2024 12:12:05 +0000 Subject: [PATCH 05/12] Bump braces from 3.0.2 to 3.0.3 Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7b57cebc0..61c38cb3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -168,11 +168,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -628,9 +628,9 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, From 166eeacab9da93dffa2454b1a9d86d4aa126394d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 01:26:00 +0000 Subject: [PATCH 06/12] Bump urllib3 from 1.26.18 to 1.26.19 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.18 to 1.26.19. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/1.26.19/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.18...1.26.19) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c56efad49..b08bbcc97 100644 --- a/requirements.txt +++ b/requirements.txt @@ -52,7 +52,7 @@ tabulate==0.9.0 # via platformio typing-extensions==4.11.0 # via starlette -urllib3==1.26.18 +urllib3==1.26.19 # via requests uvicorn==0.20.0 # via platformio From 17e1975dd826a2d1cec8e0100f332a007e3691be Mon Sep 17 00:00:00 2001 From: Rory Hayes Date: Tue, 18 Jun 2024 16:07:46 -0700 Subject: [PATCH 07/12] add ETH support for LILYGO-POE-Pro Also includes minor spelling corrections for CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- wled00/const.h | 3 ++- wled00/data/settings_wifi.htm | 1 + wled00/network.cpp | 11 +++++++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f813999bb..cd50b133d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ You are all set if you have enabled `Editor: Detect Indentation` in VS Code. #### Blocks -Whether the opening bracket of e.g. an `if` block is in the same line as the condition or in a separate line is up to your discretion. If there is only one statement, leaving out block braches is acceptable. +Whether the opening bracket of e.g. an `if` block is in the same line as the condition or in a separate line is up to your discretion. If there is only one statement, leaving out block brackets is acceptable. Good: ```cpp @@ -49,7 +49,7 @@ if (a == b) doStuff(a); ``` There should always be a space between a keyword and its condition and between the condition and brace. -Within the condition, no space should be between the paranthesis and variables. +Within the condition, no space should be between the parenthesis and variables. Spaces between variables and operators are up to the authors discretion. There should be no space between function names and their argument parenthesis. diff --git a/wled00/const.h b/wled00/const.h index 08f7386af..e871fc3c2 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -328,7 +328,7 @@ #define BTN_TYPE_TOUCH_SWITCH 9 //Ethernet board types -#define WLED_NUM_ETH_TYPES 12 +#define WLED_NUM_ETH_TYPES 13 #define WLED_ETH_NONE 0 #define WLED_ETH_WT32_ETH01 1 @@ -342,6 +342,7 @@ #define WLED_ETH_ABCWLEDV43ETH 9 #define WLED_ETH_SERG74 10 #define WLED_ETH_ESP32_POE_WROVER 11 +#define WLED_ETH_LILYGO_T_POE_PRO 12 //Hue error codes #define HUE_ERROR_INACTIVE 0 diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 3c15d5a86..1042838a2 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -248,6 +248,7 @@ Static subnet mask:
+ diff --git a/wled00/network.cpp b/wled00/network.cpp index 2ae38f799..06860cf4d 100644 --- a/wled00/network.cpp +++ b/wled00/network.cpp @@ -133,6 +133,17 @@ const ethernet_settings ethernetBoards[] = { 18, // eth_mdio, ETH_PHY_LAN8720, // eth_type, ETH_CLOCK_GPIO0_OUT // eth_clk_mode + }, + + // LILYGO T-POE Pro + // https://github.com/Xinyuan-LilyGO/LilyGO-T-ETH-Series/blob/master/schematic/T-POE-PRO.pdf + { + 0, // eth_address, + 5, // eth_power, + 23, // eth_mdc, + 18, // eth_mdio, + ETH_PHY_LAN8720, // eth_type, + ETH_CLOCK_GPIO0_OUT // eth_clk_mode } }; #endif From c540ec54176bbc607f77046a9653a3a67903b07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sat, 29 Jun 2024 16:17:46 +0200 Subject: [PATCH 08/12] Update readme.md --- usermods/LD2410_v2/readme.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/usermods/LD2410_v2/readme.md b/usermods/LD2410_v2/readme.md index af6cf014d..40463d4b0 100644 --- a/usermods/LD2410_v2/readme.md +++ b/usermods/LD2410_v2/readme.md @@ -30,4 +30,7 @@ lib_deps = ### Configuration Options The Usermod screen allows you to: - enable/disable the usermod -- Configure the RX/TX pins \ No newline at end of file +- Configure the RX/TX pins + +## Change log +- 2024-06 Created by @wesleygas (https://github.com/wesleygas/) From 7b248c8fb202aba6045dd7daa883460c7dc898b6 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 29 Jun 2024 16:36:27 +0200 Subject: [PATCH 09/12] WLED 0.15.0-b4 release - LED settings update (limit to available outputs) - minor fixes --- CHANGELOG.md | 38 + package-lock.json | 4 +- package.json | 2 +- platformio_override.sample.ini | 29 +- .../usermod_PIR_sensor_switch.h | 4 + .../usermod_v2_auto_save.h | 4 +- wled00/FX_fcn.cpp | 3670 ++++++++--------- wled00/bus_manager.h | 4 +- wled00/const.h | 52 +- wled00/data/index.js | 8 +- wled00/data/settings_leds.htm | 97 +- wled00/improv.cpp | 2 +- wled00/pin_manager.cpp | 4 + wled00/wled.h | 4 +- wled00/xml.cpp | 4 +- 15 files changed, 2021 insertions(+), 1905 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 606d9240d..63017f153 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ ## WLED changelog +#### Build 2406290 +- WLED 0.15.0-b4 release +- Add LD2410 sensor usermod (#4013 by @wesleygas) +- LED settings bus management update (WARNING only allow available outputs) +- Add ETH support for LILYGO-POE-Pro (#4030 by @rorosaurus) +- Update usermod_sn_photoresistor (#4017 by @xkvmoto) +- Several internal fixes and optimisations + - move LED_BUILTIN handling to BusManager class + - reduce max panels (web server limitation) + - edit WiFi TX power (ESP32) + - keep current ledmap ID in UI + - limit outputs in UI based on length + - wifi.ap addition to JSON Info (JSON API) + - relay pin init bugfix + - file editor button in UI + - ESP8266: update was restarting device on some occasions + - a bit of throttling in UI (for ESP8266) + +#### Build 2406120 +- Update NeoPixelBus to v2.8.0 +- Increased LED outputs one ESP32 using parallel I2S (up to 17) + - use single/mono I2S + 4x RMT for 5 outputs or less + - use parallel x8 I2S + 8x RMT for >5 outputs (limit of 300 LEDs per output) +- Fixed code of Smartnest and updated documentation (#4001 by @DevilPro1) +- ESP32-S3 WiFi fix (#4010 by @cstruck) +- TetrisAI usermod fix (#3897 by @muebau) +- ESP-NOW usermod hook +- Update wled.h regarding OTA Password (#3993 by @gsieben) +- Usermod BME68X Sensor Implementation (#3994 by @gsieben) +- Add a usermod for AHT10, AHT15 and AHT20 temperature/humidity sensors (#3977 by @LordMike) +- Update Battery usermod documentation (#3968 by @adamsthws) +- Add INA226 usermod for reading current and power over i2c (#3986 by @LordMike) +- Bugfixes: #3991 +- Several internal fixes and optimisations (WARNING: some effects may be broken that rely on overflow/narrow width) + - replace uint8_t and uint16_t with unsigned + - replace in8_t and int16_t with int + - reduces code by 1kB + #### Build 2405180 - Official 0.15.0-b3 release - Merge 0.14.3 fixes diff --git a/package-lock.json b/package-lock.json index b9dc5e0e3..38f2099d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.15.0-b3", + "version": "0.15.0-b4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "wled", - "version": "0.15.0-b3", + "version": "0.15.0-b4", "license": "ISC", "dependencies": { "clean-css": "^5.3.3", diff --git a/package.json b/package.json index b19ecc48a..e3c12629c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.15.0-b3", + "version": "0.15.0-b4", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 6becc5d7d..be959e46a 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -10,7 +10,7 @@ default_envs = WLED_tasmota_1M # define as many as you need #---------- # SAMPLE #---------- -[env:WLED_tasmota_1M] +[env:WLED_generic8266_1M] extends = env:esp01_1m_full # when you want to extend the existing environment (define only updated options) ; board = esp01_1m # uncomment when ou need different board ; platform = ${common.platform_wled_default} # uncomment and change when you want particular platform @@ -26,9 +26,9 @@ lib_deps = ${esp8266.lib_deps} ; adafruit/Adafruit BME280 Library@^2.2.2 ; Wire ; robtillaart/SHT85@~0.3.3 -; gmag11/QuickESPNow ;@ 0.6.2 +; ;gmag11/QuickESPNow @ ~0.7.0 # will also load QuickDebug ; https://github.com/blazoncek/QuickESPNow.git#optional-debug ;; exludes debug library -; https://github.com/kosme/arduinoFFT#develop @ 2.0.1 ;; used for USERMOD_AUDIOREACTIVE +; ${esp32.AR_lib_deps} ;; used for USERMOD_AUDIOREACTIVE build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} ${esp8266.build_flags} ; @@ -51,6 +51,11 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; -D WLED_DISABLE_ESPNOW ; -D WLED_DISABLE_BROWNOUT_DET ; +; enable optional built-in features +; -D WLED_ENABLE_PIXART +; -D WLED_ENABLE_USERMOD_PAGE # if created +; -D WLED_ENABLE_DMX +; ; PIN defines - uncomment and change, if needed: ; -D LEDPIN=2 ; or use this for multiple outputs @@ -64,6 +69,8 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; ; Limit max buses ; -D WLED_MAX_BUSSES=2 +; -D WLED_MAX_ANALOG_CHANNELS=3 # only 3 PWM HW pins available +; -D WLED_MAX_DIGITAL_CHANNELS=2 # only 2 HW accelerated pins available ; ; Configure default WiFi ; -D CLIENT_SSID='"MyNetwork"' @@ -128,12 +135,12 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; ; Use PIR sensor usermod and configure it to use GPIO4 and timer of 60s ; -D USERMOD_PIRSWITCH -; -D PIR_SENSOR_PIN=4 +; -D PIR_SENSOR_PIN=4 # use -1 to disable usermod ; -D PIR_SENSOR_OFF_SEC=60 +; -D PIR_SENSOR_MAX_SENSORS=2 # max allowable sensors (uses OR logic for triggering) ; ; Use Audioreactive usermod and configure I2S microphone ; -D USERMOD_AUDIOREACTIVE -; -D UM_AUDIOREACTIVE_USE_NEW_FFT ; -D AUDIOPIN=-1 ; -D DMTYPE=1 # 0-analog/disabled, 1-I2S generic, 2-ES7243, 3-SPH0645, 4-I2S+mclk, 5-I2S PDM ; -D I2S_SDPIN=36 @@ -155,18 +162,22 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; -D DEFAULT_LED_COUNT=30 ; or this for multiple outputs ; -D PIXEL_COUNTS=30,30 -; -; set milliampere limit when using ESP pin to power leds +; +; set the default LED type +; -D DEFAULT_LED_TYPE=22 # see const.h (TYPE_xxxx) +; +; set milliampere limit when using ESP power pin (or inadequate PSU) to power LEDs ; -D ABL_MILLIAMPS_DEFAULT=850 +; -D LED_MILLIAMPS_DEFAULT=55 ; ; enable IR by setting remote type -; -D IRTYPE=0 ;0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote +; -D IRTYPE=0 # 0 Remote disabled | 1 24-key RGB | 2 24-key with CT | 3 40-key blue | 4 40-key RGB | 5 21-key RGB | 6 6-key black | 7 9-key red | 8 JSON remote ; ; set default color order of your led strip ; -D DEFAULT_LED_COLOR_ORDER=COL_ORDER_GRB ; ; use PSRAM on classic ESP32 rev.1 (rev.3 or above has no issues) -; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue +; -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue # needed only for classic ESP32 rev.1 ; ; configure I2C and SPI interface (for various hardware) ; -D I2CSDAPIN=33 # initialise interface diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index d66b1b333..7a67dd749 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -58,7 +58,11 @@ private: bool sensorPinState[PIR_SENSOR_MAX_SENSORS] = {LOW}; // current PIR sensor pin state // configurable parameters +#if PIR_SENSOR_PIN < 0 + bool enabled = false; // PIR sensor disabled +#else bool enabled = true; // PIR sensor enabled +#endif int8_t PIRsensorPin[PIR_SENSOR_MAX_SENSORS] = {PIR_SENSOR_PIN}; // PIR sensor pin uint32_t m_switchOffDelay = PIR_SENSOR_OFF_SEC*1000; // delay before switch off after the sensor state goes LOW (10min) uint8_t m_onPreset = 0; // on preset diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h index 2a63dd4d8..52ff3cc1d 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -122,9 +122,9 @@ class AutoSaveUsermod : public Usermod { * Da loop. */ void loop() { - if (!autoSaveAfterSec || !enabled || strip.isUpdating() || currentPreset>0) return; // setting 0 as autosave seconds disables autosave - + static unsigned long lastRun = 0; unsigned long now = millis(); + if (!autoSaveAfterSec || !enabled || currentPreset>0 || (strip.isUpdating() && now - lastRun < 240)) return; // setting 0 as autosave seconds disables autosave uint8_t currentMode = strip.getMainSegment().mode; uint8_t currentPalette = strip.getMainSegment().palette; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 26118bdf0..ac5a89228 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1,1835 +1,1835 @@ -/* - WS2812FX_fcn.cpp contains all utility functions - Harm Aldick - 2016 - www.aldick.org - LICENSE - The MIT License (MIT) - Copyright (c) 2016 Harm Aldick - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - - Modified heavily for WLED -*/ -#include "wled.h" -#include "FX.h" -#include "palettes.h" - -/* - Custom per-LED mapping has moved! - - Create a file "ledmap.json" using the edit page. - - this is just an example (30 LEDs). It will first set all even, then all uneven LEDs. - {"map":[ - 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, - 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29]} - - another example. Switches direction every 5 LEDs. - {"map":[ - 0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14, - 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]} -*/ - -//factory defaults LED setup -//#define PIXEL_COUNTS 30, 30, 30, 30 -//#define DATA_PINS 16, 1, 3, 4 -//#define DEFAULT_LED_TYPE TYPE_WS2812_RGB - -#ifndef PIXEL_COUNTS - #define PIXEL_COUNTS DEFAULT_LED_COUNT -#endif - -#ifndef DATA_PINS - #define DATA_PINS LEDPIN -#endif - -#ifndef DEFAULT_LED_TYPE - #define DEFAULT_LED_TYPE TYPE_WS2812_RGB -#endif - -#ifndef DEFAULT_LED_COLOR_ORDER - #define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB -#endif - - -#if MAX_NUM_SEGMENTS < WLED_MAX_BUSSES - #error "Max segments must be at least max number of busses!" -#endif - - -/////////////////////////////////////////////////////////////////////////////// -// Segment class implementation -/////////////////////////////////////////////////////////////////////////////// -uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[] -uint16_t Segment::maxWidth = DEFAULT_LED_COUNT; -uint16_t Segment::maxHeight = 1; - -CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black); -CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); -CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); -uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment -uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only) - -#ifndef WLED_DISABLE_MODE_BLEND -bool Segment::_modeBlend = false; -#endif - -// copy constructor -Segment::Segment(const Segment &orig) { - //DEBUG_PRINTF_P(PSTR("-- Copy segment constructor: %p -> %p\n"), &orig, this); - memcpy((void*)this, (void*)&orig, sizeof(Segment)); - _t = nullptr; // copied segment cannot be in transition - name = nullptr; - data = nullptr; - _dataLen = 0; - if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } - if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } -} - -// move constructor -Segment::Segment(Segment &&orig) noexcept { - //DEBUG_PRINTF_P(PSTR("-- Move segment constructor: %p -> %p\n"), &orig, this); - memcpy((void*)this, (void*)&orig, sizeof(Segment)); - orig._t = nullptr; // old segment cannot be in transition any more - orig.name = nullptr; - orig.data = nullptr; - orig._dataLen = 0; -} - -// copy assignment -Segment& Segment::operator= (const Segment &orig) { - //DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this); - if (this != &orig) { - // clean destination - if (name) { delete[] name; name = nullptr; } - stopTransition(); - deallocateData(); - // copy source - memcpy((void*)this, (void*)&orig, sizeof(Segment)); - // erase pointers to allocated data - data = nullptr; - _dataLen = 0; - // copy source data - if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } - if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } - } - return *this; -} - -// move assignment -Segment& Segment::operator= (Segment &&orig) noexcept { - //DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this); - if (this != &orig) { - if (name) { delete[] name; name = nullptr; } // free old name - stopTransition(); - deallocateData(); // free old runtime data - memcpy((void*)this, (void*)&orig, sizeof(Segment)); - orig.name = nullptr; - orig.data = nullptr; - orig._dataLen = 0; - orig._t = nullptr; // old segment cannot be in transition - } - return *this; -} - -// allocates effect data buffer on heap and initialises (erases) it -bool IRAM_ATTR Segment::allocateData(size_t len) { - if (len == 0) return false; // nothing to do - if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation) - if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation - return true; - } - //DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n", len, this); - deallocateData(); // if the old buffer was smaller release it first - if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) { - // not enough memory - DEBUG_PRINT(F("!!! Effect RAM depleted: ")); - DEBUG_PRINTF_P(PSTR("%d/%d !!!\n"), len, Segment::getUsedSegmentData()); - errorFlag = ERR_NORAM; - return false; - } - // do not use SPI RAM on ESP32 since it is slow - data = (byte*)calloc(len, sizeof(byte)); - if (!data) { DEBUG_PRINTLN(F("!!! Allocation failed. !!!")); return false; } // allocation failed - Segment::addUsedSegmentData(len); - //DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data); - _dataLen = len; - return true; -} - -void IRAM_ATTR Segment::deallocateData() { - if (!data) { _dataLen = 0; return; } - //DEBUG_PRINTF_P(PSTR("--- Released data (%p): %d/%d -> %p\n"), this, _dataLen, Segment::getUsedSegmentData(), data); - if ((Segment::getUsedSegmentData() > 0) && (_dataLen > 0)) { // check that we don't have a dangling / inconsistent data pointer - free(data); - } else { - DEBUG_PRINT(F("---- Released data ")); - DEBUG_PRINTF_P(PSTR("(%p): "), this); - DEBUG_PRINT(F("inconsistent UsedSegmentData ")); - DEBUG_PRINTF_P(PSTR("(%d/%d)"), _dataLen, Segment::getUsedSegmentData()); - DEBUG_PRINTLN(F(", cowardly refusing to free nothing.")); - } - data = nullptr; - Segment::addUsedSegmentData(_dataLen <= Segment::getUsedSegmentData() ? -_dataLen : -Segment::getUsedSegmentData()); - _dataLen = 0; -} - -/** - * If reset of this segment was requested, clears runtime - * settings of this segment. - * Must not be called while an effect mode function is running - * because it could access the data buffer and this method - * may free that data buffer. - */ -void Segment::resetIfRequired() { - if (!reset) return; - //DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this); - if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData()) - next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; - reset = false; -} - -CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { - if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; - if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip - //default palette. Differs depending on effect - if (pal == 0) switch (mode) { - case FX_MODE_FIRE_2012 : pal = 35; break; // heat palette - case FX_MODE_COLORWAVES : pal = 26; break; // landscape 33 - case FX_MODE_FILLNOISE8 : pal = 9; break; // ocean colors - case FX_MODE_NOISE16_1 : pal = 20; break; // Drywet - case FX_MODE_NOISE16_2 : pal = 43; break; // Blue cyan yellow - case FX_MODE_NOISE16_3 : pal = 35; break; // heat palette - case FX_MODE_NOISE16_4 : pal = 26; break; // landscape 33 - case FX_MODE_GLITTER : pal = 11; break; // rainbow colors - case FX_MODE_SUNRISE : pal = 35; break; // heat palette - case FX_MODE_RAILWAY : pal = 3; break; // prim + sec - case FX_MODE_2DSOAP : pal = 11; break; // rainbow colors - } - switch (pal) { - case 0: //default palette. Exceptions for specific effects above - targetPalette = PartyColors_p; break; - case 1: //randomly generated palette - targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette() - break; - case 2: {//primary color only - CRGB prim = gamma32(colors[0]); - targetPalette = CRGBPalette16(prim); break;} - case 3: {//primary + secondary - CRGB prim = gamma32(colors[0]); - CRGB sec = gamma32(colors[1]); - targetPalette = CRGBPalette16(prim,prim,sec,sec); break;} - case 4: {//primary + secondary + tertiary - CRGB prim = gamma32(colors[0]); - CRGB sec = gamma32(colors[1]); - CRGB ter = gamma32(colors[2]); - targetPalette = CRGBPalette16(ter,sec,prim); break;} - case 5: {//primary + secondary (+tertiary if not off), more distinct - CRGB prim = gamma32(colors[0]); - CRGB sec = gamma32(colors[1]); - if (colors[2]) { - CRGB ter = gamma32(colors[2]); - targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,ter,ter,ter,ter,ter,prim); - } else { - targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec); - } - break;} - case 6: //Party colors - targetPalette = PartyColors_p; break; - case 7: //Cloud colors - targetPalette = CloudColors_p; break; - case 8: //Lava colors - targetPalette = LavaColors_p; break; - case 9: //Ocean colors - targetPalette = OceanColors_p; break; - case 10: //Forest colors - targetPalette = ForestColors_p; break; - case 11: //Rainbow colors - targetPalette = RainbowColors_p; break; - case 12: //Rainbow stripe colors - targetPalette = RainbowStripeColors_p; break; - default: //progmem palettes - if (pal>245) { - targetPalette = strip.customPalettes[255-pal]; // we checked bounds above - } else { - byte tcp[72]; - memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72); - targetPalette.loadDynamicGradientPalette(tcp); - } - break; - } - return targetPalette; -} - -void Segment::startTransition(uint16_t dur) { - if (dur == 0) { - if (isInTransition()) _t->_dur = dur; // this will stop transition in next handleTransition() - return; - } - if (isInTransition()) return; // already in transition no need to store anything - - // starting a transition has to occur before change so we get current values 1st - _t = new Transition(dur); // no previous transition running - if (!_t) return; // failed to allocate data - - //DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t); - loadPalette(_t->_palT, palette); - _t->_briT = on ? opacity : 0; - _t->_cctT = cct; -#ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending) { - swapSegenv(_t->_segT); - _t->_modeT = mode; - _t->_segT._dataLenT = 0; - _t->_segT._dataT = nullptr; - if (_dataLen > 0 && data) { - _t->_segT._dataT = (byte *)malloc(_dataLen); - if (_t->_segT._dataT) { - //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); - memcpy(_t->_segT._dataT, data, _dataLen); - _t->_segT._dataLenT = _dataLen; - } - } - } else { - for (size_t i=0; i_segT._colorT[i] = colors[i]; - } -#else - for (size_t i=0; i_colorT[i] = colors[i]; -#endif -} - -void Segment::stopTransition() { - if (isInTransition()) { - //DEBUG_PRINTF_P(PSTR("-- Stopping transition: %p\n"), this); - #ifndef WLED_DISABLE_MODE_BLEND - if (_t->_segT._dataT && _t->_segT._dataLenT > 0) { - //DEBUG_PRINTF_P(PSTR("-- Released duplicate data (%d) for %p: %p\n"), _t->_segT._dataLenT, this, _t->_segT._dataT); - free(_t->_segT._dataT); - _t->_segT._dataT = nullptr; - _t->_segT._dataLenT = 0; - } - #endif - delete _t; - _t = nullptr; - } -} - -void Segment::handleTransition() { - unsigned _progress = progress(); - if (_progress == 0xFFFFU) stopTransition(); -} - -// transition progression between 0-65535 -uint16_t IRAM_ATTR Segment::progress() { - if (isInTransition()) { - unsigned diff = millis() - _t->_start; - if (_t->_dur > 0 && diff < _t->_dur) return diff * 0xFFFFU / _t->_dur; - } - return 0xFFFFU; -} - -#ifndef WLED_DISABLE_MODE_BLEND -void Segment::swapSegenv(tmpsegd_t &tmpSeg) { - //DEBUG_PRINTF_P(PSTR("-- Saving temp seg: %p->(%p) [%d->%p]\n"), this, &tmpSeg, _dataLen, data); - tmpSeg._optionsT = options; - for (size_t i=0; i_segT)) { - // swap SEGENV with transitional data - options = _t->_segT._optionsT; - for (size_t i=0; i_segT._colorT[i]; - speed = _t->_segT._speedT; - intensity = _t->_segT._intensityT; - custom1 = _t->_segT._custom1T; - custom2 = _t->_segT._custom2T; - custom3 = _t->_segT._custom3T; - check1 = _t->_segT._check1T; - check2 = _t->_segT._check2T; - check3 = _t->_segT._check3T; - aux0 = _t->_segT._aux0T; - aux1 = _t->_segT._aux1T; - step = _t->_segT._stepT; - call = _t->_segT._callT; - data = _t->_segT._dataT; - _dataLen = _t->_segT._dataLenT; - } -} - -void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { - //DEBUG_PRINTF_P(PSTR("-- Restoring temp seg: %p->(%p) [%d->%p]\n"), &tmpSeg, this, _dataLen, data); - if (_t && &(_t->_segT) != &tmpSeg) { - // update possibly changed variables to keep old effect running correctly - _t->_segT._aux0T = aux0; - _t->_segT._aux1T = aux1; - _t->_segT._stepT = step; - _t->_segT._callT = call; - //if (_t->_segT._dataT != data) DEBUG_PRINTF_P(PSTR("--- data re-allocated: (%p) %p -> %p\n"), this, _t->_segT._dataT, data); - _t->_segT._dataT = data; - _t->_segT._dataLenT = _dataLen; - } - options = tmpSeg._optionsT; - for (size_t i=0; i_cctT : _t->_briT) * (0xFFFFU - prog); - return curBri / 0xFFFFU; - } - return (useCct ? cct : (on ? opacity : 0)); -} - -uint8_t IRAM_ATTR Segment::currentMode() { -#ifndef WLED_DISABLE_MODE_BLEND - unsigned prog = progress(); - if (modeBlending && prog < 0xFFFFU) return _t->_modeT; -#endif - return mode; -} - -uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { - if (slot >= NUM_COLORS) slot = 0; -#ifndef WLED_DISABLE_MODE_BLEND - return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot]; -#else - return isInTransition() ? color_blend(_t->_colorT[slot], colors[slot], progress(), true) : colors[slot]; -#endif -} - -void Segment::setCurrentPalette() { - loadPalette(_currentPalette, palette); - unsigned prog = progress(); - if (strip.paletteFade && prog < 0xFFFFU) { - // blend palettes - // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) - // minimum blend time is 100ms maximum is 65535ms - unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; - for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48); - _currentPalette = _t->_palT; // copy transitioning/temporary palette - } -} - -// relies on WS2812FX::service() to call it for each frame -void Segment::handleRandomPalette() { - // is it time to generate a new palette? - if ((uint16_t)((uint16_t)(millis() / 1000U) - _lastPaletteChange) > randomPaletteChangeTime){ - _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); - _lastPaletteChange = (uint16_t)(millis() / 1000U); - _lastPaletteBlend = (uint16_t)((uint16_t)millis() - 512); // starts blending immediately - } - - // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls) - if (strip.paletteFade) { - // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) - // in reality there need to be 255 blends to fully blend two entirely different palettes - if ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update - _lastPaletteBlend = (uint16_t)millis(); - } - nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); -} - -// segId is given when called from network callback, changes are queued if that segment is currently in its effect function -void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) { - // return if neither bounds nor grouping have changed - bool boundsUnchanged = (start == i1 && stop == i2); - #ifndef WLED_DISABLE_2D - if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D - #endif - if (boundsUnchanged - && (!grp || (grouping == grp && spacing == spc)) - && (ofs == UINT16_MAX || ofs == offset)) return; - - stateChanged = true; // send UDP/WS broadcast - - if (stop) fill(BLACK); // turn old segment range off (clears pixels if changing spacing) - if (grp) { // prevent assignment of 0 - grouping = grp; - spacing = spc; - } else { - grouping = 1; - spacing = 0; - } - if (ofs < UINT16_MAX) offset = ofs; - - DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1); - DEBUG_PRINT(','); DEBUG_PRINT(i2); - DEBUG_PRINT(F(" -> ")); DEBUG_PRINT(i1Y); - DEBUG_PRINT(','); DEBUG_PRINTLN(i2Y); - markForReset(); - if (boundsUnchanged) return; - - // apply change immediately - if (i2 <= i1) { //disable segment - stop = 0; - return; - } - if (i1 < Segment::maxWidth || (i1 >= Segment::maxWidth*Segment::maxHeight && i1 < strip.getLengthTotal())) start = i1; // Segment::maxWidth equals strip.getLengthTotal() for 1D - stop = i2 > Segment::maxWidth*Segment::maxHeight ? MIN(i2,strip.getLengthTotal()) : (i2 > Segment::maxWidth ? Segment::maxWidth : MAX(1,i2)); - startY = 0; - stopY = 1; - #ifndef WLED_DISABLE_2D - if (Segment::maxHeight>1) { // 2D - if (i1Y < Segment::maxHeight) startY = i1Y; - stopY = i2Y > Segment::maxHeight ? Segment::maxHeight : MAX(1,i2Y); - } - #endif - // safety check - if (start >= stop || startY >= stopY) { - stop = 0; - return; - } - refreshLightCapabilities(); -} - - -bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed - if (slot >= NUM_COLORS || c == colors[slot]) return false; - if (!_isRGB && !_hasW) { - if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black - if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black - } - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change - colors[slot] = c; - stateChanged = true; // send UDP/WS broadcast - return true; -} - -void Segment::setCCT(uint16_t k) { - if (k > 255) { //kelvin value, convert to 0-255 - if (k < 1900) k = 1900; - if (k > 10091) k = 10091; - k = (k - 1900) >> 5; - } - if (cct == k) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change - cct = k; - stateChanged = true; // send UDP/WS broadcast -} - -void Segment::setOpacity(uint8_t o) { - if (opacity == o) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change - opacity = o; - stateChanged = true; // send UDP/WS broadcast -} - -void Segment::setOption(uint8_t n, bool val) { - bool prevOn = on; - if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change - if (val) options |= 0x01 << n; - else options &= ~(0x01 << n); - if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast -} - -void Segment::setMode(uint8_t fx, bool loadDefaults) { - // skip reserved - while (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4) == 0) fx++; - if (fx >= strip.getModeCount()) fx = 0; // set solid mode - // if we have a valid mode & is not reserved - if (fx != mode) { -#ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending) startTransition(strip.getTransition()); // set effect transitions -#endif - mode = fx; - // load default values from effect string - if (loadDefaults) { - int sOpt; - sOpt = extractModeDefaults(fx, "sx"); speed = (sOpt >= 0) ? sOpt : DEFAULT_SPEED; - sOpt = extractModeDefaults(fx, "ix"); intensity = (sOpt >= 0) ? sOpt : DEFAULT_INTENSITY; - sOpt = extractModeDefaults(fx, "c1"); custom1 = (sOpt >= 0) ? sOpt : DEFAULT_C1; - sOpt = extractModeDefaults(fx, "c2"); custom2 = (sOpt >= 0) ? sOpt : DEFAULT_C2; - sOpt = extractModeDefaults(fx, "c3"); custom3 = (sOpt >= 0) ? sOpt : DEFAULT_C3; - sOpt = extractModeDefaults(fx, "o1"); check1 = (sOpt >= 0) ? (bool)sOpt : false; - sOpt = extractModeDefaults(fx, "o2"); check2 = (sOpt >= 0) ? (bool)sOpt : false; - sOpt = extractModeDefaults(fx, "o3"); check3 = (sOpt >= 0) ? (bool)sOpt : false; - sOpt = extractModeDefaults(fx, "m12"); if (sOpt >= 0) map1D2D = constrain(sOpt, 0, 7); else map1D2D = M12_Pixels; // reset mapping if not defined (2D FX may not work) - sOpt = extractModeDefaults(fx, "si"); if (sOpt >= 0) soundSim = constrain(sOpt, 0, 3); - sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) reverse = (bool)sOpt; - sOpt = extractModeDefaults(fx, "mi"); if (sOpt >= 0) mirror = (bool)sOpt; // NOTE: setting this option is a risky business - sOpt = extractModeDefaults(fx, "rY"); if (sOpt >= 0) reverse_y = (bool)sOpt; - sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business - sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) setPalette(sOpt); //else setPalette(0); - } - markForReset(); - stateChanged = true; // send UDP/WS broadcast - } -} - -void Segment::setPalette(uint8_t pal) { - if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes - if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes - if (pal != palette) { - if (strip.paletteFade) startTransition(strip.getTransition()); - palette = pal; - stateChanged = true; // send UDP/WS broadcast - } -} - -// 2D matrix -uint16_t IRAM_ATTR Segment::virtualWidth() const { - unsigned groupLen = groupLength(); - unsigned vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; - if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED - return vWidth; -} - -uint16_t IRAM_ATTR Segment::virtualHeight() const { - unsigned groupLen = groupLength(); - unsigned vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; - if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED - return vHeight; -} - -uint16_t IRAM_ATTR Segment::nrOfVStrips() const { - unsigned vLen = 1; -#ifndef WLED_DISABLE_2D - if (is2D()) { - switch (map1D2D) { - case M12_pBar: - vLen = virtualWidth(); - break; - } - } -#endif - return vLen; -} - -// Constants for mapping mode "Pinwheel" -#ifndef WLED_DISABLE_2D -constexpr int Pinwheel_Steps_Small = 72; // no holes up to 16x16 -constexpr int Pinwheel_Size_Small = 16; // larger than this -> use "Medium" -constexpr int Pinwheel_Steps_Medium = 192; // no holes up to 32x32 -constexpr int Pinwheel_Size_Medium = 32; // larger than this -> use "Big" -constexpr int Pinwheel_Steps_Big = 304; // no holes up to 50x50 -constexpr int Pinwheel_Size_Big = 50; // larger than this -> use "XL" -constexpr int Pinwheel_Steps_XL = 368; -constexpr float Int_to_Rad_Small = (DEG_TO_RAD * 360) / Pinwheel_Steps_Small; // conversion: from 0...72 to Radians -constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360) / Pinwheel_Steps_Medium; // conversion: from 0...192 to Radians -constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360) / Pinwheel_Steps_Big; // conversion: from 0...304 to Radians -constexpr float Int_to_Rad_XL = (DEG_TO_RAD * 360) / Pinwheel_Steps_XL; // conversion: from 0...368 to Radians - -constexpr int Fixed_Scale = 512; // fixpoint scaling factor (9bit for fraction) - -// Pinwheel helper function: pixel index to radians -static float getPinwheelAngle(int i, int vW, int vH) { - int maxXY = max(vW, vH); - if (maxXY <= Pinwheel_Size_Small) return float(i) * Int_to_Rad_Small; - if (maxXY <= Pinwheel_Size_Medium) return float(i) * Int_to_Rad_Med; - if (maxXY <= Pinwheel_Size_Big) return float(i) * Int_to_Rad_Big; - // else - return float(i) * Int_to_Rad_XL; -} -// Pinwheel helper function: matrix dimensions to number of rays -static int getPinwheelLength(int vW, int vH) { - int maxXY = max(vW, vH); - if (maxXY <= Pinwheel_Size_Small) return Pinwheel_Steps_Small; - if (maxXY <= Pinwheel_Size_Medium) return Pinwheel_Steps_Medium; - if (maxXY <= Pinwheel_Size_Big) return Pinwheel_Steps_Big; - // else - return Pinwheel_Steps_XL; -} -#endif - -// 1D strip -uint16_t IRAM_ATTR Segment::virtualLength() const { -#ifndef WLED_DISABLE_2D - if (is2D()) { - unsigned vW = virtualWidth(); - unsigned vH = virtualHeight(); - unsigned vLen = vW * vH; // use all pixels from segment - switch (map1D2D) { - case M12_pBar: - vLen = vH; - break; - case M12_pCorner: - case M12_pArc: - vLen = max(vW,vH); // get the longest dimension - break; - case M12_sPinwheel: - vLen = getPinwheelLength(vW, vH); - break; - } - return vLen; - } -#endif - unsigned groupLen = groupLength(); // is always >= 1 - unsigned vLength = (length() + groupLen - 1) / groupLen; - if (mirror) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED - return vLength; -} - -void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) -{ - if (!isActive()) return; // not active -#ifndef WLED_DISABLE_2D - int vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) -#endif - i &= 0xFFFF; - - if (i >= virtualLength() || i<0) return; // if pixel would fall out of segment just exit - -#ifndef WLED_DISABLE_2D - if (is2D()) { - int vH = virtualHeight(); // segment height in logical pixels - int vW = virtualWidth(); - switch (map1D2D) { - case M12_Pixels: - // use all available pixels as a long strip - setPixelColorXY(i % vW, i / vW, col); - break; - case M12_pBar: - // expand 1D effect vertically or have it play on virtual strips - if (vStrip>0) setPixelColorXY(vStrip - 1, vH - i - 1, col); - else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col); - break; - case M12_pArc: - // expand in circular fashion from center - if (i==0) - setPixelColorXY(0, 0, col); - else { - float step = HALF_PI / (2.85f*i); - for (float rad = 0.0f; rad <= HALF_PI+step/2; rad += step) { - // may want to try float version as well (with or without antialiasing) - int x = roundf(sin_t(rad) * i); - int y = roundf(cos_t(rad) * i); - setPixelColorXY(x, y, col); - } - // Bresenham’s Algorithm (may not fill every pixel) - //int d = 3 - (2*i); - //int y = i, x = 0; - //while (y >= x) { - // setPixelColorXY(x, y, col); - // setPixelColorXY(y, x, col); - // x++; - // if (d > 0) { - // y--; - // d += 4 * (x - y) + 10; - // } else { - // d += 4 * x + 6; - // } - //} - } - break; - case M12_pCorner: - for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col); - for (int y = 0; y < i; y++) setPixelColorXY(i, y, col); - break; - case M12_sPinwheel: { - // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small) - float centerX = roundf((vW-1) / 2.0f); - float centerY = roundf((vH-1) / 2.0f); - float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians - float cosVal = cos_t(angleRad); - float sinVal = sin_t(angleRad); - - // avoid re-painting the same pixel - int lastX = INT_MIN; // impossible position - int lastY = INT_MIN; // impossible position - // draw line at angle, starting at center and ending at the segment edge - // we use fixed point math for better speed. Starting distance is 0.5 for better rounding - // int_fast16_t and int_fast32_t types changed to int, minimum bits commented - int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit - int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit - int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit - int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit - - int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint - int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint - - // Odd rays start further from center if prevRay started at center. - static int prevRay = INT_MIN; // previous ray number - if ((i % 2 == 1) && (i - 1 == prevRay || i + 1 == prevRay)) { - int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel - posx += inc_x * jump; - posy += inc_y * jump; - } - prevRay = i; - - // draw ray until we hit any edge - while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) { - // scale down to integer (compiler will replace division with appropriate bitshift) - int x = posx / Fixed_Scale; - int y = posy / Fixed_Scale; - // set pixel - if (x != lastX || y != lastY) setPixelColorXY(x, y, col); // only paint if pixel position is different - lastX = x; - lastY = y; - // advance to next position - posx += inc_x; - posy += inc_y; - } - break; - } - } - return; - } else if (Segment::maxHeight!=1 && (width()==1 || height()==1)) { - if (start < Segment::maxWidth*Segment::maxHeight) { - // we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed) - int x = 0, y = 0; - if (virtualHeight()>1) y = i; - if (virtualWidth() >1) x = i; - setPixelColorXY(x, y, col); - return; - } - } -#endif - - unsigned len = length(); - uint8_t _bri_t = currentBri(); - if (_bri_t < 255) { - col = color_fade(col, _bri_t); - } - - // expand pixel (taking into account start, grouping, spacing [and offset]) - i = i * groupLength(); - if (reverse) { // is segment reversed? - if (mirror) { // is segment mirrored? - i = (len - 1) / 2 - i; //only need to index half the pixels - } else { - i = (len - 1) - i; - } - } - i += start; // starting pixel in a group - - uint32_t tmpCol = col; - // set all the pixels in the group - for (int j = 0; j < grouping; j++) { - unsigned indexSet = i + ((reverse) ? -j : j); - if (indexSet >= start && indexSet < stop) { - if (mirror) { //set the corresponding mirrored pixel - unsigned indexMir = stop - indexSet + start - 1; - indexMir += offset; // offset/phase - if (indexMir >= stop) indexMir -= len; // wrap -#ifndef WLED_DISABLE_MODE_BLEND - if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true); -#endif - strip.setPixelColor(indexMir, tmpCol); - } - indexSet += offset; // offset/phase - if (indexSet >= stop) indexSet -= len; // wrap -#ifndef WLED_DISABLE_MODE_BLEND - if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true); -#endif - strip.setPixelColor(indexSet, tmpCol); - } - } -} - -#ifdef WLED_USE_AA_PIXELS -// anti-aliased normalized version of setPixelColor() -void Segment::setPixelColor(float i, uint32_t col, bool aa) -{ - if (!isActive()) return; // not active - int vStrip = int(i/10.0f); // hack to allow running on virtual strips (2D segment columns/rows) - i -= int(i); - - if (i<0.0f || i>1.0f) return; // not normalized - - float fC = i * (virtualLength()-1); - if (aa) { - unsigned iL = roundf(fC-0.49f); - unsigned iR = roundf(fC+0.49f); - float dL = (fC - iL)*(fC - iL); - float dR = (iR - fC)*(iR - fC); - uint32_t cIL = getPixelColor(iL | (vStrip<<16)); - uint32_t cIR = getPixelColor(iR | (vStrip<<16)); - if (iR!=iL) { - // blend L pixel - cIL = color_blend(col, cIL, uint8_t(dL*255.0f)); - setPixelColor(iL | (vStrip<<16), cIL); - // blend R pixel - cIR = color_blend(col, cIR, uint8_t(dR*255.0f)); - setPixelColor(iR | (vStrip<<16), cIR); - } else { - // exact match (x & y land on a pixel) - setPixelColor(iL | (vStrip<<16), col); - } - } else { - setPixelColor(int(roundf(fC)) | (vStrip<<16), col); - } -} -#endif - -uint32_t IRAM_ATTR Segment::getPixelColor(int i) -{ - if (!isActive()) return 0; // not active -#ifndef WLED_DISABLE_2D - int vStrip = i>>16; -#endif - i &= 0xFFFF; - -#ifndef WLED_DISABLE_2D - if (is2D()) { - unsigned vH = virtualHeight(); // segment height in logical pixels - unsigned vW = virtualWidth(); - switch (map1D2D) { - case M12_Pixels: - return getPixelColorXY(i % vW, i / vW); - break; - case M12_pBar: - if (vStrip>0) return getPixelColorXY(vStrip - 1, vH - i -1); - else return getPixelColorXY(0, vH - i -1); - break; - case M12_pArc: - case M12_pCorner: - // use longest dimension - return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); - break; - case M12_sPinwheel: - // not 100% accurate, returns pixel at outer edge - // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small) - float centerX = roundf((vW-1) / 2.0f); - float centerY = roundf((vH-1) / 2.0f); - float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians - float cosVal = cos_t(angleRad); - float sinVal = sin_t(angleRad); - - int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit - int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit - int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit - int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit - int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint - int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint - - // trace ray from center until we hit any edge - to avoid rounding problems, we use the same method as in setPixelColor - int x = INT_MIN; - int y = INT_MIN; - while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) { - // scale down to integer (compiler will replace division with appropriate bitshift) - x = posx / Fixed_Scale; - y = posy / Fixed_Scale; - // advance to next position - posx += inc_x; - posy += inc_y; - } - return getPixelColorXY(x, y); - break; - } - return 0; - } -#endif - - if (reverse) i = virtualLength() - i - 1; - i *= groupLength(); - i += start; - /* offset/phase */ - i += offset; - if ((i >= stop) && (stop>0)) i -= length(); // avoids negative pixel index (stop = 0 is a possible value) - return strip.getPixelColor(i); -} - -uint8_t Segment::differs(Segment& b) const { - uint8_t d = 0; - if (start != b.start) d |= SEG_DIFFERS_BOUNDS; - if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS; - if (offset != b.offset) d |= SEG_DIFFERS_GSO; - if (grouping != b.grouping) d |= SEG_DIFFERS_GSO; - if (spacing != b.spacing) d |= SEG_DIFFERS_GSO; - if (opacity != b.opacity) d |= SEG_DIFFERS_BRI; - if (mode != b.mode) d |= SEG_DIFFERS_FX; - if (speed != b.speed) d |= SEG_DIFFERS_FX; - if (intensity != b.intensity) d |= SEG_DIFFERS_FX; - if (palette != b.palette) d |= SEG_DIFFERS_FX; - if (custom1 != b.custom1) d |= SEG_DIFFERS_FX; - if (custom2 != b.custom2) d |= SEG_DIFFERS_FX; - if (custom3 != b.custom3) d |= SEG_DIFFERS_FX; - if (startY != b.startY) d |= SEG_DIFFERS_BOUNDS; - if (stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS; - - //bit pattern: (msb first) - // set:2, sound:2, mapping:3, transposed, mirrorY, reverseY, [reset,] paused, mirrored, on, reverse, [selected] - if ((options & 0b1111111111011110U) != (b.options & 0b1111111111011110U)) d |= SEG_DIFFERS_OPT; - if ((options & 0x0001U) != (b.options & 0x0001U)) d |= SEG_DIFFERS_SEL; - for (unsigned i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; - - return d; -} - -void Segment::refreshLightCapabilities() { - unsigned capabilities = 0; - unsigned segStartIdx = 0xFFFFU; - unsigned segStopIdx = 0; - - if (!isActive()) { - _capabilities = 0; - return; - } - - if (start < Segment::maxWidth * Segment::maxHeight) { - // we are withing 2D matrix (includes 1D segments) - for (int y = startY; y < stopY; y++) for (int x = start; x < stop; x++) { - unsigned index = strip.getMappedPixelIndex(x + Segment::maxWidth * y); // convert logical address to physical - if (index < 0xFFFFU) { - if (segStartIdx > index) segStartIdx = index; - if (segStopIdx < index) segStopIdx = index; - } - if (segStartIdx == segStopIdx) segStopIdx++; // we only have 1 pixel segment - } - } else { - // we are on the strip located after the matrix - segStartIdx = start; - segStopIdx = stop; - } - - for (unsigned b = 0; b < BusManager::getNumBusses(); b++) { - Bus *bus = BusManager::getBus(b); - if (bus == nullptr || bus->getLength()==0) break; - if (!bus->isOk()) continue; - if (bus->getStart() >= segStopIdx) continue; - if (bus->getStart() + bus->getLength() <= segStartIdx) continue; - - //uint8_t type = bus->getType(); - if (bus->hasRGB() || (cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB; - if (!cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT; - if (correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider) - if (bus->hasWhite()) { - unsigned aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode(); - bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed - // if auto white calculation from RGB is active (Accurate/Brighter), force RGB controls even if there are no RGB busses - if (!whiteSlider) capabilities |= SEG_CAPABILITY_RGB; - // if auto white calculation from RGB is disabled/optional (None/Dual), allow white channel adjustments - if ( whiteSlider) capabilities |= SEG_CAPABILITY_W; - } - } - _capabilities = capabilities; -} - -/* - * Fills segment with color - */ -void Segment::fill(uint32_t c) { - if (!isActive()) return; // not active - const int cols = is2D() ? virtualWidth() : virtualLength(); - const int rows = virtualHeight(); // will be 1 for 1D - for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { - if (is2D()) setPixelColorXY(x, y, c); - else setPixelColor(x, c); - } -} - -/* - * fade out function, higher rate = quicker fade - */ -void Segment::fade_out(uint8_t rate) { - if (!isActive()) return; // not active - const int cols = is2D() ? virtualWidth() : virtualLength(); - const int rows = virtualHeight(); // will be 1 for 1D - - rate = (255-rate) >> 1; - float mappedRate = float(rate) +1.1f; - - uint32_t color = colors[1]; // SEGCOLOR(1); // target color - int w2 = W(color); - int r2 = R(color); - int g2 = G(color); - int b2 = B(color); - - for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { - color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); - int w1 = W(color); - int r1 = R(color); - int g1 = G(color); - int b1 = B(color); - - int wdelta = (w2 - w1) / mappedRate; - int rdelta = (r2 - r1) / mappedRate; - int gdelta = (g2 - g1) / mappedRate; - int bdelta = (b2 - b1) / mappedRate; - - // if fade isn't complete, make sure delta is at least 1 (fixes rounding issues) - wdelta += (w2 == w1) ? 0 : (w2 > w1) ? 1 : -1; - rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1; - gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1; - bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1; - - if (is2D()) setPixelColorXY(x, y, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); - else setPixelColor(x, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); - } -} - -// fades all pixels to black using nscale8() -void Segment::fadeToBlackBy(uint8_t fadeBy) { - if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply - const int cols = is2D() ? virtualWidth() : virtualLength(); - const int rows = virtualHeight(); // will be 1 for 1D - - for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { - if (is2D()) setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), 255-fadeBy)); - else setPixelColor(x, color_fade(getPixelColor(x), 255-fadeBy)); - } -} - -/* - * blurs segment content, source: FastLED colorutils.cpp - */ -void Segment::blur(uint8_t blur_amount, bool smear) { - if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur" -#ifndef WLED_DISABLE_2D - if (is2D()) { - // compatibility with 2D - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); - for (unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear); // blur all rows - for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear); // blur all columns - return; - } -#endif - uint8_t keep = smear ? 255 : 255 - blur_amount; - uint8_t seep = blur_amount >> 1; - unsigned vlength = virtualLength(); - uint32_t carryover = BLACK; - uint32_t lastnew; - uint32_t last; - uint32_t curnew = BLACK; - for (unsigned i = 0; i < vlength; i++) { - uint32_t cur = getPixelColor(i); - uint32_t part = color_fade(cur, seep); - curnew = color_fade(cur, keep); - if (i > 0) { - if (carryover) - curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); - if (last != prev) // optimization: only set pixel if color has changed - setPixelColor(i - 1, prev); - } - else // first pixel - setPixelColor(i, curnew); - lastnew = curnew; - last = cur; // save original value for comparison on next iteration - carryover = part; - } - setPixelColor(vlength - 1, curnew); -} - -/* - * Put a value 0 to 255 in to get a color value. - * The colours are a transition r -> g -> b -> back to r - * Inspired by the Adafruit examples. - */ -uint32_t Segment::color_wheel(uint8_t pos) { - if (palette) return color_from_palette(pos, false, true, 0); // perhaps "strip.paletteBlend < 2" should be better instead of "true" - uint8_t w = W(currentColor(0)); - pos = 255 - pos; - if (pos < 85) { - return RGBW32((255 - pos * 3), 0, (pos * 3), w); - } else if(pos < 170) { - pos -= 85; - return RGBW32(0, (pos * 3), (255 - pos * 3), w); - } else { - pos -= 170; - return RGBW32((pos * 3), (255 - pos * 3), 0, w); - } -} - -/* - * Gets a single color from the currently selected palette. - * @param i Palette Index (if mapping is true, the full palette will be _virtualSegmentLength long, if false, 255). Will wrap around automatically. - * @param mapping if true, LED position in segment is considered for color - * @param wrap FastLED palettes will usually wrap back to the start smoothly. Set false to get a hard edge - * @param mcol If the default palette 0 is selected, return the standard color 0, 1 or 2 instead. If >2, Party palette is used instead - * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling) - * @returns Single color from palette - */ -uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) { - uint32_t color = gamma32(currentColor(mcol)); - - // default palette or no RGB support on segment - if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); - - unsigned paletteIndex = i; - if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); - // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) - if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" - CRGB fastled_col = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global - - return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, W(color)); -} - - -/////////////////////////////////////////////////////////////////////////////// -// WS2812FX class implementation -/////////////////////////////////////////////////////////////////////////////// - -//do not call this method from system context (network callback) -void WS2812FX::finalizeInit(void) { - //reset segment runtimes - for (segment &seg : _segments) { - seg.markForReset(); - seg.resetIfRequired(); - } - - // for the lack of better place enumerate ledmaps here - // if we do it in json.cpp (serializeInfo()) we are getting flashes on LEDs - // unfortunately this means we do not get updates after uploads - // the other option is saving UI settings which will cause enumeration - enumerateLedmaps(); - - _hasWhiteChannel = _isOffRefreshRequired = false; - - //if busses failed to load, add default (fresh install, FS issue, ...) - if (BusManager::getNumBusses() == 0) { - DEBUG_PRINTLN(F("No busses, init default")); - const unsigned defDataPins[] = {DATA_PINS}; - const unsigned defCounts[] = {PIXEL_COUNTS}; - const unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0])); - const unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); - const unsigned defNumBusses = defNumPins > defNumCounts && defNumCounts > 1 && defNumPins%defNumCounts == 0 ? defNumCounts : defNumPins; - const unsigned pinsPerBus = defNumPins / defNumBusses; - unsigned prevLen = 0; - for (unsigned i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { - uint8_t defPin[5]; // max 5 pins - for (unsigned j = 0; j < pinsPerBus; j++) defPin[j] = defDataPins[i*pinsPerBus + j]; - // when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware - // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), etc - if (pinManager.isPinAllocated(defPin[0])) { - defPin[0] = 1; // start with GPIO1 and work upwards - while (pinManager.isPinAllocated(defPin[0]) && defPin[0] < WLED_NUM_PINS) defPin[0]++; - } - unsigned start = prevLen; - unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; - prevLen += count; - BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY); - if (BusManager::add(defCfg) == -1) break; - } - } - - _length = 0; - for (int i=0; igetStart() + bus->getLength() > MAX_LEDS) break; - //RGBW mode is enabled if at least one of the strips is RGBW - _hasWhiteChannel |= bus->hasWhite(); - //refresh is required to remain off if at least one of the strips requires the refresh. - _isOffRefreshRequired |= bus->isOffRefreshRequired(); - unsigned busEnd = bus->getStart() + bus->getLength(); - if (busEnd > _length) _length = busEnd; - #ifdef ESP8266 - // why do we need to reinitialise GPIO3??? - //if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue; - //uint8_t pins[5]; - //if (!bus->getPins(pins)) continue; - //BusDigital* bd = static_cast(bus); - //if (pins[0] == 3) bd->reinit(); - #endif - } - - Segment::maxWidth = _length; - Segment::maxHeight = 1; - - //segments are created in makeAutoSegments(); - DEBUG_PRINTLN(F("Loading custom palettes")); - loadCustomPalettes(); // (re)load all custom palettes - DEBUG_PRINTLN(F("Loading custom ledmaps")); - deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist) -} - -void WS2812FX::service() { - unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days - now = nowUp + timebase; - if (nowUp - _lastShow < MIN_SHOW_DELAY || _suspend) return; - bool doShow = false; - - _isServicing = true; - _segment_index = 0; - - for (segment &seg : _segments) { - if (_suspend) return; // immediately stop processing segments if suspend requested during service() - - // process transition (mode changes in the middle of transition) - seg.handleTransition(); - // reset the segment runtime data if needed - seg.resetIfRequired(); - - if (!seg.isActive()) continue; - - // last condition ensures all solid segments are updated at the same time - if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) - { - doShow = true; - unsigned delay = FRAMETIME; - - if (!seg.freeze) { //only run effect function if not frozen - int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) - _virtualSegmentLength = seg.virtualLength(); //SEGLEN - _colors_t[0] = gamma32(seg.currentColor(0)); - _colors_t[1] = gamma32(seg.currentColor(1)); - _colors_t[2] = gamma32(seg.currentColor(2)); - seg.setCurrentPalette(); // load actual palette - // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio - // when cctFromRgb is true we implicitly calculate WW and CW from RGB values - if (cctFromRgb) BusManager::setSegmentCCT(-1); - else BusManager::setSegmentCCT(seg.currentBri(true), correctWB); - // Effect blending - // When two effects are being blended, each may have different segment data, this - // data needs to be saved first and then restored before running previous mode. - // The blending will largely depend on the effect behaviour since actual output (LEDs) may be - // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer - // would need to be allocated for each effect and then blended together for each pixel. - [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition - delay = (*_mode[seg.mode])(); // run new/current mode -#ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending && seg.mode != tmpMode) { - Segment::tmpsegd_t _tmpSegData; - Segment::modeBlend(true); // set semaphore - seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) - _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) - unsigned d2 = (*_mode[tmpMode])(); // run old mode - seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) - delay = MIN(delay,d2); // use shortest delay - Segment::modeBlend(false); // unset semaphore - } -#endif - seg.call++; - if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition - BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments - } - - seg.next_time = nowUp + delay; - } - _segment_index++; - } - _virtualSegmentLength = 0; - _isServicing = false; - _triggered = false; - - #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); - #endif - if (doShow) { - yield(); - Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette - show(); - } - #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); - #endif -} - -void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) { - i = getMappedPixelIndex(i); - if (i >= _length) return; - BusManager::setPixelColor(i, col); -} - -uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) { - i = getMappedPixelIndex(i); - if (i >= _length) return 0; - return BusManager::getPixelColor(i); -} - -void WS2812FX::show(void) { - // avoid race condition, capture _callback value - show_callback callback = _callback; - if (callback) callback(); - - // some buses send asynchronously and this method will return before - // all of the data has been sent. - // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods - BusManager::show(); - - unsigned long showNow = millis(); - size_t diff = showNow - _lastShow; - size_t fpsCurr = 200; - if (diff > 0) fpsCurr = 1000 / diff; - _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) - _lastShow = showNow; -} - -/** - * Returns a true value if any of the strips are still being updated. - * On some hardware (ESP32), strip updates are done asynchronously. - */ -bool WS2812FX::isUpdating() { - return !BusManager::canAllShow(); -} - -/** - * Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough. - * Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accuracy varies - */ -uint16_t WS2812FX::getFps() { - if (millis() - _lastShow > 2000) return 0; - return _cumulativeFps +1; -} - -void WS2812FX::setTargetFps(uint8_t fps) { - if (fps > 0 && fps <= 120) _targetFps = fps; - _frametime = 1000 / _targetFps; -} - -void WS2812FX::setMode(uint8_t segid, uint8_t m) { - if (segid >= _segments.size()) return; - - if (m >= getModeCount()) m = getModeCount() - 1; - - if (_segments[segid].mode != m) { - _segments[segid].setMode(m); // do not load defaults - } -} - -//applies to all active and selected segments -void WS2812FX::setColor(uint8_t slot, uint32_t c) { - if (slot >= NUM_COLORS) return; - - for (segment &seg : _segments) { - if (seg.isActive() && seg.isSelected()) { - seg.setColor(slot, c); - } - } -} - -void WS2812FX::setCCT(uint16_t k) { - for (segment &seg : _segments) { - if (seg.isActive() && seg.isSelected()) { - seg.setCCT(k); - } - } -} - -// direct=true either expects the caller to call show() themselves (realtime modes) or be ok waiting for the next frame for the change to apply -// direct=false immediately triggers an effect redraw -void WS2812FX::setBrightness(uint8_t b, bool direct) { - if (gammaCorrectBri) b = gamma8(b); - if (_brightness == b) return; - _brightness = b; - if (_brightness == 0) { //unfreeze all segments on power off - for (segment &seg : _segments) { - seg.freeze = false; - } - } - // setting brightness with NeoPixelBusLg has no effect on already painted pixels, - // so we need to force an update to existing buffer - BusManager::setBrightness(b); - if (!direct) { - unsigned long t = millis(); - if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon - } -} - -uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) { - uint8_t totalLC = 0; - for (segment &seg : _segments) { - if (seg.isActive() && (!selectedOnly || seg.isSelected())) totalLC |= seg.getLightCapabilities(); - } - return totalLC; -} - -uint8_t WS2812FX::getFirstSelectedSegId(void) { - size_t i = 0; - for (segment &seg : _segments) { - if (seg.isActive() && seg.isSelected()) return i; - i++; - } - // if none selected, use the main segment - return getMainSegmentId(); -} - -void WS2812FX::setMainSegmentId(uint8_t n) { - _mainSegment = 0; - if (n < _segments.size()) { - _mainSegment = n; - } - return; -} - -uint8_t WS2812FX::getLastActiveSegmentId(void) { - for (size_t i = _segments.size() -1; i > 0; i--) { - if (_segments[i].isActive()) return i; - } - return 0; -} - -uint8_t WS2812FX::getActiveSegmentsNum(void) { - uint8_t c = 0; - for (size_t i = 0; i < _segments.size(); i++) { - if (_segments[i].isActive()) c++; - } - return c; -} - -uint16_t WS2812FX::getLengthTotal(void) { - unsigned len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D - if (isMatrix && _length > len) len = _length; // for 2D with trailing strip - return len; -} - -uint16_t WS2812FX::getLengthPhysical(void) { - unsigned len = 0; - for (size_t b = 0; b < BusManager::getNumBusses(); b++) { - Bus *bus = BusManager::getBus(b); - if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses - len += bus->getLength(); - } - return len; -} - -//used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw. -//returns if there is an RGBW bus (supports RGB and White, not only white) -//not influenced by auto-white mode, also true if white slider does not affect output white channel -bool WS2812FX::hasRGBWBus(void) { - for (size_t b = 0; b < BusManager::getNumBusses(); b++) { - Bus *bus = BusManager::getBus(b); - if (bus == nullptr || bus->getLength()==0) break; - if (bus->hasRGB() && bus->hasWhite()) return true; - } - return false; -} - -bool WS2812FX::hasCCTBus(void) { - if (cctFromRgb && !correctWB) return false; - for (size_t b = 0; b < BusManager::getNumBusses(); b++) { - Bus *bus = BusManager::getBus(b); - if (bus == nullptr || bus->getLength()==0) break; - if (bus->hasCCT()) return true; - } - return false; -} - -void WS2812FX::purgeSegments() { - // remove all inactive segments (from the back) - int deleted = 0; - if (_segments.size() <= 1) return; - for (size_t i = _segments.size()-1; i > 0; i--) - if (_segments[i].stop == 0) { - deleted++; - _segments.erase(_segments.begin() + i); - } - if (deleted) { - _segments.shrink_to_fit(); - setMainSegmentId(0); - } -} - -Segment& WS2812FX::getSegment(uint8_t id) { - return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors -} - -// sets new segment bounds, queues if that segment is currently running -void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) { - if (segId >= getSegmentsNum()) { - if (i2 <= i1) return; // do not append empty/inactive segments - appendSegment(Segment(0, strip.getLengthTotal())); - segId = getSegmentsNum()-1; // segments are added at the end of list - } - suspend(); - _segments[segId].setUp(i1, i2, grouping, spacing, offset, startY, stopY); - resume(); - if (segId > 0 && segId == getSegmentsNum()-1 && i2 <= i1) _segments.pop_back(); // if last segment was deleted remove it from vector -} - -void WS2812FX::resetSegments() { - _segments.clear(); // destructs all Segment as part of clearing - #ifndef WLED_DISABLE_2D - segment seg = isMatrix ? Segment(0, Segment::maxWidth, 0, Segment::maxHeight) : Segment(0, _length); - #else - segment seg = Segment(0, _length); - #endif - _segments.push_back(seg); - _segments.shrink_to_fit(); // just in case ... - _mainSegment = 0; -} - -void WS2812FX::makeAutoSegments(bool forceReset) { - if (autoSegments) { //make one segment per bus - unsigned segStarts[MAX_NUM_SEGMENTS] = {0}; - unsigned segStops [MAX_NUM_SEGMENTS] = {0}; - size_t s = 0; - - #ifndef WLED_DISABLE_2D - // 2D segment is the 1st one using entire matrix - if (isMatrix) { - segStarts[0] = 0; - segStops[0] = Segment::maxWidth*Segment::maxHeight; - s++; - } - #endif - - for (size_t i = s; i < BusManager::getNumBusses(); i++) { - Bus* b = BusManager::getBus(i); - - segStarts[s] = b->getStart(); - segStops[s] = segStarts[s] + b->getLength(); - - #ifndef WLED_DISABLE_2D - if (isMatrix && segStops[s] <= Segment::maxWidth*Segment::maxHeight) continue; // ignore buses comprising matrix - if (isMatrix && segStarts[s] < Segment::maxWidth*Segment::maxHeight) segStarts[s] = Segment::maxWidth*Segment::maxHeight; - #endif - - //check for overlap with previous segments - for (size_t j = 0; j < s; j++) { - if (segStops[j] > segStarts[s] && segStarts[j] < segStops[s]) { - //segments overlap, merge - segStarts[j] = min(segStarts[s],segStarts[j]); - segStops [j] = max(segStops [s],segStops [j]); segStops[s] = 0; - s--; - } - } - s++; - } - - _segments.clear(); - _segments.reserve(s); // prevent reallocations - // there is always at least one segment (but we need to differentiate between 1D and 2D) - #ifndef WLED_DISABLE_2D - if (isMatrix) - _segments.push_back(Segment(0, Segment::maxWidth, 0, Segment::maxHeight)); - else - #endif - _segments.push_back(Segment(segStarts[0], segStops[0])); - for (size_t i = 1; i < s; i++) { - _segments.push_back(Segment(segStarts[i], segStops[i])); - } - DEBUG_PRINTF_P(PSTR("%d auto segments created.\n"), _segments.size()); - - } else { - - if (forceReset || getSegmentsNum() == 0) resetSegments(); - //expand the main seg to the entire length, but only if there are no other segments, or reset is forced - else if (getActiveSegmentsNum() == 1) { - size_t i = getLastActiveSegmentId(); - #ifndef WLED_DISABLE_2D - _segments[i].start = 0; - _segments[i].stop = Segment::maxWidth; - _segments[i].startY = 0; - _segments[i].stopY = Segment::maxHeight; - _segments[i].grouping = 1; - _segments[i].spacing = 0; - #else - _segments[i].start = 0; - _segments[i].stop = _length; - #endif - } - } - _mainSegment = 0; - - fixInvalidSegments(); -} - -void WS2812FX::fixInvalidSegments() { - //make sure no segment is longer than total (sanity check) - for (size_t i = getSegmentsNum()-1; i > 0; i--) { - if (isMatrix) { - #ifndef WLED_DISABLE_2D - if (_segments[i].start >= Segment::maxWidth * Segment::maxHeight) { - // 1D segment at the end of matrix - if (_segments[i].start >= _length || _segments[i].startY > 0 || _segments[i].stopY > 1) { _segments.erase(_segments.begin()+i); continue; } - if (_segments[i].stop > _length) _segments[i].stop = _length; - continue; - } - if (_segments[i].start >= Segment::maxWidth || _segments[i].startY >= Segment::maxHeight) { _segments.erase(_segments.begin()+i); continue; } - if (_segments[i].stop > Segment::maxWidth) _segments[i].stop = Segment::maxWidth; - if (_segments[i].stopY > Segment::maxHeight) _segments[i].stopY = Segment::maxHeight; - #endif - } else { - if (_segments[i].start >= _length) { _segments.erase(_segments.begin()+i); continue; } - if (_segments[i].stop > _length) _segments[i].stop = _length; - } - } - // if any segments were deleted free memory - purgeSegments(); - // this is always called as the last step after finalizeInit(), update covered bus types - for (segment &seg : _segments) - seg.refreshLightCapabilities(); -} - -//true if all segments align with a bus, or if a segment covers the total length -//irrelevant in 2D set-up -bool WS2812FX::checkSegmentAlignment() { - bool aligned = false; - for (segment &seg : _segments) { - for (unsigned b = 0; bgetStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true; - } - if (seg.start == 0 && seg.stop == _length) aligned = true; - if (!aligned) return false; - } - return true; -} - -// used by analog clock overlay -void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) { - if (i2 < i) std::swap(i,i2); - for (unsigned x = i; x <= i2; x++) setPixelColor(x, col); -} - -#ifdef WLED_DEBUG -void WS2812FX::printSize() { - size_t size = 0; - for (const Segment &seg : _segments) size += seg.getSize(); - DEBUG_PRINTF_P(PSTR("Segments: %d -> %u/%dB\n"), _segments.size(), size, Segment::getUsedSegmentData()); - for (const Segment &seg : _segments) DEBUG_PRINTF_P(PSTR(" Seg: %d,%d [A=%d, 2D=%d, RGB=%d, W=%d, CCT=%d]\n"), seg.width(), seg.height(), seg.isActive(), seg.is2D(), seg.hasRGB(), seg.hasWhite(), seg.isCCT()); - DEBUG_PRINTF_P(PSTR("Modes: %d*%d=%uB\n"), sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr))); - DEBUG_PRINTF_P(PSTR("Data: %d*%d=%uB\n"), sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *))); - DEBUG_PRINTF_P(PSTR("Map: %d*%d=%uB\n"), sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t)); -} -#endif - -void WS2812FX::loadCustomPalettes() { - byte tcp[72]; //support gradient palettes with up to 18 entries - CRGBPalette16 targetPalette; - customPalettes.clear(); // start fresh - for (int index = 0; index<10; index++) { - char fileName[32]; - sprintf_P(fileName, PSTR("/palette%d.json"), index); - - StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers - if (WLED_FS.exists(fileName)) { - DEBUG_PRINT(F("Reading palette from ")); - DEBUG_PRINTLN(fileName); - - if (readObjectFromFile(fileName, nullptr, &pDoc)) { - JsonArray pal = pDoc[F("palette")]; - if (!pal.isNull() && pal.size()>3) { // not an empty palette (at least 2 entries) - if (pal[0].is() && pal[1].is()) { - // we have an array of index & hex strings - size_t palSize = MIN(pal.size(), 36); - palSize -= palSize % 2; // make sure size is multiple of 2 - for (size_t i=0, j=0; i()<256; i+=2, j+=4) { - uint8_t rgbw[] = {0,0,0,0}; - tcp[ j ] = (uint8_t) pal[ i ].as(); // index - colorFromHexString(rgbw, pal[i+1].as()); // will catch non-string entires - for (size_t c=0; c<3; c++) tcp[j+1+c] = gamma8(rgbw[c]); // only use RGB component - DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3])); - } - } else { - size_t palSize = MIN(pal.size(), 72); - palSize -= palSize % 4; // make sure size is multiple of 4 - for (size_t i=0; i()<256; i+=4) { - tcp[ i ] = (uint8_t) pal[ i ].as(); // index - tcp[i+1] = gamma8((uint8_t) pal[i+1].as()); // R - tcp[i+2] = gamma8((uint8_t) pal[i+2].as()); // G - tcp[i+3] = gamma8((uint8_t) pal[i+3].as()); // B - DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3])); - } - } - customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp)); - } else { - DEBUG_PRINTLN(F("Wrong palette format.")); - } - } - } else { - break; - } - } -} - -//load custom mapping table from JSON file (called from finalizeInit() or deserializeState()) -bool WS2812FX::deserializeMap(uint8_t n) { - // 2D support creates its own ledmap (on the fly) if a ledmap.json exists it will overwrite built one. - - char fileName[32]; - strcpy_P(fileName, PSTR("/ledmap")); - if (n) sprintf(fileName +7, "%d", n); - strcat_P(fileName, PSTR(".json")); - bool isFile = WLED_FS.exists(fileName); - - customMappingSize = 0; // prevent use of mapping if anything goes wrong - currentLedmap = 0; - if (n == 0 || isFile) interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update (to inform UI) - - if (!isFile && n==0 && isMatrix) { - setUpMatrix(); - return false; - } - - if (!isFile || !requestJSONBufferLock(7)) return false; - - if (!readObjectFromFile(fileName, nullptr, pDoc)) { - DEBUG_PRINT(F("ERROR Invalid ledmap in ")); DEBUG_PRINTLN(fileName); - releaseJSONBufferLock(); - return false; // if file does not load properly then exit - } - - JsonObject root = pDoc->as(); - // 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())) { - Segment::maxWidth = min(max(root[F("width")].as(), 1), 128); - Segment::maxHeight = min(max(root[F("height")].as(), 1), 128); - } - - if (customMappingTable) delete[] customMappingTable; - customMappingTable = new uint16_t[getLengthTotal()]; - - if (customMappingTable) { - DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName); - JsonArray map = root[F("map")]; - if (!map.isNull() && map.size()) { // not an empty map - customMappingSize = min((unsigned)map.size(), (unsigned)getLengthTotal()); - for (unsigned i=0; i 0); -} - -uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) { - // convert logical address to physical - if (index < customMappingSize - && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index]; - - return index; -} - - -WS2812FX* WS2812FX::instance = nullptr; - -const char JSON_mode_names[] PROGMEM = R"=====(["FX names moved"])====="; -const char JSON_palette_names[] PROGMEM = R"=====([ -"Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", -"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", -"Pastel","Sunset 2","Beach","Vintage","Departure","Landscape","Beech","Sherbet","Hult","Hult 64", -"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", -"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", -"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", -"Candy2" -])====="; +/* + WS2812FX_fcn.cpp contains all utility functions + Harm Aldick - 2016 + www.aldick.org + LICENSE + The MIT License (MIT) + Copyright (c) 2016 Harm Aldick + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + Modified heavily for WLED +*/ +#include "wled.h" +#include "FX.h" +#include "palettes.h" + +/* + Custom per-LED mapping has moved! + + Create a file "ledmap.json" using the edit page. + + this is just an example (30 LEDs). It will first set all even, then all uneven LEDs. + {"map":[ + 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, + 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29]} + + another example. Switches direction every 5 LEDs. + {"map":[ + 0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14, + 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]} +*/ + +//factory defaults LED setup +//#define PIXEL_COUNTS 30, 30, 30, 30 +//#define DATA_PINS 16, 1, 3, 4 +//#define DEFAULT_LED_TYPE TYPE_WS2812_RGB + +#ifndef PIXEL_COUNTS + #define PIXEL_COUNTS DEFAULT_LED_COUNT +#endif + +#ifndef DATA_PINS + #define DATA_PINS LEDPIN +#endif + +#ifndef DEFAULT_LED_TYPE + #define DEFAULT_LED_TYPE TYPE_WS2812_RGB +#endif + +#ifndef DEFAULT_LED_COLOR_ORDER + #define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB +#endif + + +#if MAX_NUM_SEGMENTS < WLED_MAX_BUSSES + #error "Max segments must be at least max number of busses!" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// Segment class implementation +/////////////////////////////////////////////////////////////////////////////// +uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[] +uint16_t Segment::maxWidth = DEFAULT_LED_COUNT; +uint16_t Segment::maxHeight = 1; + +CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black); +CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); +CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); +uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment +uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only) + +#ifndef WLED_DISABLE_MODE_BLEND +bool Segment::_modeBlend = false; +#endif + +// copy constructor +Segment::Segment(const Segment &orig) { + //DEBUG_PRINTF_P(PSTR("-- Copy segment constructor: %p -> %p\n"), &orig, this); + memcpy((void*)this, (void*)&orig, sizeof(Segment)); + _t = nullptr; // copied segment cannot be in transition + name = nullptr; + data = nullptr; + _dataLen = 0; + if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } + if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } +} + +// move constructor +Segment::Segment(Segment &&orig) noexcept { + //DEBUG_PRINTF_P(PSTR("-- Move segment constructor: %p -> %p\n"), &orig, this); + memcpy((void*)this, (void*)&orig, sizeof(Segment)); + orig._t = nullptr; // old segment cannot be in transition any more + orig.name = nullptr; + orig.data = nullptr; + orig._dataLen = 0; +} + +// copy assignment +Segment& Segment::operator= (const Segment &orig) { + //DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this); + if (this != &orig) { + // clean destination + if (name) { delete[] name; name = nullptr; } + stopTransition(); + deallocateData(); + // copy source + memcpy((void*)this, (void*)&orig, sizeof(Segment)); + // erase pointers to allocated data + data = nullptr; + _dataLen = 0; + // copy source data + if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } + if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } + } + return *this; +} + +// move assignment +Segment& Segment::operator= (Segment &&orig) noexcept { + //DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this); + if (this != &orig) { + if (name) { delete[] name; name = nullptr; } // free old name + stopTransition(); + deallocateData(); // free old runtime data + memcpy((void*)this, (void*)&orig, sizeof(Segment)); + orig.name = nullptr; + orig.data = nullptr; + orig._dataLen = 0; + orig._t = nullptr; // old segment cannot be in transition + } + return *this; +} + +// allocates effect data buffer on heap and initialises (erases) it +bool IRAM_ATTR Segment::allocateData(size_t len) { + if (len == 0) return false; // nothing to do + if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation) + if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation + return true; + } + //DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n", len, this); + deallocateData(); // if the old buffer was smaller release it first + if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) { + // not enough memory + DEBUG_PRINT(F("!!! Effect RAM depleted: ")); + DEBUG_PRINTF_P(PSTR("%d/%d !!!\n"), len, Segment::getUsedSegmentData()); + errorFlag = ERR_NORAM; + return false; + } + // do not use SPI RAM on ESP32 since it is slow + data = (byte*)calloc(len, sizeof(byte)); + if (!data) { DEBUG_PRINTLN(F("!!! Allocation failed. !!!")); return false; } // allocation failed + Segment::addUsedSegmentData(len); + //DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data); + _dataLen = len; + return true; +} + +void IRAM_ATTR Segment::deallocateData() { + if (!data) { _dataLen = 0; return; } + //DEBUG_PRINTF_P(PSTR("--- Released data (%p): %d/%d -> %p\n"), this, _dataLen, Segment::getUsedSegmentData(), data); + if ((Segment::getUsedSegmentData() > 0) && (_dataLen > 0)) { // check that we don't have a dangling / inconsistent data pointer + free(data); + } else { + DEBUG_PRINT(F("---- Released data ")); + DEBUG_PRINTF_P(PSTR("(%p): "), this); + DEBUG_PRINT(F("inconsistent UsedSegmentData ")); + DEBUG_PRINTF_P(PSTR("(%d/%d)"), _dataLen, Segment::getUsedSegmentData()); + DEBUG_PRINTLN(F(", cowardly refusing to free nothing.")); + } + data = nullptr; + Segment::addUsedSegmentData(_dataLen <= Segment::getUsedSegmentData() ? -_dataLen : -Segment::getUsedSegmentData()); + _dataLen = 0; +} + +/** + * If reset of this segment was requested, clears runtime + * settings of this segment. + * Must not be called while an effect mode function is running + * because it could access the data buffer and this method + * may free that data buffer. + */ +void Segment::resetIfRequired() { + if (!reset) return; + //DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this); + if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData()) + next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; + reset = false; +} + +CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { + if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; + if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip + //default palette. Differs depending on effect + if (pal == 0) switch (mode) { + case FX_MODE_FIRE_2012 : pal = 35; break; // heat palette + case FX_MODE_COLORWAVES : pal = 26; break; // landscape 33 + case FX_MODE_FILLNOISE8 : pal = 9; break; // ocean colors + case FX_MODE_NOISE16_1 : pal = 20; break; // Drywet + case FX_MODE_NOISE16_2 : pal = 43; break; // Blue cyan yellow + case FX_MODE_NOISE16_3 : pal = 35; break; // heat palette + case FX_MODE_NOISE16_4 : pal = 26; break; // landscape 33 + case FX_MODE_GLITTER : pal = 11; break; // rainbow colors + case FX_MODE_SUNRISE : pal = 35; break; // heat palette + case FX_MODE_RAILWAY : pal = 3; break; // prim + sec + case FX_MODE_2DSOAP : pal = 11; break; // rainbow colors + } + switch (pal) { + case 0: //default palette. Exceptions for specific effects above + targetPalette = PartyColors_p; break; + case 1: //randomly generated palette + targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette() + break; + case 2: {//primary color only + CRGB prim = gamma32(colors[0]); + targetPalette = CRGBPalette16(prim); break;} + case 3: {//primary + secondary + CRGB prim = gamma32(colors[0]); + CRGB sec = gamma32(colors[1]); + targetPalette = CRGBPalette16(prim,prim,sec,sec); break;} + case 4: {//primary + secondary + tertiary + CRGB prim = gamma32(colors[0]); + CRGB sec = gamma32(colors[1]); + CRGB ter = gamma32(colors[2]); + targetPalette = CRGBPalette16(ter,sec,prim); break;} + case 5: {//primary + secondary (+tertiary if not off), more distinct + CRGB prim = gamma32(colors[0]); + CRGB sec = gamma32(colors[1]); + if (colors[2]) { + CRGB ter = gamma32(colors[2]); + targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,ter,ter,ter,ter,ter,prim); + } else { + targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec); + } + break;} + case 6: //Party colors + targetPalette = PartyColors_p; break; + case 7: //Cloud colors + targetPalette = CloudColors_p; break; + case 8: //Lava colors + targetPalette = LavaColors_p; break; + case 9: //Ocean colors + targetPalette = OceanColors_p; break; + case 10: //Forest colors + targetPalette = ForestColors_p; break; + case 11: //Rainbow colors + targetPalette = RainbowColors_p; break; + case 12: //Rainbow stripe colors + targetPalette = RainbowStripeColors_p; break; + default: //progmem palettes + if (pal>245) { + targetPalette = strip.customPalettes[255-pal]; // we checked bounds above + } else { + byte tcp[72]; + memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72); + targetPalette.loadDynamicGradientPalette(tcp); + } + break; + } + return targetPalette; +} + +void Segment::startTransition(uint16_t dur) { + if (dur == 0) { + if (isInTransition()) _t->_dur = dur; // this will stop transition in next handleTransition() + return; + } + if (isInTransition()) return; // already in transition no need to store anything + + // starting a transition has to occur before change so we get current values 1st + _t = new Transition(dur); // no previous transition running + if (!_t) return; // failed to allocate data + + //DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t); + loadPalette(_t->_palT, palette); + _t->_briT = on ? opacity : 0; + _t->_cctT = cct; +#ifndef WLED_DISABLE_MODE_BLEND + if (modeBlending) { + swapSegenv(_t->_segT); + _t->_modeT = mode; + _t->_segT._dataLenT = 0; + _t->_segT._dataT = nullptr; + if (_dataLen > 0 && data) { + _t->_segT._dataT = (byte *)malloc(_dataLen); + if (_t->_segT._dataT) { + //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); + memcpy(_t->_segT._dataT, data, _dataLen); + _t->_segT._dataLenT = _dataLen; + } + } + } else { + for (size_t i=0; i_segT._colorT[i] = colors[i]; + } +#else + for (size_t i=0; i_colorT[i] = colors[i]; +#endif +} + +void Segment::stopTransition() { + if (isInTransition()) { + //DEBUG_PRINTF_P(PSTR("-- Stopping transition: %p\n"), this); + #ifndef WLED_DISABLE_MODE_BLEND + if (_t->_segT._dataT && _t->_segT._dataLenT > 0) { + //DEBUG_PRINTF_P(PSTR("-- Released duplicate data (%d) for %p: %p\n"), _t->_segT._dataLenT, this, _t->_segT._dataT); + free(_t->_segT._dataT); + _t->_segT._dataT = nullptr; + _t->_segT._dataLenT = 0; + } + #endif + delete _t; + _t = nullptr; + } +} + +void Segment::handleTransition() { + unsigned _progress = progress(); + if (_progress == 0xFFFFU) stopTransition(); +} + +// transition progression between 0-65535 +uint16_t IRAM_ATTR Segment::progress() { + if (isInTransition()) { + unsigned diff = millis() - _t->_start; + if (_t->_dur > 0 && diff < _t->_dur) return diff * 0xFFFFU / _t->_dur; + } + return 0xFFFFU; +} + +#ifndef WLED_DISABLE_MODE_BLEND +void Segment::swapSegenv(tmpsegd_t &tmpSeg) { + //DEBUG_PRINTF_P(PSTR("-- Saving temp seg: %p->(%p) [%d->%p]\n"), this, &tmpSeg, _dataLen, data); + tmpSeg._optionsT = options; + for (size_t i=0; i_segT)) { + // swap SEGENV with transitional data + options = _t->_segT._optionsT; + for (size_t i=0; i_segT._colorT[i]; + speed = _t->_segT._speedT; + intensity = _t->_segT._intensityT; + custom1 = _t->_segT._custom1T; + custom2 = _t->_segT._custom2T; + custom3 = _t->_segT._custom3T; + check1 = _t->_segT._check1T; + check2 = _t->_segT._check2T; + check3 = _t->_segT._check3T; + aux0 = _t->_segT._aux0T; + aux1 = _t->_segT._aux1T; + step = _t->_segT._stepT; + call = _t->_segT._callT; + data = _t->_segT._dataT; + _dataLen = _t->_segT._dataLenT; + } +} + +void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { + //DEBUG_PRINTF_P(PSTR("-- Restoring temp seg: %p->(%p) [%d->%p]\n"), &tmpSeg, this, _dataLen, data); + if (_t && &(_t->_segT) != &tmpSeg) { + // update possibly changed variables to keep old effect running correctly + _t->_segT._aux0T = aux0; + _t->_segT._aux1T = aux1; + _t->_segT._stepT = step; + _t->_segT._callT = call; + //if (_t->_segT._dataT != data) DEBUG_PRINTF_P(PSTR("--- data re-allocated: (%p) %p -> %p\n"), this, _t->_segT._dataT, data); + _t->_segT._dataT = data; + _t->_segT._dataLenT = _dataLen; + } + options = tmpSeg._optionsT; + for (size_t i=0; i_cctT : _t->_briT) * (0xFFFFU - prog); + return curBri / 0xFFFFU; + } + return (useCct ? cct : (on ? opacity : 0)); +} + +uint8_t IRAM_ATTR Segment::currentMode() { +#ifndef WLED_DISABLE_MODE_BLEND + unsigned prog = progress(); + if (modeBlending && prog < 0xFFFFU) return _t->_modeT; +#endif + return mode; +} + +uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { + if (slot >= NUM_COLORS) slot = 0; +#ifndef WLED_DISABLE_MODE_BLEND + return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot]; +#else + return isInTransition() ? color_blend(_t->_colorT[slot], colors[slot], progress(), true) : colors[slot]; +#endif +} + +void Segment::setCurrentPalette() { + loadPalette(_currentPalette, palette); + unsigned prog = progress(); + if (strip.paletteFade && prog < 0xFFFFU) { + // blend palettes + // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) + // minimum blend time is 100ms maximum is 65535ms + unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; + for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48); + _currentPalette = _t->_palT; // copy transitioning/temporary palette + } +} + +// relies on WS2812FX::service() to call it for each frame +void Segment::handleRandomPalette() { + // is it time to generate a new palette? + if ((uint16_t)((uint16_t)(millis() / 1000U) - _lastPaletteChange) > randomPaletteChangeTime){ + _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); + _lastPaletteChange = (uint16_t)(millis() / 1000U); + _lastPaletteBlend = (uint16_t)((uint16_t)millis() - 512); // starts blending immediately + } + + // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls) + if (strip.paletteFade) { + // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) + // in reality there need to be 255 blends to fully blend two entirely different palettes + if ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update + _lastPaletteBlend = (uint16_t)millis(); + } + nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); +} + +// segId is given when called from network callback, changes are queued if that segment is currently in its effect function +void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) { + // return if neither bounds nor grouping have changed + bool boundsUnchanged = (start == i1 && stop == i2); + #ifndef WLED_DISABLE_2D + if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D + #endif + if (boundsUnchanged + && (!grp || (grouping == grp && spacing == spc)) + && (ofs == UINT16_MAX || ofs == offset)) return; + + stateChanged = true; // send UDP/WS broadcast + + if (stop) fill(BLACK); // turn old segment range off (clears pixels if changing spacing) + if (grp) { // prevent assignment of 0 + grouping = grp; + spacing = spc; + } else { + grouping = 1; + spacing = 0; + } + if (ofs < UINT16_MAX) offset = ofs; + + DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1); + DEBUG_PRINT(','); DEBUG_PRINT(i2); + DEBUG_PRINT(F(" -> ")); DEBUG_PRINT(i1Y); + DEBUG_PRINT(','); DEBUG_PRINTLN(i2Y); + markForReset(); + if (boundsUnchanged) return; + + // apply change immediately + if (i2 <= i1) { //disable segment + stop = 0; + return; + } + if (i1 < Segment::maxWidth || (i1 >= Segment::maxWidth*Segment::maxHeight && i1 < strip.getLengthTotal())) start = i1; // Segment::maxWidth equals strip.getLengthTotal() for 1D + stop = i2 > Segment::maxWidth*Segment::maxHeight ? MIN(i2,strip.getLengthTotal()) : (i2 > Segment::maxWidth ? Segment::maxWidth : MAX(1,i2)); + startY = 0; + stopY = 1; + #ifndef WLED_DISABLE_2D + if (Segment::maxHeight>1) { // 2D + if (i1Y < Segment::maxHeight) startY = i1Y; + stopY = i2Y > Segment::maxHeight ? Segment::maxHeight : MAX(1,i2Y); + } + #endif + // safety check + if (start >= stop || startY >= stopY) { + stop = 0; + return; + } + refreshLightCapabilities(); +} + + +bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed + if (slot >= NUM_COLORS || c == colors[slot]) return false; + if (!_isRGB && !_hasW) { + if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black + if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black + } + if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + colors[slot] = c; + stateChanged = true; // send UDP/WS broadcast + return true; +} + +void Segment::setCCT(uint16_t k) { + if (k > 255) { //kelvin value, convert to 0-255 + if (k < 1900) k = 1900; + if (k > 10091) k = 10091; + k = (k - 1900) >> 5; + } + if (cct == k) return; + if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + cct = k; + stateChanged = true; // send UDP/WS broadcast +} + +void Segment::setOpacity(uint8_t o) { + if (opacity == o) return; + if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + opacity = o; + stateChanged = true; // send UDP/WS broadcast +} + +void Segment::setOption(uint8_t n, bool val) { + bool prevOn = on; + if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change + if (val) options |= 0x01 << n; + else options &= ~(0x01 << n); + if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast +} + +void Segment::setMode(uint8_t fx, bool loadDefaults) { + // skip reserved + while (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4) == 0) fx++; + if (fx >= strip.getModeCount()) fx = 0; // set solid mode + // if we have a valid mode & is not reserved + if (fx != mode) { +#ifndef WLED_DISABLE_MODE_BLEND + if (modeBlending) startTransition(strip.getTransition()); // set effect transitions +#endif + mode = fx; + // load default values from effect string + if (loadDefaults) { + int sOpt; + sOpt = extractModeDefaults(fx, "sx"); speed = (sOpt >= 0) ? sOpt : DEFAULT_SPEED; + sOpt = extractModeDefaults(fx, "ix"); intensity = (sOpt >= 0) ? sOpt : DEFAULT_INTENSITY; + sOpt = extractModeDefaults(fx, "c1"); custom1 = (sOpt >= 0) ? sOpt : DEFAULT_C1; + sOpt = extractModeDefaults(fx, "c2"); custom2 = (sOpt >= 0) ? sOpt : DEFAULT_C2; + sOpt = extractModeDefaults(fx, "c3"); custom3 = (sOpt >= 0) ? sOpt : DEFAULT_C3; + sOpt = extractModeDefaults(fx, "o1"); check1 = (sOpt >= 0) ? (bool)sOpt : false; + sOpt = extractModeDefaults(fx, "o2"); check2 = (sOpt >= 0) ? (bool)sOpt : false; + sOpt = extractModeDefaults(fx, "o3"); check3 = (sOpt >= 0) ? (bool)sOpt : false; + sOpt = extractModeDefaults(fx, "m12"); if (sOpt >= 0) map1D2D = constrain(sOpt, 0, 7); else map1D2D = M12_Pixels; // reset mapping if not defined (2D FX may not work) + sOpt = extractModeDefaults(fx, "si"); if (sOpt >= 0) soundSim = constrain(sOpt, 0, 3); + sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) reverse = (bool)sOpt; + sOpt = extractModeDefaults(fx, "mi"); if (sOpt >= 0) mirror = (bool)sOpt; // NOTE: setting this option is a risky business + sOpt = extractModeDefaults(fx, "rY"); if (sOpt >= 0) reverse_y = (bool)sOpt; + sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business + sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) setPalette(sOpt); //else setPalette(0); + } + markForReset(); + stateChanged = true; // send UDP/WS broadcast + } +} + +void Segment::setPalette(uint8_t pal) { + if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes + if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes + if (pal != palette) { + if (strip.paletteFade) startTransition(strip.getTransition()); + palette = pal; + stateChanged = true; // send UDP/WS broadcast + } +} + +// 2D matrix +uint16_t IRAM_ATTR Segment::virtualWidth() const { + unsigned groupLen = groupLength(); + unsigned vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; + if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED + return vWidth; +} + +uint16_t IRAM_ATTR Segment::virtualHeight() const { + unsigned groupLen = groupLength(); + unsigned vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; + if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED + return vHeight; +} + +uint16_t IRAM_ATTR Segment::nrOfVStrips() const { + unsigned vLen = 1; +#ifndef WLED_DISABLE_2D + if (is2D()) { + switch (map1D2D) { + case M12_pBar: + vLen = virtualWidth(); + break; + } + } +#endif + return vLen; +} + +// Constants for mapping mode "Pinwheel" +#ifndef WLED_DISABLE_2D +constexpr int Pinwheel_Steps_Small = 72; // no holes up to 16x16 +constexpr int Pinwheel_Size_Small = 16; // larger than this -> use "Medium" +constexpr int Pinwheel_Steps_Medium = 192; // no holes up to 32x32 +constexpr int Pinwheel_Size_Medium = 32; // larger than this -> use "Big" +constexpr int Pinwheel_Steps_Big = 304; // no holes up to 50x50 +constexpr int Pinwheel_Size_Big = 50; // larger than this -> use "XL" +constexpr int Pinwheel_Steps_XL = 368; +constexpr float Int_to_Rad_Small = (DEG_TO_RAD * 360) / Pinwheel_Steps_Small; // conversion: from 0...72 to Radians +constexpr float Int_to_Rad_Med = (DEG_TO_RAD * 360) / Pinwheel_Steps_Medium; // conversion: from 0...192 to Radians +constexpr float Int_to_Rad_Big = (DEG_TO_RAD * 360) / Pinwheel_Steps_Big; // conversion: from 0...304 to Radians +constexpr float Int_to_Rad_XL = (DEG_TO_RAD * 360) / Pinwheel_Steps_XL; // conversion: from 0...368 to Radians + +constexpr int Fixed_Scale = 512; // fixpoint scaling factor (9bit for fraction) + +// Pinwheel helper function: pixel index to radians +static float getPinwheelAngle(int i, int vW, int vH) { + int maxXY = max(vW, vH); + if (maxXY <= Pinwheel_Size_Small) return float(i) * Int_to_Rad_Small; + if (maxXY <= Pinwheel_Size_Medium) return float(i) * Int_to_Rad_Med; + if (maxXY <= Pinwheel_Size_Big) return float(i) * Int_to_Rad_Big; + // else + return float(i) * Int_to_Rad_XL; +} +// Pinwheel helper function: matrix dimensions to number of rays +static int getPinwheelLength(int vW, int vH) { + int maxXY = max(vW, vH); + if (maxXY <= Pinwheel_Size_Small) return Pinwheel_Steps_Small; + if (maxXY <= Pinwheel_Size_Medium) return Pinwheel_Steps_Medium; + if (maxXY <= Pinwheel_Size_Big) return Pinwheel_Steps_Big; + // else + return Pinwheel_Steps_XL; +} +#endif + +// 1D strip +uint16_t IRAM_ATTR Segment::virtualLength() const { +#ifndef WLED_DISABLE_2D + if (is2D()) { + unsigned vW = virtualWidth(); + unsigned vH = virtualHeight(); + unsigned vLen = vW * vH; // use all pixels from segment + switch (map1D2D) { + case M12_pBar: + vLen = vH; + break; + case M12_pCorner: + case M12_pArc: + vLen = max(vW,vH); // get the longest dimension + break; + case M12_sPinwheel: + vLen = getPinwheelLength(vW, vH); + break; + } + return vLen; + } +#endif + unsigned groupLen = groupLength(); // is always >= 1 + unsigned vLength = (length() + groupLen - 1) / groupLen; + if (mirror) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED + return vLength; +} + +void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) +{ + if (!isActive()) return; // not active +#ifndef WLED_DISABLE_2D + int vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) +#endif + i &= 0xFFFF; + + if (i >= virtualLength() || i<0) return; // if pixel would fall out of segment just exit + +#ifndef WLED_DISABLE_2D + if (is2D()) { + int vH = virtualHeight(); // segment height in logical pixels + int vW = virtualWidth(); + switch (map1D2D) { + case M12_Pixels: + // use all available pixels as a long strip + setPixelColorXY(i % vW, i / vW, col); + break; + case M12_pBar: + // expand 1D effect vertically or have it play on virtual strips + if (vStrip>0) setPixelColorXY(vStrip - 1, vH - i - 1, col); + else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col); + break; + case M12_pArc: + // expand in circular fashion from center + if (i==0) + setPixelColorXY(0, 0, col); + else { + float step = HALF_PI / (2.85f*i); + for (float rad = 0.0f; rad <= HALF_PI+step/2; rad += step) { + // may want to try float version as well (with or without antialiasing) + int x = roundf(sin_t(rad) * i); + int y = roundf(cos_t(rad) * i); + setPixelColorXY(x, y, col); + } + // Bresenham’s Algorithm (may not fill every pixel) + //int d = 3 - (2*i); + //int y = i, x = 0; + //while (y >= x) { + // setPixelColorXY(x, y, col); + // setPixelColorXY(y, x, col); + // x++; + // if (d > 0) { + // y--; + // d += 4 * (x - y) + 10; + // } else { + // d += 4 * x + 6; + // } + //} + } + break; + case M12_pCorner: + for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col); + for (int y = 0; y < i; y++) setPixelColorXY(i, y, col); + break; + case M12_sPinwheel: { + // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small) + float centerX = roundf((vW-1) / 2.0f); + float centerY = roundf((vH-1) / 2.0f); + float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians + float cosVal = cos_t(angleRad); + float sinVal = sin_t(angleRad); + + // avoid re-painting the same pixel + int lastX = INT_MIN; // impossible position + int lastY = INT_MIN; // impossible position + // draw line at angle, starting at center and ending at the segment edge + // we use fixed point math for better speed. Starting distance is 0.5 for better rounding + // int_fast16_t and int_fast32_t types changed to int, minimum bits commented + int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit + int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit + int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit + int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit + + int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint + int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint + + // Odd rays start further from center if prevRay started at center. + static int prevRay = INT_MIN; // previous ray number + if ((i % 2 == 1) && (i - 1 == prevRay || i + 1 == prevRay)) { + int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel + posx += inc_x * jump; + posy += inc_y * jump; + } + prevRay = i; + + // draw ray until we hit any edge + while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) { + // scale down to integer (compiler will replace division with appropriate bitshift) + int x = posx / Fixed_Scale; + int y = posy / Fixed_Scale; + // set pixel + if (x != lastX || y != lastY) setPixelColorXY(x, y, col); // only paint if pixel position is different + lastX = x; + lastY = y; + // advance to next position + posx += inc_x; + posy += inc_y; + } + break; + } + } + return; + } else if (Segment::maxHeight!=1 && (width()==1 || height()==1)) { + if (start < Segment::maxWidth*Segment::maxHeight) { + // we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed) + int x = 0, y = 0; + if (virtualHeight()>1) y = i; + if (virtualWidth() >1) x = i; + setPixelColorXY(x, y, col); + return; + } + } +#endif + + unsigned len = length(); + uint8_t _bri_t = currentBri(); + if (_bri_t < 255) { + col = color_fade(col, _bri_t); + } + + // expand pixel (taking into account start, grouping, spacing [and offset]) + i = i * groupLength(); + if (reverse) { // is segment reversed? + if (mirror) { // is segment mirrored? + i = (len - 1) / 2 - i; //only need to index half the pixels + } else { + i = (len - 1) - i; + } + } + i += start; // starting pixel in a group + + uint32_t tmpCol = col; + // set all the pixels in the group + for (int j = 0; j < grouping; j++) { + unsigned indexSet = i + ((reverse) ? -j : j); + if (indexSet >= start && indexSet < stop) { + if (mirror) { //set the corresponding mirrored pixel + unsigned indexMir = stop - indexSet + start - 1; + indexMir += offset; // offset/phase + if (indexMir >= stop) indexMir -= len; // wrap +#ifndef WLED_DISABLE_MODE_BLEND + if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true); +#endif + strip.setPixelColor(indexMir, tmpCol); + } + indexSet += offset; // offset/phase + if (indexSet >= stop) indexSet -= len; // wrap +#ifndef WLED_DISABLE_MODE_BLEND + if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true); +#endif + strip.setPixelColor(indexSet, tmpCol); + } + } +} + +#ifdef WLED_USE_AA_PIXELS +// anti-aliased normalized version of setPixelColor() +void Segment::setPixelColor(float i, uint32_t col, bool aa) +{ + if (!isActive()) return; // not active + int vStrip = int(i/10.0f); // hack to allow running on virtual strips (2D segment columns/rows) + i -= int(i); + + if (i<0.0f || i>1.0f) return; // not normalized + + float fC = i * (virtualLength()-1); + if (aa) { + unsigned iL = roundf(fC-0.49f); + unsigned iR = roundf(fC+0.49f); + float dL = (fC - iL)*(fC - iL); + float dR = (iR - fC)*(iR - fC); + uint32_t cIL = getPixelColor(iL | (vStrip<<16)); + uint32_t cIR = getPixelColor(iR | (vStrip<<16)); + if (iR!=iL) { + // blend L pixel + cIL = color_blend(col, cIL, uint8_t(dL*255.0f)); + setPixelColor(iL | (vStrip<<16), cIL); + // blend R pixel + cIR = color_blend(col, cIR, uint8_t(dR*255.0f)); + setPixelColor(iR | (vStrip<<16), cIR); + } else { + // exact match (x & y land on a pixel) + setPixelColor(iL | (vStrip<<16), col); + } + } else { + setPixelColor(int(roundf(fC)) | (vStrip<<16), col); + } +} +#endif + +uint32_t IRAM_ATTR Segment::getPixelColor(int i) +{ + if (!isActive()) return 0; // not active +#ifndef WLED_DISABLE_2D + int vStrip = i>>16; +#endif + i &= 0xFFFF; + +#ifndef WLED_DISABLE_2D + if (is2D()) { + unsigned vH = virtualHeight(); // segment height in logical pixels + unsigned vW = virtualWidth(); + switch (map1D2D) { + case M12_Pixels: + return getPixelColorXY(i % vW, i / vW); + break; + case M12_pBar: + if (vStrip>0) return getPixelColorXY(vStrip - 1, vH - i -1); + else return getPixelColorXY(0, vH - i -1); + break; + case M12_pArc: + case M12_pCorner: + // use longest dimension + return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); + break; + case M12_sPinwheel: + // not 100% accurate, returns pixel at outer edge + // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small) + float centerX = roundf((vW-1) / 2.0f); + float centerY = roundf((vH-1) / 2.0f); + float angleRad = getPinwheelAngle(i, vW, vH); // angle in radians + float cosVal = cos_t(angleRad); + float sinVal = sin_t(angleRad); + + int posx = (centerX + 0.5f * cosVal) * Fixed_Scale; // X starting position in fixed point 18 bit + int posy = (centerY + 0.5f * sinVal) * Fixed_Scale; // Y starting position in fixed point 18 bit + int inc_x = cosVal * Fixed_Scale; // X increment per step (fixed point) 10 bit + int inc_y = sinVal * Fixed_Scale; // Y increment per step (fixed point) 10 bit + int32_t maxX = vW * Fixed_Scale; // X edge in fixedpoint + int32_t maxY = vH * Fixed_Scale; // Y edge in fixedpoint + + // trace ray from center until we hit any edge - to avoid rounding problems, we use the same method as in setPixelColor + int x = INT_MIN; + int y = INT_MIN; + while ((posx >= 0) && (posy >= 0) && (posx < maxX) && (posy < maxY)) { + // scale down to integer (compiler will replace division with appropriate bitshift) + x = posx / Fixed_Scale; + y = posy / Fixed_Scale; + // advance to next position + posx += inc_x; + posy += inc_y; + } + return getPixelColorXY(x, y); + break; + } + return 0; + } +#endif + + if (reverse) i = virtualLength() - i - 1; + i *= groupLength(); + i += start; + /* offset/phase */ + i += offset; + if ((i >= stop) && (stop>0)) i -= length(); // avoids negative pixel index (stop = 0 is a possible value) + return strip.getPixelColor(i); +} + +uint8_t Segment::differs(Segment& b) const { + uint8_t d = 0; + if (start != b.start) d |= SEG_DIFFERS_BOUNDS; + if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS; + if (offset != b.offset) d |= SEG_DIFFERS_GSO; + if (grouping != b.grouping) d |= SEG_DIFFERS_GSO; + if (spacing != b.spacing) d |= SEG_DIFFERS_GSO; + if (opacity != b.opacity) d |= SEG_DIFFERS_BRI; + if (mode != b.mode) d |= SEG_DIFFERS_FX; + if (speed != b.speed) d |= SEG_DIFFERS_FX; + if (intensity != b.intensity) d |= SEG_DIFFERS_FX; + if (palette != b.palette) d |= SEG_DIFFERS_FX; + if (custom1 != b.custom1) d |= SEG_DIFFERS_FX; + if (custom2 != b.custom2) d |= SEG_DIFFERS_FX; + if (custom3 != b.custom3) d |= SEG_DIFFERS_FX; + if (startY != b.startY) d |= SEG_DIFFERS_BOUNDS; + if (stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS; + + //bit pattern: (msb first) + // set:2, sound:2, mapping:3, transposed, mirrorY, reverseY, [reset,] paused, mirrored, on, reverse, [selected] + if ((options & 0b1111111111011110U) != (b.options & 0b1111111111011110U)) d |= SEG_DIFFERS_OPT; + if ((options & 0x0001U) != (b.options & 0x0001U)) d |= SEG_DIFFERS_SEL; + for (unsigned i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; + + return d; +} + +void Segment::refreshLightCapabilities() { + unsigned capabilities = 0; + unsigned segStartIdx = 0xFFFFU; + unsigned segStopIdx = 0; + + if (!isActive()) { + _capabilities = 0; + return; + } + + if (start < Segment::maxWidth * Segment::maxHeight) { + // we are withing 2D matrix (includes 1D segments) + for (int y = startY; y < stopY; y++) for (int x = start; x < stop; x++) { + unsigned index = strip.getMappedPixelIndex(x + Segment::maxWidth * y); // convert logical address to physical + if (index < 0xFFFFU) { + if (segStartIdx > index) segStartIdx = index; + if (segStopIdx < index) segStopIdx = index; + } + if (segStartIdx == segStopIdx) segStopIdx++; // we only have 1 pixel segment + } + } else { + // we are on the strip located after the matrix + segStartIdx = start; + segStopIdx = stop; + } + + for (unsigned b = 0; b < BusManager::getNumBusses(); b++) { + Bus *bus = BusManager::getBus(b); + if (bus == nullptr || bus->getLength()==0) break; + if (!bus->isOk()) continue; + if (bus->getStart() >= segStopIdx) continue; + if (bus->getStart() + bus->getLength() <= segStartIdx) continue; + + //uint8_t type = bus->getType(); + if (bus->hasRGB() || (cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB; + if (!cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT; + if (correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider) + if (bus->hasWhite()) { + unsigned aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode(); + bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed + // if auto white calculation from RGB is active (Accurate/Brighter), force RGB controls even if there are no RGB busses + if (!whiteSlider) capabilities |= SEG_CAPABILITY_RGB; + // if auto white calculation from RGB is disabled/optional (None/Dual), allow white channel adjustments + if ( whiteSlider) capabilities |= SEG_CAPABILITY_W; + } + } + _capabilities = capabilities; +} + +/* + * Fills segment with color + */ +void Segment::fill(uint32_t c) { + if (!isActive()) return; // not active + const int cols = is2D() ? virtualWidth() : virtualLength(); + const int rows = virtualHeight(); // will be 1 for 1D + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { + if (is2D()) setPixelColorXY(x, y, c); + else setPixelColor(x, c); + } +} + +/* + * fade out function, higher rate = quicker fade + */ +void Segment::fade_out(uint8_t rate) { + if (!isActive()) return; // not active + const int cols = is2D() ? virtualWidth() : virtualLength(); + const int rows = virtualHeight(); // will be 1 for 1D + + rate = (255-rate) >> 1; + float mappedRate = float(rate) +1.1f; + + uint32_t color = colors[1]; // SEGCOLOR(1); // target color + int w2 = W(color); + int r2 = R(color); + int g2 = G(color); + int b2 = B(color); + + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { + color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); + int w1 = W(color); + int r1 = R(color); + int g1 = G(color); + int b1 = B(color); + + int wdelta = (w2 - w1) / mappedRate; + int rdelta = (r2 - r1) / mappedRate; + int gdelta = (g2 - g1) / mappedRate; + int bdelta = (b2 - b1) / mappedRate; + + // if fade isn't complete, make sure delta is at least 1 (fixes rounding issues) + wdelta += (w2 == w1) ? 0 : (w2 > w1) ? 1 : -1; + rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1; + gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1; + bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1; + + if (is2D()) setPixelColorXY(x, y, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); + else setPixelColor(x, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); + } +} + +// fades all pixels to black using nscale8() +void Segment::fadeToBlackBy(uint8_t fadeBy) { + if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply + const int cols = is2D() ? virtualWidth() : virtualLength(); + const int rows = virtualHeight(); // will be 1 for 1D + + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { + if (is2D()) setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), 255-fadeBy)); + else setPixelColor(x, color_fade(getPixelColor(x), 255-fadeBy)); + } +} + +/* + * blurs segment content, source: FastLED colorutils.cpp + */ +void Segment::blur(uint8_t blur_amount, bool smear) { + if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur" +#ifndef WLED_DISABLE_2D + if (is2D()) { + // compatibility with 2D + const unsigned cols = virtualWidth(); + const unsigned rows = virtualHeight(); + for (unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear); // blur all rows + for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear); // blur all columns + return; + } +#endif + uint8_t keep = smear ? 255 : 255 - blur_amount; + uint8_t seep = blur_amount >> 1; + unsigned vlength = virtualLength(); + uint32_t carryover = BLACK; + uint32_t lastnew; + uint32_t last; + uint32_t curnew = BLACK; + for (unsigned i = 0; i < vlength; i++) { + uint32_t cur = getPixelColor(i); + uint32_t part = color_fade(cur, seep); + curnew = color_fade(cur, keep); + if (i > 0) { + if (carryover) + curnew = color_add(curnew, carryover, true); + uint32_t prev = color_add(lastnew, part, true); + if (last != prev) // optimization: only set pixel if color has changed + setPixelColor(i - 1, prev); + } + else // first pixel + setPixelColor(i, curnew); + lastnew = curnew; + last = cur; // save original value for comparison on next iteration + carryover = part; + } + setPixelColor(vlength - 1, curnew); +} + +/* + * Put a value 0 to 255 in to get a color value. + * The colours are a transition r -> g -> b -> back to r + * Inspired by the Adafruit examples. + */ +uint32_t Segment::color_wheel(uint8_t pos) { + if (palette) return color_from_palette(pos, false, true, 0); // perhaps "strip.paletteBlend < 2" should be better instead of "true" + uint8_t w = W(currentColor(0)); + pos = 255 - pos; + if (pos < 85) { + return RGBW32((255 - pos * 3), 0, (pos * 3), w); + } else if(pos < 170) { + pos -= 85; + return RGBW32(0, (pos * 3), (255 - pos * 3), w); + } else { + pos -= 170; + return RGBW32((pos * 3), (255 - pos * 3), 0, w); + } +} + +/* + * Gets a single color from the currently selected palette. + * @param i Palette Index (if mapping is true, the full palette will be _virtualSegmentLength long, if false, 255). Will wrap around automatically. + * @param mapping if true, LED position in segment is considered for color + * @param wrap FastLED palettes will usually wrap back to the start smoothly. Set false to get a hard edge + * @param mcol If the default palette 0 is selected, return the standard color 0, 1 or 2 instead. If >2, Party palette is used instead + * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling) + * @returns Single color from palette + */ +uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) { + uint32_t color = gamma32(currentColor(mcol)); + + // default palette or no RGB support on segment + if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); + + unsigned paletteIndex = i; + if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); + // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) + if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" + CRGB fastled_col = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global + + return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, W(color)); +} + + +/////////////////////////////////////////////////////////////////////////////// +// WS2812FX class implementation +/////////////////////////////////////////////////////////////////////////////// + +//do not call this method from system context (network callback) +void WS2812FX::finalizeInit(void) { + //reset segment runtimes + for (segment &seg : _segments) { + seg.markForReset(); + seg.resetIfRequired(); + } + + // for the lack of better place enumerate ledmaps here + // if we do it in json.cpp (serializeInfo()) we are getting flashes on LEDs + // unfortunately this means we do not get updates after uploads + // the other option is saving UI settings which will cause enumeration + enumerateLedmaps(); + + _hasWhiteChannel = _isOffRefreshRequired = false; + + //if busses failed to load, add default (fresh install, FS issue, ...) + if (BusManager::getNumBusses() == 0) { + DEBUG_PRINTLN(F("No busses, init default")); + const unsigned defDataPins[] = {DATA_PINS}; + const unsigned defCounts[] = {PIXEL_COUNTS}; + const unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0])); + const unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); + const unsigned defNumBusses = defNumPins > defNumCounts && defNumCounts > 1 && defNumPins%defNumCounts == 0 ? defNumCounts : defNumPins; + const unsigned pinsPerBus = defNumPins / defNumBusses; + unsigned prevLen = 0; + for (unsigned i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { + uint8_t defPin[5]; // max 5 pins + for (unsigned j = 0; j < pinsPerBus; j++) defPin[j] = defDataPins[i*pinsPerBus + j]; + // when booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware + // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), etc + if (pinManager.isPinAllocated(defPin[0])) { + defPin[0] = 1; // start with GPIO1 and work upwards + while (pinManager.isPinAllocated(defPin[0]) && defPin[0] < WLED_NUM_PINS) defPin[0]++; + } + unsigned start = prevLen; + unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; + prevLen += count; + BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer); + if (BusManager::add(defCfg) == -1) break; + } + } + + _length = 0; + for (int i=0; igetStart() + bus->getLength() > MAX_LEDS) break; + //RGBW mode is enabled if at least one of the strips is RGBW + _hasWhiteChannel |= bus->hasWhite(); + //refresh is required to remain off if at least one of the strips requires the refresh. + _isOffRefreshRequired |= bus->isOffRefreshRequired(); + unsigned busEnd = bus->getStart() + bus->getLength(); + if (busEnd > _length) _length = busEnd; + #ifdef ESP8266 + // why do we need to reinitialise GPIO3??? + //if ((!IS_DIGITAL(bus->getType()) || IS_2PIN(bus->getType()))) continue; + //uint8_t pins[5]; + //if (!bus->getPins(pins)) continue; + //BusDigital* bd = static_cast(bus); + //if (pins[0] == 3) bd->reinit(); + #endif + } + + Segment::maxWidth = _length; + Segment::maxHeight = 1; + + //segments are created in makeAutoSegments(); + DEBUG_PRINTLN(F("Loading custom palettes")); + loadCustomPalettes(); // (re)load all custom palettes + DEBUG_PRINTLN(F("Loading custom ledmaps")); + deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist) +} + +void WS2812FX::service() { + unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days + now = nowUp + timebase; + if (nowUp - _lastShow < MIN_SHOW_DELAY || _suspend) return; + bool doShow = false; + + _isServicing = true; + _segment_index = 0; + + for (segment &seg : _segments) { + if (_suspend) return; // immediately stop processing segments if suspend requested during service() + + // process transition (mode changes in the middle of transition) + seg.handleTransition(); + // reset the segment runtime data if needed + seg.resetIfRequired(); + + if (!seg.isActive()) continue; + + // last condition ensures all solid segments are updated at the same time + if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) + { + doShow = true; + unsigned delay = FRAMETIME; + + if (!seg.freeze) { //only run effect function if not frozen + int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) + _virtualSegmentLength = seg.virtualLength(); //SEGLEN + _colors_t[0] = gamma32(seg.currentColor(0)); + _colors_t[1] = gamma32(seg.currentColor(1)); + _colors_t[2] = gamma32(seg.currentColor(2)); + seg.setCurrentPalette(); // load actual palette + // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio + // when cctFromRgb is true we implicitly calculate WW and CW from RGB values + if (cctFromRgb) BusManager::setSegmentCCT(-1); + else BusManager::setSegmentCCT(seg.currentBri(true), correctWB); + // Effect blending + // When two effects are being blended, each may have different segment data, this + // data needs to be saved first and then restored before running previous mode. + // The blending will largely depend on the effect behaviour since actual output (LEDs) may be + // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer + // would need to be allocated for each effect and then blended together for each pixel. + [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition + delay = (*_mode[seg.mode])(); // run new/current mode +#ifndef WLED_DISABLE_MODE_BLEND + if (modeBlending && seg.mode != tmpMode) { + Segment::tmpsegd_t _tmpSegData; + Segment::modeBlend(true); // set semaphore + seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) + _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) + unsigned d2 = (*_mode[tmpMode])(); // run old mode + seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) + delay = MIN(delay,d2); // use shortest delay + Segment::modeBlend(false); // unset semaphore + } +#endif + seg.call++; + if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition + BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments + } + + seg.next_time = nowUp + delay; + } + _segment_index++; + } + _virtualSegmentLength = 0; + _isServicing = false; + _triggered = false; + + #ifdef WLED_DEBUG + if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + #endif + if (doShow) { + yield(); + Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette + show(); + } + #ifdef WLED_DEBUG + if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + #endif +} + +void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) { + i = getMappedPixelIndex(i); + if (i >= _length) return; + BusManager::setPixelColor(i, col); +} + +uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) { + i = getMappedPixelIndex(i); + if (i >= _length) return 0; + return BusManager::getPixelColor(i); +} + +void WS2812FX::show(void) { + // avoid race condition, capture _callback value + show_callback callback = _callback; + if (callback) callback(); + + // some buses send asynchronously and this method will return before + // all of the data has been sent. + // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods + BusManager::show(); + + unsigned long showNow = millis(); + size_t diff = showNow - _lastShow; + size_t fpsCurr = 200; + if (diff > 0) fpsCurr = 1000 / diff; + _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) + _lastShow = showNow; +} + +/** + * Returns a true value if any of the strips are still being updated. + * On some hardware (ESP32), strip updates are done asynchronously. + */ +bool WS2812FX::isUpdating() { + return !BusManager::canAllShow(); +} + +/** + * Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough. + * Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accuracy varies + */ +uint16_t WS2812FX::getFps() { + if (millis() - _lastShow > 2000) return 0; + return _cumulativeFps +1; +} + +void WS2812FX::setTargetFps(uint8_t fps) { + if (fps > 0 && fps <= 120) _targetFps = fps; + _frametime = 1000 / _targetFps; +} + +void WS2812FX::setMode(uint8_t segid, uint8_t m) { + if (segid >= _segments.size()) return; + + if (m >= getModeCount()) m = getModeCount() - 1; + + if (_segments[segid].mode != m) { + _segments[segid].setMode(m); // do not load defaults + } +} + +//applies to all active and selected segments +void WS2812FX::setColor(uint8_t slot, uint32_t c) { + if (slot >= NUM_COLORS) return; + + for (segment &seg : _segments) { + if (seg.isActive() && seg.isSelected()) { + seg.setColor(slot, c); + } + } +} + +void WS2812FX::setCCT(uint16_t k) { + for (segment &seg : _segments) { + if (seg.isActive() && seg.isSelected()) { + seg.setCCT(k); + } + } +} + +// direct=true either expects the caller to call show() themselves (realtime modes) or be ok waiting for the next frame for the change to apply +// direct=false immediately triggers an effect redraw +void WS2812FX::setBrightness(uint8_t b, bool direct) { + if (gammaCorrectBri) b = gamma8(b); + if (_brightness == b) return; + _brightness = b; + if (_brightness == 0) { //unfreeze all segments on power off + for (segment &seg : _segments) { + seg.freeze = false; + } + } + // setting brightness with NeoPixelBusLg has no effect on already painted pixels, + // so we need to force an update to existing buffer + BusManager::setBrightness(b); + if (!direct) { + unsigned long t = millis(); + if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon + } +} + +uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) { + uint8_t totalLC = 0; + for (segment &seg : _segments) { + if (seg.isActive() && (!selectedOnly || seg.isSelected())) totalLC |= seg.getLightCapabilities(); + } + return totalLC; +} + +uint8_t WS2812FX::getFirstSelectedSegId(void) { + size_t i = 0; + for (segment &seg : _segments) { + if (seg.isActive() && seg.isSelected()) return i; + i++; + } + // if none selected, use the main segment + return getMainSegmentId(); +} + +void WS2812FX::setMainSegmentId(uint8_t n) { + _mainSegment = 0; + if (n < _segments.size()) { + _mainSegment = n; + } + return; +} + +uint8_t WS2812FX::getLastActiveSegmentId(void) { + for (size_t i = _segments.size() -1; i > 0; i--) { + if (_segments[i].isActive()) return i; + } + return 0; +} + +uint8_t WS2812FX::getActiveSegmentsNum(void) { + uint8_t c = 0; + for (size_t i = 0; i < _segments.size(); i++) { + if (_segments[i].isActive()) c++; + } + return c; +} + +uint16_t WS2812FX::getLengthTotal(void) { + unsigned len = Segment::maxWidth * Segment::maxHeight; // will be _length for 1D (see finalizeInit()) but should cover whole matrix for 2D + if (isMatrix && _length > len) len = _length; // for 2D with trailing strip + return len; +} + +uint16_t WS2812FX::getLengthPhysical(void) { + unsigned len = 0; + for (size_t b = 0; b < BusManager::getNumBusses(); b++) { + Bus *bus = BusManager::getBus(b); + if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses + len += bus->getLength(); + } + return len; +} + +//used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw. +//returns if there is an RGBW bus (supports RGB and White, not only white) +//not influenced by auto-white mode, also true if white slider does not affect output white channel +bool WS2812FX::hasRGBWBus(void) { + for (size_t b = 0; b < BusManager::getNumBusses(); b++) { + Bus *bus = BusManager::getBus(b); + if (bus == nullptr || bus->getLength()==0) break; + if (bus->hasRGB() && bus->hasWhite()) return true; + } + return false; +} + +bool WS2812FX::hasCCTBus(void) { + if (cctFromRgb && !correctWB) return false; + for (size_t b = 0; b < BusManager::getNumBusses(); b++) { + Bus *bus = BusManager::getBus(b); + if (bus == nullptr || bus->getLength()==0) break; + if (bus->hasCCT()) return true; + } + return false; +} + +void WS2812FX::purgeSegments() { + // remove all inactive segments (from the back) + int deleted = 0; + if (_segments.size() <= 1) return; + for (size_t i = _segments.size()-1; i > 0; i--) + if (_segments[i].stop == 0) { + deleted++; + _segments.erase(_segments.begin() + i); + } + if (deleted) { + _segments.shrink_to_fit(); + setMainSegmentId(0); + } +} + +Segment& WS2812FX::getSegment(uint8_t id) { + return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors +} + +// sets new segment bounds, queues if that segment is currently running +void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) { + if (segId >= getSegmentsNum()) { + if (i2 <= i1) return; // do not append empty/inactive segments + appendSegment(Segment(0, strip.getLengthTotal())); + segId = getSegmentsNum()-1; // segments are added at the end of list + } + suspend(); + _segments[segId].setUp(i1, i2, grouping, spacing, offset, startY, stopY); + resume(); + if (segId > 0 && segId == getSegmentsNum()-1 && i2 <= i1) _segments.pop_back(); // if last segment was deleted remove it from vector +} + +void WS2812FX::resetSegments() { + _segments.clear(); // destructs all Segment as part of clearing + #ifndef WLED_DISABLE_2D + segment seg = isMatrix ? Segment(0, Segment::maxWidth, 0, Segment::maxHeight) : Segment(0, _length); + #else + segment seg = Segment(0, _length); + #endif + _segments.push_back(seg); + _segments.shrink_to_fit(); // just in case ... + _mainSegment = 0; +} + +void WS2812FX::makeAutoSegments(bool forceReset) { + if (autoSegments) { //make one segment per bus + unsigned segStarts[MAX_NUM_SEGMENTS] = {0}; + unsigned segStops [MAX_NUM_SEGMENTS] = {0}; + size_t s = 0; + + #ifndef WLED_DISABLE_2D + // 2D segment is the 1st one using entire matrix + if (isMatrix) { + segStarts[0] = 0; + segStops[0] = Segment::maxWidth*Segment::maxHeight; + s++; + } + #endif + + for (size_t i = s; i < BusManager::getNumBusses(); i++) { + Bus* b = BusManager::getBus(i); + + segStarts[s] = b->getStart(); + segStops[s] = segStarts[s] + b->getLength(); + + #ifndef WLED_DISABLE_2D + if (isMatrix && segStops[s] <= Segment::maxWidth*Segment::maxHeight) continue; // ignore buses comprising matrix + if (isMatrix && segStarts[s] < Segment::maxWidth*Segment::maxHeight) segStarts[s] = Segment::maxWidth*Segment::maxHeight; + #endif + + //check for overlap with previous segments + for (size_t j = 0; j < s; j++) { + if (segStops[j] > segStarts[s] && segStarts[j] < segStops[s]) { + //segments overlap, merge + segStarts[j] = min(segStarts[s],segStarts[j]); + segStops [j] = max(segStops [s],segStops [j]); segStops[s] = 0; + s--; + } + } + s++; + } + + _segments.clear(); + _segments.reserve(s); // prevent reallocations + // there is always at least one segment (but we need to differentiate between 1D and 2D) + #ifndef WLED_DISABLE_2D + if (isMatrix) + _segments.push_back(Segment(0, Segment::maxWidth, 0, Segment::maxHeight)); + else + #endif + _segments.push_back(Segment(segStarts[0], segStops[0])); + for (size_t i = 1; i < s; i++) { + _segments.push_back(Segment(segStarts[i], segStops[i])); + } + DEBUG_PRINTF_P(PSTR("%d auto segments created.\n"), _segments.size()); + + } else { + + if (forceReset || getSegmentsNum() == 0) resetSegments(); + //expand the main seg to the entire length, but only if there are no other segments, or reset is forced + else if (getActiveSegmentsNum() == 1) { + size_t i = getLastActiveSegmentId(); + #ifndef WLED_DISABLE_2D + _segments[i].start = 0; + _segments[i].stop = Segment::maxWidth; + _segments[i].startY = 0; + _segments[i].stopY = Segment::maxHeight; + _segments[i].grouping = 1; + _segments[i].spacing = 0; + #else + _segments[i].start = 0; + _segments[i].stop = _length; + #endif + } + } + _mainSegment = 0; + + fixInvalidSegments(); +} + +void WS2812FX::fixInvalidSegments() { + //make sure no segment is longer than total (sanity check) + for (size_t i = getSegmentsNum()-1; i > 0; i--) { + if (isMatrix) { + #ifndef WLED_DISABLE_2D + if (_segments[i].start >= Segment::maxWidth * Segment::maxHeight) { + // 1D segment at the end of matrix + if (_segments[i].start >= _length || _segments[i].startY > 0 || _segments[i].stopY > 1) { _segments.erase(_segments.begin()+i); continue; } + if (_segments[i].stop > _length) _segments[i].stop = _length; + continue; + } + if (_segments[i].start >= Segment::maxWidth || _segments[i].startY >= Segment::maxHeight) { _segments.erase(_segments.begin()+i); continue; } + if (_segments[i].stop > Segment::maxWidth) _segments[i].stop = Segment::maxWidth; + if (_segments[i].stopY > Segment::maxHeight) _segments[i].stopY = Segment::maxHeight; + #endif + } else { + if (_segments[i].start >= _length) { _segments.erase(_segments.begin()+i); continue; } + if (_segments[i].stop > _length) _segments[i].stop = _length; + } + } + // if any segments were deleted free memory + purgeSegments(); + // this is always called as the last step after finalizeInit(), update covered bus types + for (segment &seg : _segments) + seg.refreshLightCapabilities(); +} + +//true if all segments align with a bus, or if a segment covers the total length +//irrelevant in 2D set-up +bool WS2812FX::checkSegmentAlignment() { + bool aligned = false; + for (segment &seg : _segments) { + for (unsigned b = 0; bgetStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true; + } + if (seg.start == 0 && seg.stop == _length) aligned = true; + if (!aligned) return false; + } + return true; +} + +// used by analog clock overlay +void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) { + if (i2 < i) std::swap(i,i2); + for (unsigned x = i; x <= i2; x++) setPixelColor(x, col); +} + +#ifdef WLED_DEBUG +void WS2812FX::printSize() { + size_t size = 0; + for (const Segment &seg : _segments) size += seg.getSize(); + DEBUG_PRINTF_P(PSTR("Segments: %d -> %u/%dB\n"), _segments.size(), size, Segment::getUsedSegmentData()); + for (const Segment &seg : _segments) DEBUG_PRINTF_P(PSTR(" Seg: %d,%d [A=%d, 2D=%d, RGB=%d, W=%d, CCT=%d]\n"), seg.width(), seg.height(), seg.isActive(), seg.is2D(), seg.hasRGB(), seg.hasWhite(), seg.isCCT()); + DEBUG_PRINTF_P(PSTR("Modes: %d*%d=%uB\n"), sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr))); + DEBUG_PRINTF_P(PSTR("Data: %d*%d=%uB\n"), sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *))); + DEBUG_PRINTF_P(PSTR("Map: %d*%d=%uB\n"), sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t)); +} +#endif + +void WS2812FX::loadCustomPalettes() { + byte tcp[72]; //support gradient palettes with up to 18 entries + CRGBPalette16 targetPalette; + customPalettes.clear(); // start fresh + for (int index = 0; index<10; index++) { + char fileName[32]; + sprintf_P(fileName, PSTR("/palette%d.json"), index); + + StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers + if (WLED_FS.exists(fileName)) { + DEBUG_PRINT(F("Reading palette from ")); + DEBUG_PRINTLN(fileName); + + if (readObjectFromFile(fileName, nullptr, &pDoc)) { + JsonArray pal = pDoc[F("palette")]; + if (!pal.isNull() && pal.size()>3) { // not an empty palette (at least 2 entries) + if (pal[0].is() && pal[1].is()) { + // we have an array of index & hex strings + size_t palSize = MIN(pal.size(), 36); + palSize -= palSize % 2; // make sure size is multiple of 2 + for (size_t i=0, j=0; i()<256; i+=2, j+=4) { + uint8_t rgbw[] = {0,0,0,0}; + tcp[ j ] = (uint8_t) pal[ i ].as(); // index + colorFromHexString(rgbw, pal[i+1].as()); // will catch non-string entires + for (size_t c=0; c<3; c++) tcp[j+1+c] = gamma8(rgbw[c]); // only use RGB component + DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3])); + } + } else { + size_t palSize = MIN(pal.size(), 72); + palSize -= palSize % 4; // make sure size is multiple of 4 + for (size_t i=0; i()<256; i+=4) { + tcp[ i ] = (uint8_t) pal[ i ].as(); // index + tcp[i+1] = gamma8((uint8_t) pal[i+1].as()); // R + tcp[i+2] = gamma8((uint8_t) pal[i+2].as()); // G + tcp[i+3] = gamma8((uint8_t) pal[i+3].as()); // B + DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3])); + } + } + customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp)); + } else { + DEBUG_PRINTLN(F("Wrong palette format.")); + } + } + } else { + break; + } + } +} + +//load custom mapping table from JSON file (called from finalizeInit() or deserializeState()) +bool WS2812FX::deserializeMap(uint8_t n) { + // 2D support creates its own ledmap (on the fly) if a ledmap.json exists it will overwrite built one. + + char fileName[32]; + strcpy_P(fileName, PSTR("/ledmap")); + if (n) sprintf(fileName +7, "%d", n); + strcat_P(fileName, PSTR(".json")); + bool isFile = WLED_FS.exists(fileName); + + customMappingSize = 0; // prevent use of mapping if anything goes wrong + currentLedmap = 0; + if (n == 0 || isFile) interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update (to inform UI) + + if (!isFile && n==0 && isMatrix) { + setUpMatrix(); + return false; + } + + if (!isFile || !requestJSONBufferLock(7)) return false; + + if (!readObjectFromFile(fileName, nullptr, pDoc)) { + DEBUG_PRINT(F("ERROR Invalid ledmap in ")); DEBUG_PRINTLN(fileName); + releaseJSONBufferLock(); + return false; // if file does not load properly then exit + } + + JsonObject root = pDoc->as(); + // 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())) { + Segment::maxWidth = min(max(root[F("width")].as(), 1), 128); + Segment::maxHeight = min(max(root[F("height")].as(), 1), 128); + } + + if (customMappingTable) delete[] customMappingTable; + customMappingTable = new uint16_t[getLengthTotal()]; + + if (customMappingTable) { + DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName); + JsonArray map = root[F("map")]; + if (!map.isNull() && map.size()) { // not an empty map + customMappingSize = min((unsigned)map.size(), (unsigned)getLengthTotal()); + for (unsigned i=0; i 0); +} + +uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) { + // convert logical address to physical + if (index < customMappingSize + && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index]; + + return index; +} + + +WS2812FX* WS2812FX::instance = nullptr; + +const char JSON_mode_names[] PROGMEM = R"=====(["FX names moved"])====="; +const char JSON_palette_names[] PROGMEM = R"=====([ +"Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", +"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", +"Pastel","Sunset 2","Beach","Vintage","Departure","Landscape","Beech","Sherbet","Hult","Hult 64", +"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", +"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", +"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", +"Candy2" +])====="; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 3304105bc..b1f5f13c8 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -37,7 +37,7 @@ struct BusConfig { uint8_t milliAmpsPerLed; uint16_t milliAmpsMax; - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=55, uint16_t maMax=ABL_MILLIAMPS_DEFAULT) + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT) : count(len) , start(pstart) , colorOrder(pcolorOrder) @@ -129,7 +129,7 @@ class Bus { virtual uint32_t getPixelColor(uint16_t pix) { return 0; } virtual void setBrightness(uint8_t b) { _bri = b; }; virtual uint8_t getPins(uint8_t* pinArray) { return 0; } - virtual uint16_t getLength() { return _len; } + virtual uint16_t getLength() { return isOk() ? _len : 0; } virtual void setColorOrder(uint8_t co) {} virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } virtual uint8_t skippedLeds() { return 0; } diff --git a/wled00/const.h b/wled00/const.h index a45e0133f..0ff70e47d 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -46,36 +46,58 @@ #ifndef WLED_MAX_BUSSES #ifdef ESP8266 - #define WLED_MAX_BUSSES 3 + #define WLED_MAX_DIGITAL_CHANNELS 3 + #define WLED_MAX_ANALOG_CHANNELS 5 + #define WLED_MAX_BUSSES 4 // will allow 3 digital & 1 analog RGB #define WLED_MIN_VIRTUAL_BUSSES 2 #else #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM - #define WLED_MAX_BUSSES 3 // will allow 2 digital & 1 analog (or the other way around) + #define WLED_MAX_BUSSES 4 // will allow 2 digital & 2 analog RGB + #define WLED_MAX_DIGITAL_CHANNELS 2 + #define WLED_MAX_ANALOG_CHANNELS 6 #define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB // the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though) - #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog + #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog RGB + #define WLED_MAX_DIGITAL_CHANNELS 5 + #define WLED_MAX_ANALOG_CHANNELS 8 #define WLED_MIN_VIRTUAL_BUSSES 3 #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB does not support them ATM - #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog + #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog RGB + #define WLED_MAX_DIGITAL_CHANNELS 4 + #define WLED_MAX_ANALOG_CHANNELS 8 #define WLED_MIN_VIRTUAL_BUSSES 4 #else // the last digital bus (I2S0) will prevent Audioreactive usermod from functioning - #define WLED_MAX_BUSSES 17 - #define WLED_MIN_VIRTUAL_BUSSES 0 + #define WLED_MAX_BUSSES 20 // will allow 17 digital & 3 analog RGB + #define WLED_MAX_DIGITAL_CHANNELS 17 + #define WLED_MAX_ANALOG_CHANNELS 10 + #define WLED_MIN_VIRTUAL_BUSSES 4 #endif #endif #else #ifdef ESP8266 - #if WLED_MAX_BUSES > 5 + #if WLED_MAX_BUSSES > 5 #error Maximum number of buses is 5. #endif + #ifndef WLED_MAX_ANALOG_CHANNELS + #error You must also define WLED_MAX_ANALOG_CHANNELS. + #endif + #ifndef WLED_MAX_DIGITAL_CHANNELS + #error You must also define WLED_MAX_DIGITAL_CHANNELS. + #endif #define WLED_MIN_VIRTUAL_BUSSES (5-WLED_MAX_BUSSES) #else - #if WLED_MAX_BUSES > 17 - #error Maximum number of buses is 17. + #if WLED_MAX_BUSSES > 20 + #error Maximum number of buses is 20. #endif - #define WLED_MIN_VIRTUAL_BUSSES (17-WLED_MAX_BUSSES) + #ifndef WLED_MAX_ANALOG_CHANNELS + #error You must also define WLED_MAX_ANALOG_CHANNELS. + #endif + #ifndef WLED_MAX_DIGITAL_CHANNELS + #error You must also define WLED_MAX_DIGITAL_CHANNELS. + #endif + #define WLED_MIN_VIRTUAL_BUSSES (20-WLED_MAX_BUSSES) #endif #endif @@ -480,6 +502,16 @@ #endif #endif +#ifndef LED_MILLIAMPS_DEFAULT + #define LED_MILLIAMPS_DEFAULT 55 // common WS2812B +#else + #if LED_MILLIAMPS_DEFAULT < 1 || LED_MILLIAMPS_DEFAULT > 100 + #warning "Unusual LED mA current, overriding with default value." + #undef LED_MILLIAMPS_DEFAULT + #define LED_MILLIAMPS_DEFAULT 55 + #endif +#endif + // PWM settings #ifndef WLED_PWM_FREQ #ifdef ESP8266 diff --git a/wled00/data/index.js b/wled00/data/index.js index aaf4ef14a..58cf7b466 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2794,8 +2794,9 @@ function getPalettesData(page, callback) return res.json(); }) .then(json => { + retry = false; palettesData = Object.assign({}, palettesData, json.p); - if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 50); + if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 75); else callback(); }) .catch((error)=>{ @@ -2835,7 +2836,8 @@ function search(field, listId = null) { if (gId("filters").querySelectorAll("input[type=checkbox]:checked").length) return; // filter list items but leave (Default & Solid) always visible - gId(listId).querySelectorAll('.lstI').forEach((listItem,i)=>{ + const listItems = gId("fxlist").querySelectorAll('.lstI'); + listItems.forEach((listItem,i)=>{ if (listId!=='pcont' && i===0) return; const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase(); const searchIndex = listItemName.indexOf(field.value.toUpperCase()); @@ -3058,7 +3060,7 @@ function size() function togglePcMode(fromB = false) { - let ap = (fromB && !lastinfo) || (lastinfo && lastinfo.wifi && lastinfo.witi.ap); + let ap = (fromB && !lastinfo) || (lastinfo && lastinfo.wifi && lastinfo.wifi.ap); if (fromB) { pcModeA = !pcModeA; localStorage.setItem('pcm', pcModeA); diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 063acf2db..ac40afa0b 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -5,7 +5,7 @@ LED Settings