Compare commits

...

189 Commits

Author SHA1 Message Date
cschwinne
0c9bcb2445 Update readme 2019-12-13 01:31:48 +01:00
cschwinne
c98c54bd6b Release of WLED v0.9.0-b1
Timebase reset when turned off
Added Aurora paletta
2019-12-13 01:23:07 +01:00
Aircoookie
f1810a9784 Merge pull request #449 from ohminy/patch-1
Create wled06_usermod.ino
2019-12-12 18:10:27 +01:00
ohminy
9526051766 Create wled06_usermod.ino
Using rotary encoder, control effect or brightness
2019-12-12 23:01:13 +09:00
Aircoookie
bd435b175b Merge pull request #445 from spitsw/mstr
Closes Aircoookie/WLED#444
2019-12-12 00:19:44 +01:00
Warren Spits
42ab734256 Closes Aircoookie/WLED#444 2019-12-11 22:08:59 +11:00
cschwinne
77d89e7df3 Fix iOS scrolling
Other small adjustments
Allow for passwords with * as 1st char
2019-12-11 00:59:15 +01:00
cschwinne
6122a8371a Added Glitter and Candle effects 2019-12-06 01:44:45 +01:00
cschwinne
4ffeb05120 Fix #418 and #420 2019-12-05 11:07:54 +01:00
cschwinne
310f55abb6 Merge branch 'master' of https://github.com/Aircoookie/WLED 2019-12-05 01:58:12 +01:00
cschwinne
d6c0642a02 Add new UI! 2019-12-05 01:58:03 +01:00
Aircoookie
541556874f Merge pull request #413 from dkneisz/master
Added Arduino Core 2.6.1 and 2.6.2 and made 2.6.2 default
2019-12-05 01:56:06 +01:00
Dave
477d7080b8 Added Arduino Core 2.6.1 and 2.6.2 and made 2.6.2 as default
2.4.2 was default and caused a boot loops on NodeMCU for me. Even erasing and re-flashing didn't solve the problem. With core 2.6.2 it seems to be fine.
2019-12-04 19:12:16 +01:00
cschwinne
173c752d62 Added spots and two dots effects 2019-12-04 12:15:12 +01:00
cschwinne
3b70488828 NTP server settable
Added segment commands to HTTP api
Removed HA autodiscovery
2019-12-04 02:01:47 +01:00
cschwinne
334783f89a Preset 16 working 2019-12-03 14:15:12 +01:00
cschwinne
89a54e31f1 Cleanup and segment improvements 2019-12-02 12:41:35 +01:00
cschwinne
354d18f78e Main segment changes 2019-12-01 01:42:52 +01:00
cschwinne
0e8806eb2b Integrated liveview 2019-11-30 19:17:25 +01:00
cschwinne
731550acb3 Fixed reverse 2019-11-30 11:46:31 +01:00
Aircoookie
be4019b4d3 Merge pull request #400 from ironosity/disable_n_leds_for_all_fx
Adding new setting to disable fixed number of LED
2019-11-30 11:20:53 +01:00
cschwinne
70ffcd9adf Merge branch 'master' of https://github.com/Aircoookie/WLED 2019-11-30 11:17:43 +01:00
cschwinne
bbe511dd15 Attempting to fix disconnect on Adalight (ESP32, #194) 2019-11-30 11:17:37 +01:00
Aircoookie
82d5ac91d7 Merge pull request #402 from 400killer/patch-3
Create wled00.txt
2019-11-30 00:33:18 +01:00
Aircoookie
54de0eab6a Merge pull request #401 from 400killer/patch-2
wled06_usermod.ino
2019-11-30 00:32:46 +01:00
400killer
4d5bb274d1 Create wled00.txt
Definitions needed for reading the Dallas temp sensor used on the QuinLED boards.
2019-11-29 11:58:40 -08:00
400killer
131fae57e5 wled06_usermod.ino
This section is used to read the temperature from the sensor and prints it using the MQTT service started by Aircoookie.
2019-11-29 11:55:26 -08:00
emerrill
37da53c20e fixing some spacing 2019-11-29 12:25:30 -07:00
Aircoookie
6ad57a15cf Merge pull request #399 from 400killer/patch-1
Create readme.txt
2019-11-29 19:02:02 +01:00
emerrill
4729bce16c Merge branch 'master' of github.com:Aircoookie/WLED into disable_n_leds_for_all_fx
 Conflicts:
	wled00/data/settings_leds.htm
2019-11-29 10:56:59 -07:00
emerrill
0b5ac7a139 Adding ability to turn off specified number of LEDs. Added to LED settings.
Also added FX for Tri Color Static, defaults tri to white.
2019-11-29 10:53:01 -07:00
400killer
677e23ad14 Create readme.txt 2019-11-29 08:52:51 -08:00
Aircoookie
6d838e3043 Merge pull request #397 from mrVanboy/ib/usermod-oled
usermods: Add SSD1306 display with u8g2
2019-11-28 22:53:56 +01:00
Ivan Boyarkin
f322abceb8 usermods: Add SSD1306 display with u8g2
This commit adds example of usermod file for displaying basic
infromation SSD1306 I2C OLED displya utilizing u8g2 library.

Related to:
https://github.com/Aircoookie/WLED/issues/389
2019-11-28 22:43:46 +01:00
cschwinne
e754d21598 Raise max universes to 9 2019-11-28 20:13:52 +01:00
Aircoookie
0fdd861ef1 Merge pull request #391 from badbadc0ffee/adalight
rewrite adalight parser
2019-11-28 19:50:40 +01:00
cschwinne
2e5f6a3507 Improved adalight show() handling 2019-11-28 19:25:04 +01:00
Aircoookie
4e57cab0fa Merge pull request #395 from spitsw/gamma
Converted UTF-16 files with UTF-8 and CR/LF to LF.
2019-11-28 09:32:22 +01:00
Warren Spits
9930c8f94d Converted UTF-16 files with UTF-8 and CR/LF to LF. 2019-11-28 08:28:13 +11:00
Florian Moesch
f8e262b87e rewrite adalight parser 2019-11-26 21:47:55 +01:00
cschwinne
896bdaf124 Create usermods folder (#389) 2019-11-26 21:21:54 +01:00
cschwinne
2e4f2639a3 Fix #388 2019-11-26 20:41:15 +01:00
Aircoookie
ce89a92d0d Merge pull request #368 from stockklauser/master
Fix Visual Studio Project Structure and add 3 new Effects
2019-11-25 01:34:34 +01:00
cschwinne
1d9d1f6bbd Simplified police code 2019-11-25 01:20:03 +01:00
cschwinne
767b57fc01 Add "psave" field to state JSON 2019-11-25 00:20:00 +01:00
thomas.stockklauser
9e00177d76 - Fix the Visual Studio Project Structure with the e131 lib change to async
- Add 3 New Effects: Police, Police All, Multi Dynamic
2019-11-22 19:19:48 +01:00
Aircoookie
095429a7df Merge pull request #364 from debsahu/patch-1
fix #361
2019-11-21 15:26:29 +01:00
cschwinne
983efd61fb Only connect with static IP if GW is configured (#362) 2019-11-21 15:20:15 +01:00
Debashish Sahu
e028316308 fix #361 2019-11-21 09:02:12 -05:00
Aircoookie
e1354accb8 Merge pull request #363 from TheZoker/fix-espasyncudp-version
Pin ESPAsyncUDP version to avoid unexpected behavior
2019-11-21 14:47:47 +01:00
Florian Gareis
ea726f928d Pin ESPAsyncUDP version to avoid unexpected behavior 2019-11-21 14:01:45 +01:00
cschwinne
6b419dbfc0 Fix PIO (#92) 2019-11-20 00:47:43 +01:00
Aircoookie
006a9eaf44 Merge pull request #356 from nwestwind/patch-1
Update readme.txt
2019-11-19 12:35:40 +01:00
Noah
76117854c6 Update readme.txt 2019-11-18 20:27:45 -08:00
cschwinne
6eae6db46b Migrate to ESPAsyncE131 2019-11-18 20:43:27 +01:00
cschwinne
3aacb7150d Added live preview json 2019-11-18 12:29:36 +01:00
cschwinne
81298a1034 Fix string overflow 2019-11-13 01:20:14 +01:00
cschwinne
b3d728df91 Add 12V brightness limiter and configurable per-led current (#295) 2019-11-12 19:33:34 +01:00
cschwinne
6989b1730e Added mqtt status topic 2019-11-10 22:13:07 +01:00
cschwinne
1595542d59 Replaced literal URLs in readme 2019-11-10 01:12:38 +01:00
cschwinne
fba9992a10 Updated Espalexa (#274) and readme 2019-11-10 00:54:35 +01:00
Aircoookie
867dce2294 Merge pull request #337 from brentbrooks70/master
Fixed wled00.vcxproj, was broken as of 0.86 WS2812FX filename changes
2019-11-09 22:11:27 +01:00
BrentBrooks70
692554a899 Add files via upload
Updated WS2812FX file names to FX
As of 0.86 this was broken for Visual Micro
2019-11-09 14:28:46 -05:00
cschwinne
f3cc616e07 Fix reverse on segments with start > 0 2019-11-08 16:58:23 +01:00
cschwinne
e7a0874a57 Improved theater effect 2019-11-03 01:18:02 +01:00
cschwinne
1beb9c4bb8 Added WiFi section to JSON info (#288)
Add tt command to JSON API (#291)
2019-10-29 02:21:23 +01:00
cschwinne
6eef3a9037 Add mac address to mDNS announcement (#305) 2019-10-29 01:30:07 +01:00
cschwinne
ddaaae46a6 Amending missing files in previous commit 2019-10-29 01:19:56 +01:00
cschwinne
4e4773a370 Fix JSON API FX change not sending sync (#283)
Rename duplicate effects (#294)
Add India Standard Time
Fix flash on startup
Fix NTP with lwip2
2019-10-29 01:19:04 +01:00
cschwinne
f4a2ffc5d2 Update platformio.ini 2019-10-26 01:01:16 +02:00
cschwinne
ba1117e10e Release v0.8.6 2019-10-26 00:00:44 +02:00
cschwinne
0cd46f932a Fix 2.4.0 2019-10-25 15:32:09 +02:00
cschwinne
937f404583 Fix ESP32 2019-10-25 11:54:47 +02:00
cschwinne
d13d60d752 New WiFi logic 2019-10-25 00:14:58 +02:00
cschwinne
31e4e7c709 HA discovery wdt reset 2019-10-20 17:38:25 +02:00
cschwinne
0d3a8ce31b Update MQTT library 2019-10-20 12:48:29 +02:00
cschwinne
be185b46a7 Reworked WiFi logic
Remaining issues:
MQTT reconnects too often
WiFI AP doesn't work if searching for STA
2019-10-18 23:47:11 +02:00
cschwinne
90fa5b3b93 Removed onlyAP 2019-10-18 14:06:07 +02:00
cschwinne
733996772b WLED_CONNECTED macro 2019-10-18 13:26:39 +02:00
cschwinne
d4c921ea2e Timebase sync 2019-10-18 12:19:52 +02:00
cschwinne
2852061699 Refactor WS812FX file names 2019-10-07 23:38:21 +02:00
cschwinne
d8859b9f0a Improved running effects 2019-10-07 23:22:56 +02:00
cschwinne
ae1bc96006 More effects use FRAMETIME 2019-10-07 20:17:52 +02:00
cschwinne
f30ffb4413 Improved rainbow effects 2019-10-05 01:56:55 +02:00
cschwinne
273c6467c8 Fix travis (ESP01 too little flash) 2019-10-04 01:38:42 +02:00
cschwinne
846a1d007c Improved fade modes 2019-10-04 01:21:18 +02:00
cschwinne
1dccc8dc78 Improved Color Wipe 2019-10-03 20:57:22 +02:00
cschwinne
e0d67bd057 Improved effects 2019-10-03 16:33:37 +02:00
cschwinne
4b4b93ac04 Added Halloween Eyes effect
Added Twinklecat
2019-10-02 01:17:26 +02:00
Aircoookie
4390aee1e0 Merge pull request #234 from pille/master
fix verison number of current release
2019-09-29 11:20:30 +02:00
pille
4cddb16788 fix verison number of current release 2019-09-28 13:43:57 +02:00
cschwinne
e1179fd8c8 Delete accidentallly included bin 2019-09-26 14:06:50 +02:00
cschwinne
cb77285277 Support APA102 on ESP32 2019-09-26 14:02:58 +02:00
cschwinne
6c9d161950 Fixed transitions and gamma 2019-09-19 21:15:20 +02:00
Aircoookie
40aaac5868 Merge pull request #218 from Aircoookie/captiveportal
Release v0.8.5
2019-09-12 15:30:34 +02:00
cschwinne
e16b69594e Fix PIO 2019-09-12 13:08:07 +02:00
cschwinne
4837bf007a Update welcome page 2019-09-12 12:41:51 +02:00
cschwinne
705fd4dafd Release v0.8.5 2019-09-12 12:40:06 +02:00
cschwinne
a3e28d3c66 First version of captive portal 2019-09-05 22:45:59 +02:00
cschwinne
4a6755c28a Added C9 and Sakura palettes 2019-08-31 01:41:25 +02:00
cschwinne
188fe5dc52 Added TwinkleFOX effect
Added Orangery palette
2019-08-30 15:39:34 +02:00
cschwinne
44a8ae457d Fixed JSON API POST requests
Speed set COOLING for Fire2012 (#208)
2019-08-25 23:52:40 +02:00
cschwinne
92eafcfe1a Fixed crash on opening settings in core 2.5.2 (#168) 2019-08-21 01:18:25 +02:00
Aircoookie
b12b031fdd Merge pull request #202 from timothybrown/mqttauth
MQTT Authentication Support
2019-08-19 23:20:35 +02:00
cschwinne
492ec489a1 Small changes to MQTT auth
Changed mqttPort to uint16 type
Password no longer transmitted to settings page
Chnaged topics and identifiers to last 6 bytes of mac format
Added security warning
2019-08-18 18:14:17 +02:00
Timothy Brown
c57124e876 Added MQTT port field, bumped user, pass and CID to 40 characters 2019-08-17 21:34:47 -04:00
Timothy Brown
95b33c9c34 Tidied up code 2019-08-17 07:26:40 -04:00
Timothy Brown
c6d8b63e54 Added MQTT authentication support 2019-08-17 06:27:06 -04:00
Aircoookie
f0f02c4ea6 Merge pull request #193 from stockklauser/0.8.4_master_extend_VS
Fix Compile Issues with Visual Studio 2017 / Visual Assist Arduino  and Add Visual Studio Project Files
2019-07-24 23:22:01 +02:00
thomas.stockklauser
eb2cb6810a Modify Structure to fix path issues 2019-07-23 18:51:26 +02:00
thomas.stockklauser
b3c090e9ed Add Visual Studio Support and fix a Compile Issue with Visual Assist / Studio 2017 2019-07-23 18:04:26 +02:00
thomas.stockklauser
13366fc9f8 Add Visual Studio Project Structure 2019-07-23 17:59:55 +02:00
thomas.stockklauser
929af7830a Add Visual Studio Project Structure
Fix a compile Issue in wled19_json.ino with Visual Studio / Visual Assist
2019-07-23 17:35:40 +02:00
cschwinne
13062cf0e4 Merge branch 'master' of https://github.com/Aircoookie/WLED.git 2019-06-21 23:14:36 +02:00
cschwinne
b897a8a35f Updated to ArduinoJson v6
Fixed JSON crash on core v2.5.2
2019-06-21 23:12:58 +02:00
cschwinne
117dc5288d Added basic segment support
Updated Espalexa
2019-06-20 14:40:12 +02:00
Aircoookie
4b5a3bd3d5 Revert LEDPIN to 2 2019-05-23 00:33:15 +02:00
cschwinne
b224a67ea7 Refactored WS2812FX variable names 2019-05-22 00:23:09 +02:00
cschwinne
793f919d59 Added MQTT auto reconnect 2019-05-21 18:50:56 +02:00
Aircoookie
315987b2f6 Merge pull request #160 from T-Arens/master
Added support for APA102 LEDs.
2019-05-04 15:54:33 +02:00
Thomas Arens
9b7db548a2 Only disable the button pin if it conflicts with one of the APA102 pins. 2019-05-01 16:52:22 +02:00
Thomas Arens
126b70f781 Added support for APA102 LEDs. Uncomment "#define USE_APA102" in NbpWrapper.h. Connect clock to GPIO 0 and data to GPIO 2. 2019-05-01 03:09:08 +02:00
Aircoookie
0bbff627e2 Merge pull request #152 from YeonV/patch-1
Fixed MQTT color response
2019-04-15 22:37:41 +02:00
Yeon Vinzenz Varapragasam
961d23e2a1 Fixed MQTT color response
Leading zeros are not trimmed on /c topic anymore :)
Before blue: #FF
After blue: #0000FF
2019-04-15 20:43:32 +02:00
cschwinne
b03ff9a48a Updated Espalexa to 2.4.2
Added UDP realtime 255 as keep state until changed
Added "true" and "false" MQTT payloads
2019-04-14 19:31:25 +02:00
cschwinne
3ffb40fafa Fixed HA autodiscovery and MQTT ON 2019-03-27 21:31:59 +01:00
cschwinne
1a3b4ac2ac Fixed meteor FX crashing 2019-03-27 21:06:07 +01:00
cschwinne
794e17442f Release of v0.8.4
Default to LwiP 2 in PIO
Fixed 12hr format time
2019-03-25 23:27:35 +01:00
cschwinne
238d7119e0 Completed HA autodiscovery
Modified platformio.ini
2019-03-24 18:28:36 +01:00
cschwinne
8a929a8348 Added new Homeassistent broadcast logic 2019-03-24 00:49:26 +01:00
cschwinne
cf77153647 Merge branch 'master' of https://github.com/Aircoookie/WLED.git 2019-03-19 12:21:56 +01:00
cschwinne
a2da0b0641 Fixed HTTP API XML response 2019-03-19 12:19:48 +01:00
Aircoookie
73faa13811 Merge pull request #134
Added Homeassistant autodiscovery
2019-03-18 19:56:53 +01:00
cschwinne
1a71872c7b Added flag to enable Homeassistant autodiscovery 2019-03-18 19:54:06 +01:00
Debashish Sahu
62fe7135bd PIO ESP01 fix
- fix core for ESP01, newer cores are too big
2019-03-18 13:30:47 -04:00
Debashish Sahu
078940d29f PIO & TravisCI fix
- fix errors while compiling for PIO and TravisCI
2019-03-18 13:13:04 -04:00
Debashish Sahu
2fafe42c18 HA Light Auto Discovery
- Send HA MQTT Discovery message ~2.4kB based on input by @YeonV from here: https://github.com/Aircoookie/WLED/issues/131
2019-03-18 12:23:39 -04:00
cschwinne
c8a7537157 Added support for SPIFFS
Fixed ESP32
2019-03-16 02:09:37 +01:00
cschwinne
d4bf1cb23d Added button double press macro option
Added toggle (relay) pin
2019-03-13 11:13:03 +01:00
cschwinne
46e4350013 Improved heap usage by 2k 2019-03-11 19:30:49 +01:00
cschwinne
202eb0d854 Fixed /json with ESP core <2.5.0 2019-03-11 17:57:06 +01:00
cschwinne
898702346e Fixed JSON API on bug
Fixed RN=1 not having an effect if default off
2019-03-11 00:20:17 +01:00
cschwinne
b72e6f16ca Small memory improvements 2019-03-09 21:41:23 +01:00
cschwinne
b9c27ed324 Added RD HTTP api call for realtime udp 2019-03-09 14:48:13 +01:00
cschwinne
0166dfe16e Fixed colorwheel 2019-03-07 23:22:52 +01:00
cschwinne
7274541722 Fixed platformio compilation
Added more debug info in serial on boot
2019-03-07 16:36:26 +01:00
cschwinne
709ff7a701 Finished JSON API
Added RV http api call
Fixed CY,PA,PC,PX api calls
Fixed CORS
2019-03-06 21:31:12 +01:00
cschwinne
66c224c954 Added JSON state API 2019-03-06 01:20:38 +01:00
cschwinne
3f9b37aa7f Added /json/state 2019-03-05 10:59:15 +01:00
cschwinne
0377958d8f Updated hue sync to use ArduinoJSON
Fixed brightness when ABL deactivated
2019-03-03 23:27:52 +01:00
cschwinne
cc1cfd70b8 Added ArduinoJSON 2019-03-03 18:05:56 +01:00
cschwinne
bc125ad76c Updated Espalexa to v2.4.0 2019-03-01 17:10:42 +01:00
cschwinne
62a2246448 Included effect and palette lists in LED settings 2019-02-25 22:23:26 +01:00
cschwinne
587cf751d8 Fixed preset loading 2019-02-25 19:14:13 +01:00
cschwinne
600181ed07 Updated platformio.ini 2019-02-22 22:53:39 +01:00
cschwinne
f0e525d2e2 Added relative API calls 2019-02-22 22:53:33 +01:00
cschwinne
f86cdd8cde Added /json/info page 2019-02-21 16:32:15 +01:00
cschwinne
4a4c537a0d Reverted to default LEDPIN 2019-02-21 00:21:35 +01:00
cschwinne
1caaf04dfa Various performance and reliability improvements 2019-02-20 23:44:34 +01:00
cschwinne
b422a80249 Fixed button-caused asyncserver unresponsiveness
Fixed RGBW power calculation
2019-02-20 15:18:04 +01:00
cschwinne
ba19e20833 Added Macro notification option
Removed realtime UI lock
2019-02-19 12:57:50 +01:00
cschwinne
c34ddb2bc3 Initial async hue client 2019-02-18 22:34:21 +01:00
cschwinne
aa315f8472 Switched from PubSubClient to AsyncMqttClient 2019-02-17 19:21:09 +01:00
cschwinne
2af6af2bf0 Added HTTP OTA update via ESPAsyncWebServer 2019-02-17 17:11:10 +01:00
cschwinne
5694ff7c97 Migrated to AsyncWebServer 2019-02-16 00:21:22 +01:00
cschwinne
76f1c689c1 Interim Async Update 2019-02-14 17:25:41 +01:00
cschwinne
a371239172 Fixed mobile UI effect list not loading 2019-02-12 14:50:19 +01:00
cschwinne
4fd904fbcc Merge branch 'master' of https://github.com/Aircoookie/WLED.git 2019-02-12 11:05:00 +01:00
cschwinne
b73a257389 Fixed broadcast IP compilation issue 2019-02-12 11:03:54 +01:00
Aircoookie
9e70d6b3e1 Merge pull request #105 from Aircoookie/development
Release of v0.8.3
2019-02-11 23:53:53 +01:00
cschwinne
9caca37ab1 Release of v0.8.3
Removed initLedsLast
Improved Fireworks
2019-02-11 23:49:04 +01:00
cschwinne
6e76fc0aa7 Added JSON FX + palette lists 2019-02-10 23:05:06 +01:00
cschwinne
6171883758 Split up WS2812FX.cpp in FX and helper files 2019-02-09 16:37:20 +01:00
cschwinne
942b68c948 Added shields.io to readme 2019-02-09 15:41:55 +01:00
cschwinne
9ca7ffa5a3 Refactored white to col[3]
Added Saw effect
2019-02-05 21:53:39 +01:00
cschwinne
d1ce23c5ac Unique mDNS name
Various optimizations
2019-02-05 19:40:24 +01:00
cschwinne
b7b6d0a6bc Improved ripple effect 2019-02-02 23:59:48 +01:00
cschwinne
10c51eea2c Added Ripple and revamped twinkle effects 2019-01-31 23:42:48 +01:00
cschwinne
48d20c02a1 Added timed macro weekday support 2019-01-31 00:09:44 +01:00
cschwinne
c5cc0b3f2b Updated Mobile UI
Fixed Smooth Meteor stuck pixels
Added CORS response
Added secondary color to http API response
2019-01-18 01:20:36 +01:00
cschwinne
6ebef8846c Merge branch 'development' of https://github.com/Aircoookie/WLED.git into development 2019-01-09 23:03:34 +01:00
cschwinne
5d1993935e Added Alexa Color support 2019-01-09 22:55:18 +01:00
Aircoookie
caab8943cb Merge pull request #90 from cboltz/cboltz-timezone-deps
Use time/time.h from local dependencies
2018-12-20 21:17:42 +01:00
Christian Boltz
f5c05b24fb Use time/time.h from local dependencies
This fixes a compile issue, which can be
a) file not found or
b) (after installing the Time library) redefinition of a variable
2018-12-17 22:07:43 +01:00
Aircoookie
940a0d006d Merge pull request #89 from definitio/master
Fix compiling on a case sensitive filesystems
2018-12-17 15:41:24 +01:00
definitio
8fe67a04d8 Fix compiling on a case sensitive file systems 2018-12-17 18:08:59 +04:00
cschwinne
bec745d095 Improved colortwinkles on longer strips
Added offMode
2018-12-16 20:38:00 +01:00
Aircoookie
223fd35138 Merge pull request #84 from Aircoookie/development
Updated platformio.ini for v0.8.2
2018-12-06 16:32:52 +01:00
cschwinne
d3fc0309c0 Updated platformio.ini for v0.8.2 2018-12-06 16:31:52 +01:00
123 changed files with 19951 additions and 12572 deletions

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@
.piolibdeps
.vscode
!.vscode/extensions.json
/wled00/Release
/wled00/extLibs

BIN
.vs/wled00/v15/.suo Normal file

Binary file not shown.

View File

@@ -5,8 +5,9 @@
src_dir = ./wled00
data_dir = ./wled00/data
lib_extra_dirs = ./wled00/src
; Please uncomment one of the 5 lines below to select your board
; env_default = nodemcuv2
env_default = esp01
; env_default = esp01
; env_default = esp01_1m
; env_default = d1_mini
; env_default = esp32dev
@@ -16,46 +17,87 @@ env_default = esp01
framework = arduino
monitor_speed = 115200
board_build.flash_mode = dout
upload_speed = 921600
upload_speed = 115200
upload_speed_fast = 921600
build_flags =
; -D VERSION=0.8.2-dev
-w ; supresses all C/C++ warnings
; -D VERSION=0.8.5
; -D DEBUG
# TODO replace libs in /lib with managed libs in here if possible.
# If they are not changed it's just a metter of setting the correfct version and change the import statement
# If they are not changed it's just a matter of setting the correct version and change the import statement
lib_deps_external =
#Blynk@0.5.4
#E131@1.0.0
#webserver
FastLED@3.2.1
NeoPixelBus@2.3.4
#PubSubClient@2.7
#Blynk@0.5.4(changed)
#E131@1.0.0(changed)
FastLED@3.3.2
NeoPixelBus@2.5.1
ESPAsyncTCP@1.2.0
ESPAsyncUDP@697c75a025
AsyncTCP@1.0.3
Esp Async WebServer@1.2.0
#ArduinoJson@5.13.5
IRremoteESP8266@2.5.5
#Time@1.5
#Timezone@1.2.1
#WS2812FX@1.1.2
[common:esp8266]
platform = espressif8266@1.8.0
# ------------------------------------------------------------------------------
# PLATFORM:
# !! DO NOT confuse platformio's ESP8266 development platform with Arduino core for ESP8266
# We use Arduino Core 2.5.0 (platformIO 2.0.4) as default
#
# arduino core 2.3.0 = platformIO 1.5.0
# arduino core 2.4.0 = platformIO 1.6.0
# arduino core 2.4.1 = platformIO 1.7.3
# arduino core 2.4.2 = platformIO 1.8.0
# arduino core 2.5.0 = platformIO 2.0.4
# arduino core stage = platformIO feature#stage
# ------------------------------------------------------------------------------
arduino_core_2_3_0 = espressif8266@1.5.0
arduino_core_2_4_0 = espressif8266@1.6.0
arduino_core_2_4_1 = espressif8266@1.7.3
arduino_core_2_4_2 = espressif8266@1.8.0
arduino_core_2_5_0 = espressif8266@2.0.4
arduino_core_2_5_2 = espressif8266@2.2.3
arduino_core_2_6_1 = espressif8266@2.3.0
arduino_core_2_6_2 = espressif8266@2.3.1
arduino_core_stage = https://github.com/platformio/platform-espressif8266.git#feature/stage
platform = ${common:esp8266.arduino_core_2_6_2}
build_flags =
-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH
-Wl,-Teagle.flash.4m1m.ld ;;;; Required for core > v2.5.0 or staging version 4MB Flash 3MB SPIFFs
[common:esp8266_512k]
platform = espressif8266@1.7.0
[common:esp8266_1M]
platform = espressif8266@1.8.0
build_flags =
-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
-Wl,-Teagle.flash.1m0.ld ;;;; Compile with no SPIFFS to leave space for OTA
; -D WLED_DISABLE_MOBILE_UI
; -D WLED_DISABLE_OTA
; -D WLED_DISABLE_ALEXA
-D WLED_DISABLE_BLYNK
-D WLED_DISABLE_CRONIXIE
; -D WLED_DISABLE_HUESYNC
-D WLED_DISABLE_INFRARED
[common:esp8266_512k]
platform = espressif8266@1.8.0
build_flags =
-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
-Wl,-Teagle.flash.512k0.ld ;;;; Compile with no SPIFFS
; -D WLED_DISABLE_MOBILE_UI
-D WLED_DISABLE_OTA
-D WLED_DISABLE_ALEXA
; -D WLED_DISABLE_BLYNK
; -D WLED_DISABLE_CRONIXIE
-D WLED_DISABLE_HUESYNC
; -D WLED_DISABLE_ALEXA
-D WLED_DISABLE_BLYNK
-D WLED_DISABLE_CRONIXIE
; -D WLED_DISABLE_HUESYNC
-D WLED_DISABLE_INFRARED
[common:esp32]
platform = espressif32@1.5.0
platform = espressif32@1.11.1
build_flags =
-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH
-D ARDUINO_ARCH_ESP32
-D WORKAROUND_ESP32_BITBANG
-D WLED_DISABLE_INFRARED
# see: http://docs.platformio.org/en/latest/platforms/espressif8266.html
[env:nodemcuv2]
@@ -84,13 +126,15 @@ lib_deps =
[env:esp01_1m]
board = esp01_1m
platform = ${common:esp8266.platform}
platform = ${common:esp8266_1M.platform}
monitor_speed = ${common.monitor_speed}
upload_speed = ${common.upload_speed}
framework = ${common.framework}
build_flags =
${common.build_flags}
${common:esp8266.build_flags}
${common:esp8266_1M.build_flags}
# disable IR because there is no pin for it
-D WLED_DISABLE_INFRARED
lib_deps =
${common.lib_deps_external}
@@ -111,11 +155,13 @@ lib_deps =
board = esp32dev
platform = ${common:esp32.platform}
monitor_speed = ${common.monitor_speed}
upload_speed = ${common.upload_speed}
upload_speed = ${common.upload_speed_fast}
framework = ${common.framework}
build_flags =
${common.build_flags}
${common:esp32.build_flags}
lib_deps =
${common.lib_deps_external}
lib_ignore =
IRremoteESP8266
ESPAsyncUDP

View File

@@ -1,41 +1,54 @@
![WLED logo](https://raw.githubusercontent.com/Aircoookie/WLED/master/wled_logo.png)
![WLED logo](https://raw.githubusercontent.com/Aircoookie/WLED/master/wled_logo.png)
## Welcome to my project WLED! (v0.8.2)
[![](https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square)](https://github.com/Aircoookie/WLED/releases)
[![](https://img.shields.io/discord/473448917040758787.svg?colorB=blue&label=discord&style=flat-square)](https://discord.gg/KuqP7NE)
[![](https://img.shields.io/badge/quick_start-wiki-blue.svg?style=flat-square)](https://github.com/Aircoookie/WLED/wiki)
[![](https://img.shields.io/badge/app-wled-blue.svg?style=flat-square)](https://github.com/Aircoookie/WLED-App)
A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B) LEDs!
## Welcome to my project WLED!
A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812, APA102) LEDs!
### Features:
- WS2812FX library integrated for 75 special effects
- FastLED noise effects and palettes
- Customizable Mobile and desktop UI with color and effect controls
- Settings page - configuration over network
- Access Point and station mode - automatic failsafe AP
- Support for RGBW strips
- 25 user presets to save and load colors/effects easily, supports cycling through them.
- Macro functions to automatically execute API calls
- Nightlight function (gradually dims down)
- Full OTA software updatability (HTTP + ArduinoOTA), password protectable
- Configurable analog clock + support for the Cronixie kit by Diamex
- Configurable Auto Brightness limit for safer operation
- WS2812FX library integrated for almost 90 special effects
- FastLED noise effects and palettes
- Modern UI with color, effect and segment controls
- Segments to set different effects and colors to parts of the LEDs
- Settings page - configuration over network
- Access Point and station mode - automatic failsafe AP
- Support for RGBW strips
- 16 user presets to save and load colors/effects easily, supports cycling through them.
- Macro functions to automatically execute API calls
- Nightlight function (gradually dims down)
- Full OTA software updatability (HTTP + ArduinoOTA), password protectable
- Configurable analog clock + support for the Cronixie kit by Diamex
- Configurable Auto Brightness limit for safer operation
### Supported light control interfaces:
- HTTP request API
- Blynk IoT
- MQTT
- E1.31
- Hyperion
- UDP realtime
- Alexa smart device (including dimming)
- Sync to Philips hue lights
- Adalight (PC ambilight via serial)
- Sync color of multiple WLED devices (UDP notifier)
- Infrared remotes (24-key RGB, receiver required)
- Simple timers/schedules (time from NTP, timezones/DST supported)
- WLED app for Android and iOS
- JSON and HTTP request APIs
- MQTT
- Blynk IoT
- E1.31
- Hyperion
- UDP realtime
- Alexa voice control (including dimming and color)
- Sync to Philips hue lights
- Adalight (PC ambilight via serial)
- Sync color of multiple WLED devices (UDP notifier)
- Infrared remotes (24-key RGB, receiver required)
- Simple timers/schedules (time from NTP, timezones/DST supported)
### Quick start guide and documentation:
See the [wiki](https://github.com/Aircoookie/WLED/wiki)!
DrZzs has made some excellent video guides:
[Introduction, hardware and installation](https://www.youtube.com/watch?v=tXvtxwK3jRk)
[Settings, tips and tricks](https://www.youtube.com/watch?v=6eCE2BpLaUQ)
If you'd rather read, here is a very [detailed step-by-step beginner tutorial](https://tynick.com/blog/11-03-2019/getting-started-with-wled-on-esp8266/) by tynick!
### Other
Licensed under the MIT license
@@ -45,10 +58,10 @@ Uses Linearicons by Perxis!
Join the Discord [server](https://discord.gg/KuqP7NE) to discuss everything about WLED!
You can also send me mails to [dev.aircoookie@gmail.com](mailto:dev.aircoookie@gmail.com).
If WLED really brightens up your every day, you can [send me a small gift](https://paypal.me/aircoookie)!
If WLED really brightens up your every day, you can [![](https://img.shields.io/badge/send%20me%20a%20small%20gift-paypal-blue.svg?style=flat-square)](https://paypal.me/aircoookie)
*Disclaimer:*
If you are sensitive to photoeleptic seizures it is not recommended that you use this software.
In case you still want to try, don't use strobe, lighting or noise modes or high effect speed settings.
As per the MIT license, i assume no liability for any damage to you or any other person or equipment.

View File

@@ -0,0 +1,8 @@
These files allow WLED 0.8.6 to report the temp sensor on the Quinled board to MQTT. I use it to report the board temp to Home Assistant via MQTT, so it will send notifications if something happens and the board start to heat up.
This code uses Aircookie's WLED software. It has a premade file for user modifications. I use it to publish the temperature from the dallas temperature sensor on the Quinled board. The entries for the top of the WLED00 file, initializes the required libraries, and variables for the sensor. The .ino file waits for 60 seconds, and checks to see if the MQTT server is connected (thanks Aircoookie). It then poles the sensor, and published it using the MQTT service already running, using the main topic programmed in the WLED UI.
To install:
Add the enties in the WLED00 file to the top of the same file from Aircoookies WLED.
Replace the WLED06_usermod.ino file in Aircoookies WLED folder.

View File

@@ -0,0 +1,8 @@
//Intiating code for QuinLED Dig-Uno temp sensor
//Uncomment Celsius if that is your prefered temperature scale
#include <DallasTemperature.h>
OneWire oneWire(14);
DallasTemperature sensors(&oneWire);
long temptimer = millis();
long lastMeasure = 0;
//#define Celsius

View File

@@ -0,0 +1,41 @@
//starts Dallas Temp service on boot
void userSetup()
{
// Start the DS18B20 sensor
sensors.begin();
}
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
void userConnected()
{
}
void userLoop()
{
temptimer = millis();
// Timer to publishe new temperature every 60 seconds
if (temptimer - lastMeasure > 60000) {
lastMeasure = temptimer;
//Check if MQTT Connected, otherwise it will crash the 8266
if (mqtt != nullptr){
sensors.requestTemperatures();
//Gets prefered temperature scale based on selection in definitions section
#ifdef Celsius
float board_temperature = sensors.getTempCByIndex(0);
#else
float board_temperature = sensors.getTempFByIndex(0);
#endif
//Create character string populated with user defined device topic from the UI, and the read temperature. Then publish to MQTT server.
char subuf[38];
strcpy(subuf, mqttDeviceTopic);
strcat(subuf, "/temperature");
mqtt->publish(subuf, 0, true, String(board_temperature).c_str());
return;}
return;}
return;
}

18
usermods/readme.md Normal file
View File

@@ -0,0 +1,18 @@
### Usermods
This folder serves as a repository for usermods (custom `wled06_usermod.ino` files)!
If you have created an usermod that you believe is useful (for example to support a particular sensor, display, feature...), feel free to contribute by opening a pull request!
In order for other people to be able to have fun with your usermod, please keep these points in mind:
- Create a folder in this folder with a descriptive name (for example `usermod_ds18b20_temp_sensor_mqtt`)
- Include your custom `wled06_usermod.ino` file
- If your usermod requieres changes to other WLED files, please write a `readme.md` outlining the steps one has to take to use the usermod
- Create a pull request!
- If your feature is useful for the majority of WLED users, I will consider adding it to the base code!
While I do my best to not break too much, keep in mind that as WLED is being updated, usermods might break.
I am not actively maintaining any usermod in this directory, that is your responsibility as the creator of the usermod.
Thank you for your help :)

View File

@@ -0,0 +1,45 @@
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
long lastTime = 0;
int delayMs = 10;
const int pinA = D6; //data
const int pinB = D7; //clk
int oldA = LOW;
//gets called once at boot. Do all initialization that doesn't depend on network here
void userSetup() {
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
}
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
void userConnected() {
}
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
void userLoop() {
if (millis()-lastTime > delayMs) {
int A = digitalRead(pinA);
int B = digitalRead(pinB);
if (oldA == LOW && A == HIGH) {
if (oldB == HIGH) {
// bri += 10;
// if (bri > 250) bri = 10;
effectCurrent += 1;
if (effectCurrent >= MODE_COUNT) effectCurrent = 0;
}
else {
// bri -= 10;
// if (bri < 10) bri = 250;
effectCurrent -= 1;
if (effectCurrent < 0) effectCurrent = (MODE_COUNT-1);
}
oldA = A;
//call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification)
// 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa
colorUpdated(6);
lastTime = millis();
}
}

View File

@@ -0,0 +1,35 @@
# SSD1306 128x32 OLED via I2C with u8g2
This usermod allows to connect 128x32 Oled display to WLED controlled and show
the next information:
- Current SSID
- IP address if obtained
* in AP mode and turned off lightning AP password is shown
- Current effect
- Current palette
- On/Off icon (sun/moon)
## Hardware
![Hardware connection](assets/hw_connection.png)
## Requirements
Functionality checked with:
- commit 095429a7df4f9e2b34dd464f7bbfd068df6558eb
- Wemos d1 mini
- PlatformIO
- Generic SSD1306 128x32 I2C OLED display from aliexpress
### Platformio
Add `U8g2@~2.27.2` dependency to `lib_deps_external` under `[common]` section in `platformio.ini`:
```ini
# platformio.ini
...
[common]
...
lib_deps_external =
...
U8g2@~2.27.2
...
```
### Arduino IDE
Install library `U8g2 by oliver` in `Tools | Include Library | Manage libraries` menu.

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -0,0 +1,149 @@
#include <U8x8lib.h> // from https://github.com/olikraus/u8g2/
// If display does not work or looks corrupted check the
// constructor reference:
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
// or check the gallery:
// https://github.com/olikraus/u8g2/wiki/gallery
U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, 5,
4); // Pins are Reset, SCL, SDA
// gets called once at boot. Do all initialization that doesn't depend on
// network here
void userSetup() {
u8x8.begin();
u8x8.setPowerSave(0);
u8x8.setFont(u8x8_font_chroma48medium8_r);
u8x8.drawString(0, 0, "Loading...");
}
// gets called every time WiFi is (re-)connected. Initialize own network
// interfaces here
void userConnected() {}
// needRedraw marks if redraw is required to prevent often redrawing.
bool needRedraw = true;
// Next variables hold the previous known values to determine if redraw is
// required.
String knownSsid = "";
IPAddress knownIp;
uint8_t knownBrightness = 0;
uint8_t knownMode = 0;
uint8_t knownPalette = 0;
long lastUpdate = 0;
// How often we are redrawing screen
#define USER_LOOP_REFRESH_RATE_MS 5000
void userLoop() {
// Check if we time interval for redrawing passes.
if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) {
return;
}
lastUpdate = millis();
// Check if values which are shown on display changed from the last tiem.
if ((apActive == true ? String(apSSID) : WiFi.SSID()) != knownSsid) {
needRedraw = true;
} else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) {
needRedraw = true;
} else if (knownBrightness != bri) {
needRedraw = true;
} else if (knownMode != strip.getMode()) {
needRedraw = true;
} else if (knownPalette != strip.getSegment(0).palette) {
needRedraw = true;
}
if (!needRedraw) {
return;
}
needRedraw = false;
// Update last known values.
knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID();
knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP();
knownBrightness = bri;
knownMode = strip.getMode();
knownPalette = strip.getSegment(0).palette;
u8x8.clear();
u8x8.setFont(u8x8_font_chroma48medium8_r);
// First row with Wifi name
u8x8.setCursor(1, 0);
u8x8.print(ssid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0));
// Print `~` char to indicate that SSID is longer, than owr dicplay
if (ssid.length() > u8x8.getCols())
u8x8.print("~");
// Second row with IP or Psssword
u8x8.setCursor(1, 1);
// Print password in AP mode and if led is OFF.
if (apActive && bri == 0)
u8x8.print(apPass);
else
u8x8.print(ip);
// Third row with mode name
u8x8.setCursor(2, 2);
uint8_t qComma = 0;
bool insideQuotes = false;
uint8_t printedChars = 0;
char singleJsonSymbol;
// Find the mode name in JSON
for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) {
singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i);
switch (singleJsonSymbol) {
case '"':
insideQuotes = !insideQuotes;
break;
case '[':
case ']':
break;
case ',':
qComma++;
default:
if (!insideQuotes || (qComma != knownMode))
break;
u8x8.print(singleJsonSymbol);
printedChars++;
}
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
break;
}
// Fourth row with palette name
u8x8.setCursor(2, 3);
qComma = 0;
insideQuotes = false;
printedChars = 0;
// Looking for palette name in JSON.
for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) {
singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i);
switch (singleJsonSymbol) {
case '"':
insideQuotes = !insideQuotes;
break;
case '[':
case ']':
break;
case ',':
qComma++;
default:
if (!insideQuotes || (qComma != knownPalette))
break;
u8x8.print(singleJsonSymbol);
printedChars++;
}
if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2))
break;
}
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
u8x8.drawGlyph(0, 0, 80); // wifi icon
u8x8.drawGlyph(0, 1, 68); // home icon
u8x8.setFont(u8x8_font_open_iconic_weather_2x2);
u8x8.drawGlyph(0, 2, 66 + (bri > 0 ? 3 : 0)); // sun/moon icon
}

25
wled00.sln Normal file
View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28010.2046
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wled00", "wled00\wled00.vcxproj", "{C5F80730-F44F-4478-BDAE-6634EFC2CA88}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.ActiveCfg = Debug|Win32
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.Build.0 = Debug|Win32
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.ActiveCfg = Release|Win32
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9A679C2B-61D3-400B-B96F-06E604E9CED2}
EndGlobalSection
EndGlobal

BIN
wled00/.vs/wled00/v15/.suo Normal file

Binary file not shown.

2351
wled00/FX.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,7 @@
//pixelmethod now in NpbWrapper.h
/*
WS2812FX.h - Library for WS2812 LED effects.
Harm Aldick - 2016
www.aldick.org
FEATURES
* A lot of blinken modes and counting
* WS2812FX can be used as drop-in replacement for Adafruit NeoPixel Library
NOTES
* Uses the Adafruit NeoPixel library. Get it here:
https://github.com/adafruit/Adafruit_NeoPixel
LICENSE
The MIT License (MIT)
Copyright (c) 2016 Harm Aldick
@@ -28,11 +20,7 @@
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.
CHANGELOG
2016-05-28 Initial beta release
2016-06-03 Code cleanup, minor improvements, new modes
2016-06-04 2 new fx, fixed setColor (now also resets _mode_color)
2017-02-02 added external trigger functionality (e.g. for sound-to-light)
Modified for WLED
*/
@@ -47,19 +35,26 @@
#define DEFAULT_BRIGHTNESS (uint8_t)127
#define DEFAULT_MODE (uint8_t)0
#define DEFAULT_SPEED (uint8_t)128
#define DEFAULT_COLOR (uint32_t)0xFF0000
#define DEFAULT_COLOR (uint32_t)0xFFAA00
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
/* each segment uses 38 bytes of SRAM memory, so if you're application fails because of
/* Not used in all effects yet */
#define WLED_FPS 42
#define FRAMETIME (1000/WLED_FPS)
/* each segment uses 37 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
#define MAX_NUM_SEGMENTS 10
#define NUM_COLORS 3 /* number of colors per segment */
#define NUM_COLORS 3 /* number of colors per segment */
#define SEGMENT _segments[_segment_index]
#define SEGMENT_RUNTIME _segment_runtimes[_segment_index]
#define SEGMENT_LENGTH (SEGMENT.stop - SEGMENT.start + 1)
#define SPEED_FORMULA_L 5 + (50*(255 - SEGMENT.speed))/SEGMENT_LENGTH
#define SEGCOLOR(x) gamma32(_segments[_segment_index].colors[x])
#define SEGENV _segment_runtimes[_segment_index]
#define SEGLEN SEGMENT.length()
#define SEGACT SEGMENT.stop
#define SPEED_FORMULA_L 5 + (50*(255 - SEGMENT.speed))/SEGLEN
#define RESET_RUNTIME memset(_segment_runtimes, 0, sizeof(_segment_runtimes))
// some common colors
@@ -77,15 +72,19 @@
#define ULTRAWHITE (uint32_t)0xFFFFFFFF
// options
// bit 8: reverse animation
// bits 5-7: fade rate (0-7)
// bit 4: gamma correction
// bits 1-3: TBD
// bit 7: segment is in transition mode
// bits 2-6: TBD
// bit 1: reverse segment
// bit 0: segment is selected
#define NO_OPTIONS (uint8_t)0x00
#define REVERSE (uint8_t)0x80
#define IS_REVERSE ((SEGMENT.options & REVERSE) == REVERSE)
#define TRANSITIONAL (uint8_t)0x80
#define REVERSE (uint8_t)0x02
#define SELECTED (uint8_t)0x01
#define IS_TRANSITIONAL ((SEGMENT.options & TRANSITIONAL) == TRANSITIONAL)
#define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE )
#define IS_SELECTED ((SEGMENT.options & SELECTED) == SELECTED )
#define MODE_COUNT 79
#define MODE_COUNT 89
#define FX_MODE_STATIC 0
#define FX_MODE_BLINK 1
@@ -103,10 +102,10 @@
#define FX_MODE_THEATER_CHASE 13
#define FX_MODE_THEATER_CHASE_RAINBOW 14
#define FX_MODE_RUNNING_LIGHTS 15
#define FX_MODE_TWINKLE 16
#define FX_MODE_TWINKLE_RANDOM 17
#define FX_MODE_TWINKLE_FADE 18
#define FX_MODE_TWINKLE_FADE_RANDOM 19
#define FX_MODE_SAW 16
#define FX_MODE_TWINKLE 17
#define FX_MODE_DISSOLVE 18
#define FX_MODE_DISSOLVE_RANDOM 19
#define FX_MODE_SPARKLE 20
#define FX_MODE_FLASH_SPARKLE 21
#define FX_MODE_HYPER_SPARKLE 22
@@ -130,15 +129,15 @@
#define FX_MODE_LARSON_SCANNER 40
#define FX_MODE_COMET 41
#define FX_MODE_FIREWORKS 42
#define FX_MODE_FIREWORKS_RANDOM 43
#define FX_MODE_RAIN 43
#define FX_MODE_MERRY_CHRISTMAS 44
#define FX_MODE_FIRE_FLICKER 45
#define FX_MODE_GRADIENT 46
#define FX_MODE_LOADING 47
#define FX_MODE_DUAL_COLOR_WIPE_IN_OUT 48
#define FX_MODE_DUAL_COLOR_WIPE_IN_IN 49
#define FX_MODE_DUAL_COLOR_WIPE_OUT_OUT 50
#define FX_MODE_DUAL_COLOR_WIPE_OUT_IN 51
#define FX_MODE_POLICE 48
#define FX_MODE_POLICE_ALL 49
#define FX_MODE_TWO_DOTS 50
#define FX_MODE_TWO_AREAS 51
#define FX_MODE_CIRCUS_COMBUSTUS 52
#define FX_MODE_HALLOWEEN 53
#define FX_MODE_TRICOLOR_CHASE 54
@@ -150,7 +149,6 @@
#define FX_MODE_DUAL_LARSON_SCANNER 60
#define FX_MODE_RANDOM_CHASE 61
#define FX_MODE_OSCILLATE 62
//Modes that use FastLED -->
#define FX_MODE_PRIDE_2015 63
#define FX_MODE_JUGGLE 64
#define FX_MODE_PALETTE 65
@@ -167,6 +165,16 @@
#define FX_MODE_METEOR 76
#define FX_MODE_METEOR_SMOOTH 77
#define FX_MODE_RAILWAY 78
#define FX_MODE_RIPPLE 79
#define FX_MODE_TWINKLEFOX 80
#define FX_MODE_TWINKLECAT 81
#define FX_MODE_HALLOWEEN_EYES 82
#define FX_MODE_STATIC_PATTERN 83
#define FX_MODE_TRI_STATIC_PATTERN 84
#define FX_MODE_SPOTS 85
#define FX_MODE_SPOTS_FADE 86
#define FX_MODE_GLITTER 87
#define FX_MODE_CANDLE 88
class WS2812FX {
@@ -174,28 +182,56 @@ class WS2812FX {
// segment parameters
public:
typedef struct Segment { // 21 bytes
typedef struct Segment { // 24 bytes
uint16_t start;
uint16_t stop;
uint16_t stop; //segment invalid if stop == 0
uint8_t speed;
uint8_t intensity;
uint8_t palette;
uint8_t mode;
uint8_t options;
uint8_t mode;
uint8_t options; //bit pattern: msb first: transitional tbd tbd tbd tbd paused reverse selected
uint8_t group, spacing;
uint8_t opacity;
uint32_t colors[NUM_COLORS];
void setOption(uint8_t n, bool val)
{
if (val) {
options |= 0x01 << n;
} else
{
options &= ~(0x01 << n);
}
}
bool getOption(uint8_t n)
{
return ((options >> n) & 0x01);
}
bool isSelected()
{
return getOption(0);
}
bool isActive()
{
return stop > start;
}
uint16_t length()
{
return stop - start;
}
} segment;
// segment runtime parameters
typedef struct Segment_runtime { // 17 bytes
typedef struct Segment_runtime { // 16 bytes
unsigned long next_time;
uint32_t counter_mode_step;
uint32_t counter_mode_call;
uint16_t aux_param;
uint16_t aux_param2;
uint8_t trans_act;
uint32_t step;
uint32_t call;
uint16_t aux0;
uint16_t aux1;
void reset(){next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;};
} segment_runtime;
WS2812FX() {
//assign each member of the _mode[] array to its respective function reference
_mode[FX_MODE_STATIC] = &WS2812FX::mode_static;
_mode[FX_MODE_BLINK] = &WS2812FX::mode_blink;
_mode[FX_MODE_COLOR_WIPE] = &WS2812FX::mode_color_wipe;
@@ -210,10 +246,10 @@ class WS2812FX {
_mode[FX_MODE_FADE] = &WS2812FX::mode_fade;
_mode[FX_MODE_THEATER_CHASE] = &WS2812FX::mode_theater_chase;
_mode[FX_MODE_THEATER_CHASE_RAINBOW] = &WS2812FX::mode_theater_chase_rainbow;
_mode[FX_MODE_SAW] = &WS2812FX::mode_saw;
_mode[FX_MODE_TWINKLE] = &WS2812FX::mode_twinkle;
_mode[FX_MODE_TWINKLE_RANDOM] = &WS2812FX::mode_twinkle_random;
_mode[FX_MODE_TWINKLE_FADE] = &WS2812FX::mode_twinkle_fade;
_mode[FX_MODE_TWINKLE_FADE_RANDOM] = &WS2812FX::mode_twinkle_fade_random;
_mode[FX_MODE_DISSOLVE] = &WS2812FX::mode_dissolve;
_mode[FX_MODE_DISSOLVE_RANDOM] = &WS2812FX::mode_dissolve_random;
_mode[FX_MODE_SPARKLE] = &WS2812FX::mode_sparkle;
_mode[FX_MODE_FLASH_SPARKLE] = &WS2812FX::mode_flash_sparkle;
_mode[FX_MODE_HYPER_SPARKLE] = &WS2812FX::mode_hyper_sparkle;
@@ -237,15 +273,15 @@ class WS2812FX {
_mode[FX_MODE_LARSON_SCANNER] = &WS2812FX::mode_larson_scanner;
_mode[FX_MODE_COMET] = &WS2812FX::mode_comet;
_mode[FX_MODE_FIREWORKS] = &WS2812FX::mode_fireworks;
_mode[FX_MODE_FIREWORKS_RANDOM] = &WS2812FX::mode_fireworks_random;
_mode[FX_MODE_RAIN] = &WS2812FX::mode_rain;
_mode[FX_MODE_MERRY_CHRISTMAS] = &WS2812FX::mode_merry_christmas;
_mode[FX_MODE_FIRE_FLICKER] = &WS2812FX::mode_fire_flicker;
_mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient;
_mode[FX_MODE_LOADING] = &WS2812FX::mode_loading;
_mode[FX_MODE_DUAL_COLOR_WIPE_IN_OUT] = &WS2812FX::mode_dual_color_wipe_in_out;
_mode[FX_MODE_DUAL_COLOR_WIPE_IN_IN] = &WS2812FX::mode_dual_color_wipe_in_in;
_mode[FX_MODE_DUAL_COLOR_WIPE_OUT_OUT] = &WS2812FX::mode_dual_color_wipe_out_out;
_mode[FX_MODE_DUAL_COLOR_WIPE_OUT_IN] = &WS2812FX::mode_dual_color_wipe_out_in;
_mode[FX_MODE_POLICE] = &WS2812FX::mode_police;
_mode[FX_MODE_POLICE_ALL] = &WS2812FX::mode_police_all;
_mode[FX_MODE_TWO_DOTS] = &WS2812FX::mode_two_dots;
_mode[FX_MODE_TWO_AREAS] = &WS2812FX::mode_two_areas;
_mode[FX_MODE_CIRCUS_COMBUSTUS] = &WS2812FX::mode_circus_combustus;
_mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween;
_mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase;
@@ -275,44 +311,38 @@ class WS2812FX {
_mode[FX_MODE_METEOR] = &WS2812FX::mode_meteor;
_mode[FX_MODE_METEOR_SMOOTH] = &WS2812FX::mode_meteor_smooth;
_mode[FX_MODE_RAILWAY] = &WS2812FX::mode_railway;
_mode[FX_MODE_RIPPLE] = &WS2812FX::mode_ripple;
_mode[FX_MODE_TWINKLEFOX] = &WS2812FX::mode_twinklefox;
_mode[FX_MODE_TWINKLECAT] = &WS2812FX::mode_twinklecat;
_mode[FX_MODE_HALLOWEEN_EYES] = &WS2812FX::mode_halloween_eyes;
_mode[FX_MODE_STATIC_PATTERN] = &WS2812FX::mode_static_pattern;
_mode[FX_MODE_TRI_STATIC_PATTERN] = &WS2812FX::mode_tri_static_pattern;
_mode[FX_MODE_SPOTS] = &WS2812FX::mode_spots;
_mode[FX_MODE_SPOTS_FADE] = &WS2812FX::mode_spots_fade;
_mode[FX_MODE_GLITTER] = &WS2812FX::mode_glitter;
_mode[FX_MODE_CANDLE] = &WS2812FX::mode_candle;
_brightness = DEFAULT_BRIGHTNESS;
_running = false;
_num_segments = 1;
_segments[0].mode = DEFAULT_MODE;
_segments[0].colors[0] = DEFAULT_COLOR;
_segments[0].start = 0;
_segments[0].speed = DEFAULT_SPEED;
_reverseMode = false;
_skipFirstMode = false;
colorOrder = 0;
paletteFade = 0;
paletteBlend = 0;
ablMilliampsMax = 750;
currentPalette = CRGBPalette16(CRGB::Black);
targetPalette = CloudColors_p;
ablMilliampsMax = 850;
currentMilliamps = 0;
_locked = NULL;
_cronixieDigits = new byte[6];
timebase = 0;
_locked = nullptr;
_modeUsesLock = false;
bus = new NeoPixelWrapper();
RESET_RUNTIME;
resetSegments();
}
void
init(bool supportWhite, uint16_t countPixels, bool skipFirst),
init(bool supportWhite, uint16_t countPixels, bool skipFirs, uint8_t disableNLeds),
service(void),
clear(void),
strip_off(void),
blur(uint8_t),
fade_out(uint8_t r),
setMode(uint8_t m),
setSpeed(uint8_t s),
setIntensity(uint8_t i),
setPalette(uint8_t p),
setColor(uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
setSecondaryColor(uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
setColor(uint32_t c),
setSecondaryColor(uint32_t c),
setMode(uint8_t segid, uint8_t m),
setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
setColor(uint8_t slot, uint32_t c),
setBrightness(uint8_t b),
setReverseMode(bool b),
driverModeCronixie(bool b),
setCronixieDigits(byte* d),
setCronixieBacklight(bool b),
@@ -325,37 +355,56 @@ class WS2812FX {
unlockAll(void),
setTransitionMode(bool t),
trigger(void),
setNumSegments(uint8_t n),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, uint32_t color, uint8_t speed, uint8_t intensity, bool reverse),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint8_t speed, uint8_t intensity, bool reverse),
setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint8_t speed, uint8_t intensity, uint8_t options),
setSegment(uint8_t n, uint16_t start, uint16_t stop),
resetSegments(),
setPixelColor(uint16_t n, uint32_t c),
setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0),
show(void);
bool
reverseMode = false,
gammaCorrectBri = false,
gammaCorrectCol = true,
applyToAllSelected = true,
segmentsAreIdentical(Segment* a, Segment* b),
setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p);
uint8_t
paletteFade,
paletteBlend,
colorOrder,
mainSegment = 0,
paletteFade = 0,
paletteBlend = 0,
colorOrder = 0,
milliampsPerLed = 55,
_disableNLeds = 0,
getBrightness(void),
getMode(void),
getSpeed(void),
getNumSegments(void),
getModeCount(void),
getPaletteCount(void),
getMaxSegments(void),
//getFirstSelectedSegment(void),
getMainSegmentId(void),
gamma8(uint8_t),
get_random_wheel_index(uint8_t);
uint16_t
ablMilliampsMax,
currentMilliamps,
triwave16(uint16_t),
getUsableCount();
uint32_t
timebase,
color_wheel(uint8_t),
color_from_palette(uint16_t, bool, bool, uint8_t, uint8_t pbri = 255),
color_blend(uint32_t,uint32_t,uint8_t),
gamma32(uint32_t),
getLastShow(void),
getPixelColor(uint16_t),
getColor(void);
WS2812FX::Segment
getSegment(void);
WS2812FX::Segment&
getSegment(uint8_t n);
WS2812FX::Segment_runtime
getSegmentRuntime(void);
@@ -363,21 +412,6 @@ class WS2812FX {
WS2812FX::Segment*
getSegments(void);
// mode helper functions
uint16_t
ablMilliampsMax,
currentMilliamps,
blink(uint32_t, uint32_t, bool strobe, bool),
color_wipe(uint32_t, uint32_t, bool , bool),
scan(bool),
theater_chase(uint32_t, uint32_t, bool),
twinkle(uint32_t),
twinkle_fade(uint32_t),
chase(uint32_t, uint32_t, uint32_t, uint8_t),
running(uint32_t, uint32_t),
fireworks(uint32_t),
tricolor_chase(uint32_t, uint32_t, uint32_t);
// builtin modes
uint16_t
mode_static(void),
@@ -400,10 +434,10 @@ class WS2812FX {
mode_rainbow(void),
mode_rainbow_cycle(void),
mode_running_lights(void),
mode_saw(void),
mode_twinkle(void),
mode_twinkle_random(void),
mode_twinkle_fade(void),
mode_twinkle_fade_random(void),
mode_dissolve(void),
mode_dissolve_random(void),
mode_sparkle(void),
mode_flash_sparkle(void),
mode_hyper_sparkle(void),
@@ -423,21 +457,22 @@ class WS2812FX {
mode_larson_scanner(void),
mode_comet(void),
mode_fireworks(void),
mode_fireworks_random(void),
mode_rain(void),
mode_merry_christmas(void),
mode_halloween(void),
mode_fire_flicker(void),
mode_gradient(void),
mode_loading(void),
mode_dual_color_wipe_in_out(void),
mode_dual_color_wipe_in_in(void),
mode_dual_color_wipe_out_out(void),
mode_dual_color_wipe_out_in(void),
mode_police(void),
mode_police_all(void),
mode_two_dots(void),
mode_two_areas(void),
mode_circus_combustus(void),
mode_bicolor_chase(void),
mode_tricolor_chase(void),
mode_tricolor_wipe(void),
mode_tricolor_fade(void),
mode_lightning(void),
mode_icu(void),
mode_multi_comet(void),
mode_dual_larson_scanner(void),
@@ -459,45 +494,100 @@ class WS2812FX {
mode_meteor(void),
mode_meteor_smooth(void),
mode_railway(void),
mode_lightning(void);
mode_ripple(void),
mode_twinklefox(void),
mode_twinklecat(void),
mode_halloween_eyes(void),
mode_static_pattern(void),
mode_tri_static_pattern(void),
mode_spots(void),
mode_spots_fade(void),
mode_glitter(void),
mode_candle(void);
private:
NeoPixelWrapper *bus;
CRGB fastled_from_col(uint32_t);
uint16_t _length;
uint32_t crgb_to_col(CRGB fastled);
CRGB col_to_crgb(uint32_t);
CRGBPalette16 currentPalette;
CRGBPalette16 targetPalette;
uint32_t now;
uint16_t _length, _lengthRaw, _usableCount;
uint16_t _rand16seed;
uint8_t _brightness;
void handle_palette(void);
void fill(uint32_t);
bool modeUsesLock(uint8_t);
boolean
_running,
bool
_modeUsesLock,
_rgbwMode,
_reverseMode,
_cronixieMode,
_cronixieBacklightEnabled,
_skipFirstMode,
_triggered;
byte* _locked;
byte* _cronixieDigits;
byte _cronixieDigits[6];
mode_ptr _mode[MODE_COUNT]; // SRAM footprint: 4 bytes per element
// mode helper functions
uint16_t
blink(uint32_t, uint32_t, bool strobe, bool),
color_wipe(bool, bool),
scan(bool),
theater_chase(uint32_t, uint32_t, bool),
running_base(bool),
dissolve(uint32_t),
chase(uint32_t, uint32_t, uint32_t, bool),
gradient_base(bool),
police_base(uint32_t, uint32_t),
running(uint32_t, uint32_t),
tricolor_chase(uint32_t, uint32_t),
twinklefox_base(bool),
spots_base(uint16_t);
CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat);
uint32_t _lastPaletteChange = 0;
uint32_t _lastShow = 0;
uint8_t _segment_index = 0;
uint8_t _segment_index_palette_last = 99;
uint8_t _num_segments = 1;
segment _segments[MAX_NUM_SEGMENTS] = { // SRAM footprint: 21 bytes per element
// start, stop, speed, intensity, palette, mode, options, color[]
{ 0, 7, DEFAULT_SPEED, 128, 0, FX_MODE_STATIC, NO_OPTIONS, {DEFAULT_COLOR}}
segment _segments[MAX_NUM_SEGMENTS] = { // SRAM footprint: 24 bytes per element
// start, stop, speed, intensity, palette, mode, options, 3 unused bytes (group, spacing, opacity), color[]
{ 0, 7, DEFAULT_SPEED, 128, 0, DEFAULT_MODE, NO_OPTIONS, 1, 0, 255, {DEFAULT_COLOR}}
};
segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 17 bytes per element
segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 16 bytes per element
};
//10 names per line
const char JSON_mode_names[] PROGMEM = R"=====([
"Solid","Blink","Breathe","Wipe","Wipe Random","Random Colors","Sweep","Dynamic","Colorloop","Rainbow",
"Scan","Dual Scan","Fade","Theater","Theater Rainbow","Running","Saw","Twinkle","Dissolve","Dissolve Rnd",
"Sparkle","Sparkle Dark","Sparkle+","Strobe","Strobe Rainbow","Strobe Mega","Blink Rainbow","Android","Chase","Chase Random",
"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Running 2","Red & Blue","Stream",
"Scanner","Lighthouse","Fireworks","Rain","Merry Christmas","Fire Flicker","Gradient","Loading","Police","Police All",
"Two Dots","Two Areas","Circus","Halloween","Tri Chase","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet",
"Scanner Dual ","Stream 2","Oscillate","Pride 2015","Juggle","Palette","Fire 2012","Colorwaves","Bpm","Fill Noise",
"Noise 1","Noise 2","Noise 3","Noise 4","Colortwinkles","Lake","Meteor","Meteor Smooth","Railway","Ripple",
"Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle"
])=====";
const char JSON_palette_names[] PROGMEM = R"=====([
"Default","Random Cycle","Primary Color","Based on Primary","Set Colors","Based on Set","Party","Cloud","Lava","Ocean",
"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash",
"Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","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"
])=====";
#endif

864
wled00/FX_fcn.cpp Normal file
View File

@@ -0,0 +1,864 @@
/*
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 "FX.h"
#include "palettes.h"
#define LED_SKIP_AMOUNT 1
#define MIN_SHOW_DELAY 15
void WS2812FX::init(bool supportWhite, uint16_t countPixels, bool skipFirst, uint8_t disableNLeds)
{
if (supportWhite == _rgbwMode && countPixels == _length && _locked != NULL && disableNLeds == _disableNLeds) return;
RESET_RUNTIME;
_rgbwMode = supportWhite;
_skipFirstMode = skipFirst;
_length = countPixels;
if (disableNLeds > 0) {
uint16_t groupCount = disableNLeds +1;
//since 1st led is lit, even partial group has a led lit, whereas int division truncates decimal.
bool hasExtraLight = _length % groupCount != 0;
_usableCount = _length/groupCount;
_usableCount += hasExtraLight ? 1 : 0;
} else {
_usableCount = _length;
}
_disableNLeds = disableNLeds;
uint8_t ty = 1;
if (supportWhite) ty =2;
_lengthRaw = _length;
if (_skipFirstMode) {
_lengthRaw += LED_SKIP_AMOUNT;
}
bus->Begin((NeoPixelType)ty, _lengthRaw);
delete[] _locked;
_locked = new byte[_length];
_segments[0].start = 0;
_segments[0].stop = _usableCount;
unlockAll();
setBrightness(_brightness);
}
void WS2812FX::service() {
uint32_t nowUp = millis(); // Be aware, millis() rolls over every 49 days
now = nowUp + timebase;
if (nowUp - _lastShow < MIN_SHOW_DELAY) return;
bool doShow = false;
for(uint8_t i=0; i < MAX_NUM_SEGMENTS; i++)
{
_segment_index = i;
if (SEGMENT.isActive())
{
if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) //last is temporary
{
doShow = true;
handle_palette();
uint16_t delay = (this->*_mode[SEGMENT.mode])();
SEGENV.next_time = nowUp + delay;
if (SEGMENT.mode != FX_MODE_HALLOWEEN_EYES) SEGENV.call++;
}
}
}
if(doShow) {
yield();
show();
}
_triggered = false;
}
bool WS2812FX::modeUsesLock(uint8_t m)
{
if (m == FX_MODE_FIRE_2012 || m == FX_MODE_COLORTWINKLE ||
m == FX_MODE_METEOR || m == FX_MODE_METEOR_SMOOTH ||
m == FX_MODE_RIPPLE || m == FX_MODE_DYNAMIC ) return true;
return false;
}
void WS2812FX::setPixelColor(uint16_t n, uint32_t c) {
uint8_t w = (c >> 24);
uint8_t r = (c >> 16);
uint8_t g = (c >> 8);
uint8_t b = c ;
setPixelColor(n, r, g, b, w);
}
void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w)
{
i = i * (_disableNLeds+1);
if (_locked[i] && !_modeUsesLock) return;
if (IS_REVERSE) i = SEGMENT.stop -1 -i + SEGMENT.start; //reverse just individual segment
byte tmpg = g;
switch (colorOrder) //0 = Grb, default
{
case 0: break; //0 = Grb, default
case 1: g = r; r = tmpg; break; //1 = Rgb, common for WS2811
case 2: g = b; b = tmpg; break; //2 = Brg
case 3: g = b; b = r; r = tmpg; //3 = Rbg
}
if (!_cronixieMode)
{
if (reverseMode) i = _length -1 -i;
if (_skipFirstMode)
{
if (i < LED_SKIP_AMOUNT) bus->SetPixelColor(i, RgbwColor(0,0,0,0));
i += LED_SKIP_AMOUNT;
}
if (i < _lengthRaw) bus->SetPixelColor(i, RgbwColor(r,g,b,w));
if (_disableNLeds > 0) {
for(uint16_t offCount=0; offCount < _disableNLeds; offCount++) {
if (i < _lengthRaw) bus->SetPixelColor((i + offCount + 1), RgbwColor(0,0,0,0));
}
}
} else {
if(i>6)return;
byte o = 10*i;
if (_cronixieBacklightEnabled && _cronixieDigits[i] <11)
{
byte r2 = _segments[0].colors[1] >>16;
byte g2 = _segments[0].colors[1] >> 8;
byte b2 = _segments[0].colors[1];
byte w2 = _segments[0].colors[1] >>24;
for (int j=o; j< o+19; j++)
{
bus->SetPixelColor(j, RgbwColor(r2,g2,b2,w2));
}
} else
{
for (int j=o; j< o+19; j++)
{
bus->SetPixelColor(j, RgbwColor(0,0,0,0));
}
}
if (_skipFirstMode) o += LED_SKIP_AMOUNT;
switch(_cronixieDigits[i])
{
case 0: bus->SetPixelColor(o+5, RgbwColor(r,g,b,w)); break;
case 1: bus->SetPixelColor(o+0, RgbwColor(r,g,b,w)); break;
case 2: bus->SetPixelColor(o+6, RgbwColor(r,g,b,w)); break;
case 3: bus->SetPixelColor(o+1, RgbwColor(r,g,b,w)); break;
case 4: bus->SetPixelColor(o+7, RgbwColor(r,g,b,w)); break;
case 5: bus->SetPixelColor(o+2, RgbwColor(r,g,b,w)); break;
case 6: bus->SetPixelColor(o+8, RgbwColor(r,g,b,w)); break;
case 7: bus->SetPixelColor(o+3, RgbwColor(r,g,b,w)); break;
case 8: bus->SetPixelColor(o+9, RgbwColor(r,g,b,w)); break;
case 9: bus->SetPixelColor(o+4, RgbwColor(r,g,b,w)); break;
}
}
}
void WS2812FX::driverModeCronixie(bool b)
{
_cronixieMode = b;
_segments[0].stop = (b) ? 6 : _length;
}
void WS2812FX::setCronixieBacklight(bool b)
{
_cronixieBacklightEnabled = b;
}
void WS2812FX::setCronixieDigits(byte d[])
{
for (int i = 0; i<6; i++)
{
_cronixieDigits[i] = d[i];
}
}
//DISCLAIMER
//The following function attemps to calculate the current LED power usage,
//and will limit the brightness to stay below a set amperage threshold.
//It is NOT a measurement and NOT guaranteed to stay within the ablMilliampsMax margin.
//Stay safe with high amperage and have a reasonable safety margin!
//I am NOT to be held liable for burned down garages!
//fine tune power estimation constants for your setup
#define MA_FOR_ESP 100 //how much mA does the ESP use (Wemos D1 about 80mA, ESP32 about 120mA)
//you can set it to 0 if the ESP is powered by USB and the LEDs by external
void WS2812FX::show(void) {
//power limit calculation
//each LED can draw up 195075 "power units" (approx. 53mA)
//one PU is the power it takes to have 1 channel 1 step brighter per brightness step
//so A=2,R=255,G=0,B=0 would use 510 PU per LED (1mA is about 3700 PU)
if (ablMilliampsMax > 149 && milliampsPerLed > 0) //0 mA per LED and too low numbers turn off calculation
{
uint32_t puPerMilliamp = 195075 / milliampsPerLed;
uint32_t powerBudget = (ablMilliampsMax - MA_FOR_ESP) * puPerMilliamp; //100mA for ESP power
if (powerBudget > puPerMilliamp * _length) //each LED uses about 1mA in standby, exclude that from power budget
{
powerBudget -= puPerMilliamp * _length;
} else
{
powerBudget = 0;
}
uint32_t powerSum = 0;
for (uint16_t i = 0; i < _length; i++) //sum up the usage of each LED
{
RgbwColor c = bus->GetPixelColorRgbw(i);
powerSum += (c.R + c.G + c.B + c.W);
}
if (_rgbwMode) //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
{
powerSum *= 3;
powerSum = powerSum >> 2; //same as /= 4
}
uint32_t powerSum0 = powerSum;
powerSum *= _brightness;
if (powerSum > powerBudget) //scale brightness down to stay in current limit
{
float scale = (float)powerBudget / (float)powerSum;
uint16_t scaleI = scale * 255;
uint8_t scaleB = (scaleI > 255) ? 255 : scaleI;
uint8_t newBri = scale8(_brightness, scaleB);
bus->SetBrightness(newBri);
currentMilliamps = (powerSum0 * newBri) / puPerMilliamp;
} else
{
currentMilliamps = powerSum / puPerMilliamp;
bus->SetBrightness(_brightness);
}
currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate
currentMilliamps += _length; //add standby power back to estimate
} else {
currentMilliamps = 0;
bus->SetBrightness(_brightness);
}
bus->Show();
_lastShow = millis();
}
void WS2812FX::trigger() {
_triggered = true;
}
void WS2812FX::setMode(uint8_t segid, uint8_t m) {
if (segid >= MAX_NUM_SEGMENTS) return;
bool anyUsedLock = _modeUsesLock, anyUseLock = false;
if (m >= MODE_COUNT) m = MODE_COUNT - 1;
if (_segments[segid].mode != m)
{
_segment_runtimes[segid].reset();
_segments[segid].mode = m;
}
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
if (modeUsesLock(_segments[i].mode)) anyUseLock = true;
}
if (anyUsedLock && !anyUseLock) unlockAll();
_modeUsesLock = anyUseLock;
}
uint8_t WS2812FX::getModeCount()
{
return MODE_COUNT;
}
uint8_t WS2812FX::getPaletteCount()
{
return 13 + gGradientPaletteCount;
}
//TODO transitions
bool WS2812FX::setEffectConfig(uint8_t m, uint8_t s, uint8_t in, uint8_t p) {
uint8_t mainSeg = getMainSegmentId();
Segment& seg = _segments[getMainSegmentId()];
uint8_t modePrev = seg.mode, speedPrev = seg.speed, intensityPrev = seg.intensity, palettePrev = seg.palette;
if (applyToAllSelected) {
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
if (_segments[i].isSelected())
{
_segments[i].speed = s;
_segments[i].intensity = in;
_segments[i].palette = p;
setMode(i, m);
}
}
} else {
seg.speed = s;
seg.intensity = in;
seg.palette = p;
setMode(mainSegment, m);
}
if (seg.mode != modePrev || seg.speed != speedPrev || seg.intensity != intensityPrev || seg.palette != palettePrev) return true;
return false;
}
void WS2812FX::setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
setColor(slot, ((uint32_t)w << 24) |((uint32_t)r << 16) | ((uint32_t)g << 8) | b);
}
void WS2812FX::setColor(uint8_t slot, uint32_t c) {
if (slot >= NUM_COLORS) return;
if (applyToAllSelected) {
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
if (_segments[i].isSelected()) _segments[i].colors[slot] = c;
}
} else {
_segments[getMainSegmentId()].colors[slot] = c;
}
}
void WS2812FX::setBrightness(uint8_t b) {
if (_brightness == b) return;
_brightness = (gammaCorrectBri) ? gamma8(b) : b;
_segment_index = 0;
if (SEGENV.next_time > millis() + 22) show();//apply brightness change immediately if no refresh soon
}
uint8_t WS2812FX::getMode(void) {
return _segments[getMainSegmentId()].mode;
}
uint8_t WS2812FX::getSpeed(void) {
return _segments[getMainSegmentId()].speed;
}
uint8_t WS2812FX::getBrightness(void) {
return _brightness;
}
uint8_t WS2812FX::getMaxSegments(void) {
return MAX_NUM_SEGMENTS;
}
/*uint8_t WS2812FX::getFirstSelectedSegment(void)
{
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++)
{
if (_segments[i].isActive() && _segments[i].isSelected()) return i;
}
for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) //if none selected, get first active
{
if (_segments[i].isActive()) return i;
}
return 0;
}*/
uint8_t WS2812FX::getMainSegmentId(void) {
if (mainSegment >= MAX_NUM_SEGMENTS) return 0;
return mainSegment;
}
uint32_t WS2812FX::getColor(void) {
return _segments[getMainSegmentId()].colors[0];
}
uint32_t WS2812FX::getPixelColor(uint16_t i)
{
i = i * (_disableNLeds+1);
if (reverseMode) i = _length- 1 -i;
if (IS_REVERSE) i = SEGMENT.stop -1 -i + SEGMENT.start; //reverse just individual segment
if (_skipFirstMode) i += LED_SKIP_AMOUNT;
if (_cronixieMode)
{
if(i>6)return 0;
byte o = 10*i;
switch(_cronixieDigits[i])
{
case 0: i=o+5; break;
case 1: i=o+0; break;
case 2: i=o+6; break;
case 3: i=o+1; break;
case 4: i=o+7; break;
case 5: i=o+2; break;
case 6: i=o+8; break;
case 7: i=o+3; break;
case 8: i=o+9; break;
case 9: i=o+4; break;
default: return 0;
}
}
if (i >= _lengthRaw) return 0;
RgbwColor lColor = bus->GetPixelColorRgbw(i);
byte r = lColor.R, g = lColor.G, b = lColor.B;
switch (colorOrder)
{
case 0: break; //0 = Grb
case 1: r = lColor.G; g = lColor.R; break; //1 = Rgb, common for WS2811
case 2: g = lColor.B; b = lColor.G; break; //2 = Brg
case 3: r = lColor.B; g = lColor.R; b = lColor.G; //3 = Rbg
}
return ( (lColor.W << 24) | (r << 16) | (g << 8) | (b) );
}
WS2812FX::Segment& WS2812FX::getSegment(uint8_t id) {
if (id >= MAX_NUM_SEGMENTS) return _segments[0];
return _segments[id];
}
WS2812FX::Segment_runtime WS2812FX::getSegmentRuntime(void) {
return SEGENV;
}
WS2812FX::Segment* WS2812FX::getSegments(void) {
return _segments;
}
uint16_t WS2812FX::getUsableCount(void) {
return _usableCount;
}
uint32_t WS2812FX::getLastShow(void) {
return _lastShow;
}
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2) {
if (n >= MAX_NUM_SEGMENTS) return;
Segment& seg = _segments[n];
if (seg.start == i1 && seg.stop == i2) return;
if (seg.isActive() && modeUsesLock(seg.mode))
{
_modeUsesLock = false;
unlockRange(seg.start, seg.stop);
_modeUsesLock = true;
}
_segment_index = n; fill(0); //turn old segment range off
if (i2 <= i1) //disable segment
{
seg.stop = 0; return;
}
if (i1 < _length) seg.start = i1;
seg.stop = i2;
if (i2 > _length) seg.stop = _length;
_segment_runtimes[n].reset();
}
void WS2812FX::resetSegments() {
memset(_segments, 0, sizeof(_segments));
memset(_segment_runtimes, 0, sizeof(_segment_runtimes));
_segment_index = 0;
_segments[0].mode = DEFAULT_MODE;
_segments[0].colors[0] = DEFAULT_COLOR;
_segments[0].start = 0;
_segments[0].speed = DEFAULT_SPEED;
_segments[0].stop = _length;
_segments[0].setOption(0, 1); //select
for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++)
{
_segments[i].colors[0] = color_wheel(i*51);
}
}
void WS2812FX::setIndividual(uint16_t i, uint32_t col)
{
if (modeUsesLock(SEGMENT.mode)) return;
if (i >= 0 && i < _length)
{
_locked[i] = false;
setPixelColor(i, col);
_locked[i] = true;
}
}
void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col)
{
if (i2 >= i)
{
for (uint16_t x = i; x <= i2; x++) setIndividual(x,col);
} else
{
for (uint16_t x = i2; x <= i; x++) setIndividual(x,col);
}
}
void WS2812FX::lock(uint16_t i)
{
if (_modeUsesLock) return;
if (i < _length) _locked[i] = true;
}
void WS2812FX::lockRange(uint16_t i, uint16_t i2)
{
if (_modeUsesLock) return;
for (uint16_t x = i; x < i2; x++)
{
if (x < _length) _locked[i] = true;
}
}
void WS2812FX::unlock(uint16_t i)
{
if (_modeUsesLock) return;
if (i < _length) _locked[i] = false;
}
void WS2812FX::unlockRange(uint16_t i, uint16_t i2)
{
if (_modeUsesLock) return;
for (uint16_t x = i; x < i2; x++)
{
if (x < _length) _locked[x] = false;
}
}
void WS2812FX::unlockAll()
{
for (int i=0; i < _length; i++) _locked[i] = false;
}
void WS2812FX::setTransitionMode(bool t)
{
_segment_index = getMainSegmentId();
SEGMENT.setOption(7,t);
if (!t) return;
unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled
if (SEGMENT.mode == FX_MODE_STATIC && SEGENV.next_time > waitMax) SEGENV.next_time = waitMax;
}
/*
* color blend function
*/
uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint8_t blend) {
if(blend == 0) return color1;
if(blend == 255) return color2;
uint32_t w1 = (color1 >> 24) & 0xff;
uint32_t r1 = (color1 >> 16) & 0xff;
uint32_t g1 = (color1 >> 8) & 0xff;
uint32_t b1 = color1 & 0xff;
uint32_t w2 = (color2 >> 24) & 0xff;
uint32_t r2 = (color2 >> 16) & 0xff;
uint32_t g2 = (color2 >> 8) & 0xff;
uint32_t b2 = color2 & 0xff;
uint32_t w3 = ((w2 * blend) + (w1 * (255 - blend))) >> 8;
uint32_t r3 = ((r2 * blend) + (r1 * (255 - blend))) >> 8;
uint32_t g3 = ((g2 * blend) + (g1 * (255 - blend))) >> 8;
uint32_t b3 = ((b2 * blend) + (b1 * (255 - blend))) >> 8;
return ((w3 << 24) | (r3 << 16) | (g3 << 8) | (b3));
}
/*
* Fills segment with color
*/
void WS2812FX::fill(uint32_t c) {
for(uint16_t i=SEGMENT.start; i < SEGMENT.stop; i++) {
setPixelColor(i, c);
}
}
/*
* fade out function, higher rate = quicker fade
*/
void WS2812FX::fade_out(uint8_t rate) {
rate = (255-rate) >> 1;
float mappedRate = float(rate) +1.1;
uint32_t color = SEGCOLOR(1); // target color
int w2 = (color >> 24) & 0xff;
int r2 = (color >> 16) & 0xff;
int g2 = (color >> 8) & 0xff;
int b2 = color & 0xff;
for(uint16_t i=SEGMENT.start; i < SEGMENT.stop; i++) {
color = getPixelColor(i);
int w1 = (color >> 24) & 0xff;
int r1 = (color >> 16) & 0xff;
int g1 = (color >> 8) & 0xff;
int b1 = color & 0xff;
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;
setPixelColor(i, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta);
}
}
/*
* blurs segment content, source: FastLED colorutils.cpp
*/
void WS2812FX::blur(uint8_t blur_amount)
{
uint8_t keep = 255 - blur_amount;
uint8_t seep = blur_amount >> 1;
CRGB carryover = CRGB::Black;
for(uint16_t i = SEGMENT.start; i < SEGMENT.stop; i++)
{
CRGB cur = col_to_crgb(getPixelColor(i));
CRGB part = cur;
part.nscale8(seep);
cur.nscale8(keep);
cur += carryover;
if(i > SEGMENT.start) {
uint32_t c = getPixelColor(i-1);
uint8_t r = (c >> 16 & 0xFF);
uint8_t g = (c >> 8 & 0xFF);
uint8_t b = (c & 0xFF);
setPixelColor(i-1, qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue));
}
setPixelColor(i,cur.red, cur.green, cur.blue);
carryover = part;
}
}
uint16_t WS2812FX::triwave16(uint16_t in)
{
if (in < 0x8000) return in *2;
return 0xFFFF - (in - 0x8000)*2;
}
/*
* 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 WS2812FX::color_wheel(uint8_t pos) {
if (SEGMENT.palette) return color_from_palette(pos, false, true, 0);
pos = 255 - pos;
if(pos < 85) {
return ((uint32_t)(255 - pos * 3) << 16) | ((uint32_t)(0) << 8) | (pos * 3);
} else if(pos < 170) {
pos -= 85;
return ((uint32_t)(0) << 16) | ((uint32_t)(pos * 3) << 8) | (255 - pos * 3);
} else {
pos -= 170;
return ((uint32_t)(pos * 3) << 16) | ((uint32_t)(255 - pos * 3) << 8) | (0);
}
}
/*
* Returns a new, random wheel index with a minimum distance of 42 from pos.
*/
uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) {
uint8_t r = 0, x = 0, y = 0, d = 0;
while(d < 42) {
r = random8();
x = abs(pos - r);
y = 255 - x;
d = min(x, y);
}
return r;
}
uint32_t WS2812FX::crgb_to_col(CRGB fastled)
{
return (((uint32_t)fastled.red << 16) | ((uint32_t)fastled.green << 8) | fastled.blue);
}
CRGB WS2812FX::col_to_crgb(uint32_t color)
{
CRGB fastled_col;
fastled_col.red = (color >> 16 & 0xFF);
fastled_col.green = (color >> 8 & 0xFF);
fastled_col.blue = (color & 0xFF);
return fastled_col;
}
/*
* FastLED palette modes helper function. Limitation: Due to memory reasons, multiple active segments with FastLED will disable the Palette transitions
*/
void WS2812FX::handle_palette(void)
{
bool singleSegmentMode = (_segment_index == _segment_index_palette_last);
_segment_index_palette_last = _segment_index;
byte paletteIndex = SEGMENT.palette;
if (SEGMENT.mode == FX_MODE_GLITTER && paletteIndex == 0) paletteIndex = 11;
if (SEGMENT.mode >= FX_MODE_METEOR && paletteIndex == 0) paletteIndex = 4;
switch (paletteIndex)
{
case 0: {//default palette. Differs depending on effect
switch (SEGMENT.mode)
{
case FX_MODE_FIRE_2012 : targetPalette = gGradientPalettes[22]; break;//heat palette
case FX_MODE_COLORWAVES : targetPalette = gGradientPalettes[13]; break;//landscape 33
case FX_MODE_FILLNOISE8 : targetPalette = OceanColors_p; break;
case FX_MODE_NOISE16_1 : targetPalette = gGradientPalettes[17]; break;//Drywet
case FX_MODE_NOISE16_2 : targetPalette = gGradientPalettes[30]; break;//Blue cyan yellow
case FX_MODE_NOISE16_3 : targetPalette = gGradientPalettes[22]; break;//heat palette
case FX_MODE_NOISE16_4 : targetPalette = gGradientPalettes[13]; break;//landscape 33
//case FX_MODE_GLITTER : targetPalette = RainbowColors_p; break;
default: targetPalette = PartyColors_p; break;//palette, bpm
}
break;}
case 1: {//periodically replace palette with a random one. Doesn't work with multiple FastLED segments
if (!singleSegmentMode)
{
targetPalette = PartyColors_p; break; //fallback
}
if (millis() - _lastPaletteChange > 1000 + ((uint32_t)(255-SEGMENT.intensity))*100)
{
targetPalette = CRGBPalette16(
CHSV(random8(), 255, random8(128, 255)),
CHSV(random8(), 255, random8(128, 255)),
CHSV(random8(), 192, random8(128, 255)),
CHSV(random8(), 255, random8(128, 255)));
_lastPaletteChange = millis();
} break;}
case 2: {//primary color only
CRGB prim = col_to_crgb(SEGCOLOR(0));
targetPalette = CRGBPalette16(prim); break;}
case 3: {//based on primary
//considering performance implications
CRGB prim = col_to_crgb(SEGCOLOR(0));
CHSV prim_hsv = rgb2hsv_approximate(prim);
targetPalette = CRGBPalette16(
CHSV(prim_hsv.h, prim_hsv.s, prim_hsv.v), //color itself
CHSV(prim_hsv.h, max(prim_hsv.s - 50,0), prim_hsv.v), //less saturated
CHSV(prim_hsv.h, prim_hsv.s, max(prim_hsv.v - 50,0)), //darker
CHSV(prim_hsv.h, prim_hsv.s, prim_hsv.v)); //color itself
break;}
case 4: {//primary + secondary
CRGB prim = col_to_crgb(SEGCOLOR(0));
CRGB sec = col_to_crgb(SEGCOLOR(1));
targetPalette = CRGBPalette16(sec,prim); break;}
case 5: {//based on primary + secondary
CRGB prim = col_to_crgb(SEGCOLOR(0));
CRGB sec = col_to_crgb(SEGCOLOR(1));
CRGB ter = col_to_crgb(SEGCOLOR(2));
targetPalette = CRGBPalette16(ter,sec,prim); 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
targetPalette = gGradientPalettes[constrain(SEGMENT.palette -13, 0, gGradientPaletteCount -1)];
}
if (singleSegmentMode && paletteFade) //only blend if just one segment uses FastLED mode
{
nblendPaletteTowardPalette(currentPalette, targetPalette, 48);
} else
{
currentPalette = targetPalette;
}
}
uint32_t WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri)
{
if (SEGMENT.palette == 0 && mcol < 3) return SEGCOLOR(mcol); //WS2812FX default
uint8_t paletteIndex = i;
if (mapping) paletteIndex = map(i,SEGMENT.start,SEGMENT.stop-1,0,255);
if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end"
CRGB fastled_col;
fastled_col = ColorFromPalette( currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND);
return fastled_col.r*65536 + fastled_col.g*256 + fastled_col.b;
}
bool WS2812FX::segmentsAreIdentical(Segment* a, Segment* b)
{
//if (a->start != b->start) return false;
//if (a->stop != b->stop) return false;
for (uint8_t i = 0; i < NUM_COLORS; i++)
{
if (a->colors[i] != b->colors[i]) return false;
}
if (a->mode != b->mode) return false;
if (a->speed != b->speed) return false;
if (a->intensity != b->intensity) return false;
if (a->palette != b->palette) return false;
//if (a->getOption(1) != b->getOption(1)) return false; //reverse
return true;
}
//gamma 2.4 lookup table used for color correction
const byte gammaT[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114,
115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142,
144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175,
177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213,
215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 };
uint8_t WS2812FX::gamma8(uint8_t b)
{
return gammaT[b];
}
uint32_t WS2812FX::gamma32(uint32_t color)
{
if (!gammaCorrectCol) return color;
uint8_t w = (color >> 24);
uint8_t r = (color >> 16);
uint8_t g = (color >> 8);
uint8_t b = color;
w = gammaT[w];
r = gammaT[r];
g = gammaT[g];
b = gammaT[b];
return ((w << 24) | (r << 16) | (g << 8) | (b));
}

View File

@@ -2,29 +2,38 @@
#ifndef NpbWrapper_h
#define NpbWrapper_h
#define WORKAROUND_ESP32_BITBANG
//see https://github.com/Aircoookie/WLED/issues/2 for flicker free ESP32 support
//PIN CONFIGURATION
#define LEDPIN 2 //strip pin. Any for ESP32, gpio2 is recommended for ESP8266
#define BTNPIN 0 //button pin. Needs to have pullup (gpio0 recommended)
#define IR_PIN 4 //infrared pin.
#define AUXPIN 15 //unused auxiliary output pin
#define LEDPIN 2 //strip pin. Any for ESP32, gpio2 or 3 is recommended for ESP8266 (gpio2/3 are labeled D4/RX on NodeMCU and Wemos)
//#define USE_APA102 // Uncomment for using APA102 LEDs.
#define BTNPIN 0 //button pin. Needs to have pullup (gpio0 recommended)
#define IR_PIN 4 //infrared pin (-1 to disable)
#define RLYPIN 12 //pin for relay, will be set HIGH if LEDs are on (-1 to disable). Also usable for standby leds, triggers,...
#define AUXPIN -1 //debug auxiliary output pin (-1 to disable)
#define RLYMDE 1 //mode for relay, 0: LOW if LEDs are on 1: HIGH if LEDs are on
#ifdef USE_APA102
#define CLKPIN 0
#define DATAPIN 2
#if BTNPIN == CLKPIN || BTNPIN == DATAPIN
#undef BTNPIN // Deactivate button pin if it conflicts with one of the APA102 pins.
#endif
#endif
//automatically uses the right driver method for each platform
#ifdef ARDUINO_ARCH_ESP32
#ifdef WORKAROUND_ESP32_BITBANG
#define PIXELMETHOD NeoEsp32BitBangWs2813Method
#pragma message "Software BitBang is used because of your NeoPixelBus version. Look in NpbWrapper.h for instructions on how to mitigate flickering."
#ifdef USE_APA102
#define PIXELMETHOD DotStarMethod
#else
#define PIXELMETHOD NeoEsp32RmtWS2813_V3Method
#define PIXELMETHOD NeoEsp32Rmt0Ws2812xMethod
#endif
#else //esp8266
//autoselect the right method depending on strip pin
#if LEDPIN == 2
#define PIXELMETHOD NeoEsp8266Uart800KbpsMethod
#ifdef USE_APA102
#define PIXELMETHOD DotStarMethod
#elif LEDPIN == 2
#define PIXELMETHOD NeoEsp8266Uart1Ws2813Method //if you get an error here, try to change to NeoEsp8266UartWs2813Method or update Neopixelbus
#elif LEDPIN == 3
#define PIXELMETHOD NeoEsp8266Dma800KbpsMethod
#else
@@ -35,8 +44,13 @@
//you can now change the color order in the web settings
#define PIXELFEATURE3 NeoGrbFeature
#define PIXELFEATURE4 NeoGrbwFeature
#ifdef USE_APA102
#define PIXELFEATURE3 DotStarBgrFeature
#define PIXELFEATURE4 DotStarLbgrFeature
#else
#define PIXELFEATURE3 NeoGrbFeature
#define PIXELFEATURE4 NeoGrbwFeature
#endif
#include <NeoPixelBrightnessBus.h>
@@ -74,12 +88,20 @@ public:
switch (_type)
{
case NeoPixelType_Grb:
#ifdef USE_APA102
_pGrb = new NeoPixelBrightnessBus<PIXELFEATURE3,PIXELMETHOD>(countPixels, CLKPIN, DATAPIN);
#else
_pGrb = new NeoPixelBrightnessBus<PIXELFEATURE3,PIXELMETHOD>(countPixels, LEDPIN);
#endif
_pGrb->Begin();
break;
case NeoPixelType_Grbw:
#ifdef USE_APA102
_pGrbw = new NeoPixelBrightnessBus<PIXELFEATURE4,PIXELMETHOD>(countPixels, CLKPIN, DATAPIN);
#else
_pGrbw = new NeoPixelBrightnessBus<PIXELFEATURE4,PIXELMETHOD>(countPixels, LEDPIN);
#endif
_pGrbw->Begin();
break;
}
@@ -87,24 +109,11 @@ public:
void Show()
{
#ifdef ARDUINO_ARCH_ESP32
#ifdef WORKAROUND_ESP32_BITBANG
delay(1);
portDISABLE_INTERRUPTS(); //this is a workaround to prevent flickering (see https://github.com/adafruit/Adafruit_NeoPixel/issues/139)
#endif
#endif
switch (_type)
{
case NeoPixelType_Grb: _pGrb->Show(); break;
case NeoPixelType_Grbw: _pGrbw->Show(); break;
}
#ifdef ARDUINO_ARCH_ESP32
#ifdef WORKAROUND_ESP32_BITBANG
portENABLE_INTERRUPTS();
#endif
#endif
}
bool CanShow() const

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,129 @@
/*
Editor: https://www.visualmicro.com/
This file is for intellisense purpose only.
Visual micro (and the arduino ide) ignore this code during compilation. This code is automatically maintained by visualmicro, manual changes to this file will be overwritten
The contents of the _vm sub folder can be deleted prior to publishing a project
All non-arduino files created by visual micro and all visual studio project or solution files can be freely deleted and are not required to compile a sketch (do not delete your own code!).
Note: debugger breakpoints are stored in '.sln' or '.asln' files, knowledge of last uploaded breakpoints is stored in the upload.vmps.xml file. Both files are required to continue a previous debug session without needing to compile and upload again
Hardware: ESP32 Dev Module, Platform=esp32, Package=esp32
*/
#if defined(_VMICRO_INTELLISENSE)
#ifndef _VSARDUINO_H_
#define _VSARDUINO_H_
#define __ESP32_esp32__
#define __ESP32_ESP32__
#define ESP_PLATFORM
#define HAVE_CONFIG_H
#define F_CPU 240000000L
#define ARDUINO 10809
#define ARDUINO_ESP32_DEV
#define ARDUINO_ARCH_ESP32
#define ESP32
#define CORE_DEBUG_LEVEL 0
#define __cplusplus 201103L
#define _Pragma(x)
#undef __cplusplus
#define __cplusplus 201103L
#define __STDC__
#define __ARM__
#define __arm__
#define __inline__
#define __asm__(...)
#define __extension__
#define __ATTR_PURE__
#define __ATTR_CONST__
#define __volatile__
#define __ASM
#define __INLINE
#define __attribute__(noinline)
//#define _STD_BEGIN
//#define EMIT
#define WARNING
#define _Lockit
#define __CLR_OR_THIS_CALL
#define C4005
#define _NEW
typedef bool _Bool;
typedef int _read;
typedef int _seek;
typedef int _write;
typedef int _close;
typedef int __cleanup;
//#define inline
#define __builtin_clz
#define __builtin_clzl
#define __builtin_clzll
#define __builtin_labs
#define __builtin_va_list
typedef int __gnuc_va_list;
#define __ATOMIC_ACQ_REL
#define __CHAR_BIT__
#define _EXFUN()
typedef unsigned char byte;
extern "C" void __cxa_pure_virtual() {;}
typedef long __INTPTR_TYPE__ ;
typedef long __UINTPTR_TYPE__ ;
typedef long __SIZE_TYPE__ ;
typedef long __PTRDIFF_TYPE__;
typedef long pthread_t;
typedef long pthread_key_t;
typedef long pthread_once_t;
typedef long pthread_mutex_t;
typedef long pthread_mutex_t;
typedef long pthread_cond_t;
#include "arduino.h"
#include <pins_arduino.h>
//#include "..\generic\Common.h"
//#include "..\generic\pins_arduino.h"
//#undef F
//#define F(string_literal) ((const PROGMEM char *)(string_literal))
//#undef PSTR
//#define PSTR(string_literal) ((const PROGMEM char *)(string_literal))
//current vc++ does not understand this syntax so use older arduino example for intellisense
//todo:move to the new clang/gcc project types.
#define interrupts() sei()
#define noInterrupts() cli()
#include "wled00.ino"
#include "wled01_eeprom.ino"
#include "wled02_xml.ino"
#include "wled03_set.ino"
#include "wled04_file.ino"
#include "wled05_init.ino"
#include "wled06_usermod.ino"
#include "wled07_notify.ino"
#include "wled08_led.ino"
#include "wled09_button.ino"
#include "wled10_ntp.ino"
#include "wled11_ol.ino"
#include "wled12_alexa.ino"
#include "wled13_cronixie.ino"
#include "wled14_colors.ino"
#include "wled15_hue.ino"
#include "wled16_blynk.ino"
#include "wled17_mqtt.ino"
#include "wled18_server.ino"
#include "wled19_json.ino"
#include "wled20_ir.ino"
#endif
#endif

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

100
wled00/data/jsontest.htm Normal file
View File

@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html>
<head>
<title>JSON client</title>
<style>
:root {
--bCol:#333;--cCol:#222;--dCol:#666;--tCol:#fff;
}
body {
font-family: Verdana, sans-serif;
text-align: center;
background: var(--cCol);
color: var(--tCol);
margin: 20px;
background-attachment: fixed;
}
button {
background: var(--cCol);
color: var(--tCol);
border: 0.3ch solid var(--cCol);
display: inline-block;
font-size: 20px;
margin: 8px;
margin-top: 12px;
}
input {
background: var(--cCol);
color: var(--tCol);
border: 0.5ch solid var(--cCol);
width: 100%;
}
h1{
margin: 0px;
font-size: 20px;
}
h2{
font-size: 16px;
margin-top: 20px;
}
form{
background: var(--bCol);
width: 500px;
padding: 20px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
display: inline-block;
}
textarea{
background: var(--cCol);
color: var(--tCol);
padding-top: 10px;
width: 100%;
font-family: monaco,monospace;
font-size: 12px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
}
</style>
</head>
<body>
<form name="cf">
<h1>JSON API test tool</h1>
<h2>URL:</h2>
<input name="cu" type="text" size="60" value="http://192.168.4.1/json">
<div id="buttons">
<button type="button" onclick="rq('GET')">GET</button>
<button type="button" onclick="rq('POST')">POST</button>
</div>
<h2>Body:</h2>
<textarea name="bd" rows="8" cols="100"></textarea>
<h2>Response:</h2>
<textarea name="rsp" rows="25" cols="100"></textarea>
</form>
</body>
</html>
<script>
function rq(cm)
{
var h = new XMLHttpRequest();
h.open(cm, document.cf.cu.value, true);
h.onreadystatechange = function()
{
if(h.readyState == 4)
{
if(h.status==200)
{
document.cf.rsp.value="Bad JSON: "+h.responseText
document.cf.rsp.value=JSON.stringify(JSON.parse(h.responseText), null, '\t');
}
else
{
document.cf.rsp.value="Error "+h.status+"\r\n\n"+h.responseText;
}
}
}
h.send(document.cf.bd.value);
}
</script>

64
wled00/data/liveview.htm Normal file
View File

@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta charset="utf-8">
<meta name="theme-color" content="#222222">
<title>WLED Live Preview</title>
<style>
body {
margin: 0;
}
#canv {
background: black;
filter: brightness(175%);
width: 100%;
height: 100%;
position: absolute;
}
</style>
</head>
<body>
<div id="canv" />
<script>
update();
var tmout = null;
function update()
{
if (document.hidden) {
clearTimeout(tmout);
tmout = setTimeout(update, 250);
return;
}
fetch('/json/live')
.then(res => {
if (!res.ok) {
clearTimeout(tmout);
tmout = setTimeout(update, 2500);
}
return res.json();
})
.then(json => {
var str = "linear-gradient(90deg,";
var len = json.leds.length;
for (i = 0; i < len; i++) {
var leddata = json.leds[i];
if (leddata.length > 6) leddata = leddata.substring(2);
str += "#" + leddata;
if (i < len -1) str += ","
}
str += ")";
document.getElementById("canv").style.background = str;
clearTimeout(tmout);
tmout = setTimeout(update, 40);
})
.catch(function (error) {
clearTimeout(tmout);
tmout = setTimeout(update, 2500);
})
}
</script>
</body>
</html>

View File

@@ -23,7 +23,7 @@
--tCol: #328CC1;
--cFn: Verdana;
}
button {
.bt {
background: var(--bCol);
color: var(--tCol);
border: 0.3ch solid var(--bCol);
@@ -34,6 +34,9 @@
margin: 8px;
margin-top: 12px;
}
input[type=file] {
font-size: 16px;
}
body {
font-family: var(--cFn), sans-serif;
text-align: center;
@@ -47,6 +50,7 @@
</head>
<body>
<h2>Sample message.</h2>
Sample detail.
</body>
</html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

4
wled00/data/update.htm Normal file
View File

@@ -0,0 +1,4 @@
<!DOCTYPE html>
<html><head><meta content='width=device-width' name='viewport'><title>WLED Message</title><script>function B(){window.history.back()}</script>
<style>:root{--aCol:#D9B310;--bCol:#0B3C5D;--cCol:#1D2731;--dCol:#328CC1;--sCol:#000;--tCol:#328CC1;--cFn:Verdana;}.bt{background:var(--bCol);color:var(--tCol);font-family:var(--cFn),sans-serif;border:.3ch solid var(--bCol);display:inline-block;filter:drop-shadow(-5px -5px 5px var(--sCol));font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:var(--cFn),sans-serif;text-align:center;background:var(--cCol);color:var(--tCol);line-height:200%}</style></head>
<body><h2>WLED Software Update</h2>Installed version: 0.8.5-dev<br>Download the latest binary: <a href="https://github.com/Aircoookie/WLED/releases"><img src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square"></a><br><form method='POST' action='/update' enctype='multipart/form-data'><input type='file' class="bt" name='update' required><br><input type='submit' class="bt" value='Update!'></form><button type="button" class="bt" onclick="B()">Back</button></body></html>

View File

@@ -1,27 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content='width=device-width' name='viewport'>
<meta name="theme-color" content="#333333">
<title>WLED Setup</title>
<style>
:root {
--aCol: #D9B310;
--bCol: #0B3C5D;
--cCol: #1D2731;
--dCol: #328CC1;
--sCol: #000;
}
body {
font-family: Verdana, Helvetica, sans-serif;
text-align: center;
background: linear-gradient(var(--bCol),black);
background-color: #333;
margin: 0;
background-attachment: fixed;
color: var(--dCol);
color: #fff;
}
button {
outline: none;
cursor: pointer;
padding: 8px;
margin: 10px;
width: 230px;
text-transform: uppercase;
font-family: helvetica;
font-size: 19px;
background-color: #222;
color: white;
border: 0px solid white;
border-radius: 5px;
}
svg {
fill: var(--dCol);
fill: #fff;
}
</style>
</head>
@@ -34,10 +43,12 @@
<svg><use xlink:href="#lnr-smile"></use></svg>
<h1>Welcome to WLED!</h1>
<h3>Thank you for installing my application!</h3>
Take a quick look at the <a href="https://github.com/Aircoookie/WLED/wiki" target="_blank">wiki</a>!<br>
If you encounter a bug or have a question/feature suggestion, feel free to open a GitHub issue!<br><br>
<b>Next steps:</b><br><br>
Connect the module to your local WiFi <a href="/settings/wifi">here</a>!<br><br>
<i>Just trying this out in AP mode?</i> <a href="/sliders">Here are the controls.</a><br>
Connect the module to your local WiFi here!<br>
<button onclick="window.location.href='/settings/wifi'">WiFi settings</button><br>
<i>Just trying this out in AP mode?</i><br>
<button onclick="window.location.href='/sliders'">To the controls!</button>
</body>
</html>

View File

@@ -1,711 +0,0 @@
/*
* Binary arrays for the classic desktop UI index page.
* gzip is used for smaller size and improved speeds.
*
* Workflow for creating them from HTML source:
* 1. Minify HTML (https://htmlcompressor.com/compressor/) (optional)
* 2. Compress with gzip (https://online-converting.com/archives/convert-to-gzip/)
* 3. Convert gzip binary to c array (https://sourceforge.net/projects/bin2header/)
* alternative: https://littlevgl.com/image-to-c-array (raw)
* 4. update length value
*/
const uint16_t PAGE_index0_L = 11122;
const char PAGE_index0[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x08, 0x8d, 0x45, 0x08, 0x5c, 0x00, 0x03, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65,
0x73, 0x73, 0x65, 0x64, 0x20, 0x28, 0x31, 0x30, 0x29, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x00, 0xcc,
0x5a, 0x09, 0x6f, 0xdb, 0xc6, 0xb6, 0xfe, 0x2b, 0xac, 0x82, 0xb6, 0x52, 0x2d, 0xd2, 0xe4, 0x70,
0x97, 0x4c, 0x17, 0x89, 0xdb, 0x2c, 0x40, 0xe2, 0x1a, 0xb1, 0x6f, 0x9b, 0xa2, 0x28, 0x02, 0x8a,
0xa4, 0x2c, 0xc6, 0x14, 0xa9, 0x4b, 0x52, 0x5e, 0xea, 0xfa, 0xbf, 0xbf, 0xef, 0xcc, 0x0c, 0x25,
0x52, 0xde, 0xa4, 0xdc, 0xbe, 0xfb, 0x5e, 0x80, 0x50, 0x9a, 0x39, 0x33, 0x73, 0x96, 0xef, 0x6c,
0x43, 0xf9, 0xe0, 0x9b, 0x9f, 0x7e, 0x39, 0x3a, 0xfb, 0xfd, 0xe4, 0x67, 0x65, 0x56, 0xcf, 0xb3,
0xc3, 0x03, 0xf9, 0x4c, 0xc2, 0xf8, 0xf0, 0x60, 0x9e, 0xd4, 0xa1, 0x12, 0xcd, 0xc2, 0xb2, 0x4a,
0xea, 0x60, 0x59, 0x4f, 0x55, 0x4f, 0xce, 0xe5, 0xe1, 0x3c, 0x09, 0xea, 0x59, 0x32, 0x4f, 0xd4,
0xa8, 0xc8, 0x8a, 0x52, 0x89, 0x8a, 0xbc, 0x4e, 0xf2, 0x3a, 0x78, 0x31, 0x9d, 0x4e, 0x0f, 0x0f,
0xb2, 0x34, 0xbf, 0x50, 0xca, 0x24, 0x0b, 0xbe, 0xaf, 0x66, 0x45, 0x59, 0x47, 0xcb, 0x5a, 0x49,
0xb1, 0xe2, 0x7b, 0xa5, 0xbe, 0x59, 0x24, 0x41, 0x3a, 0x0f, 0xcf, 0x93, 0xfd, 0x6b, 0x95, 0xa6,
0x94, 0x59, 0x99, 0x4c, 0x83, 0xef, 0xf7, 0xa7, 0xe1, 0x25, 0x0d, 0x35, 0x3c, 0xbe, 0xdf, 0x3f,
0x3c, 0xa8, 0xd3, 0x3a, 0x4b, 0x0e, 0x7f, 0x7b, 0xff, 0xf3, 0x4f, 0x8a, 0xae, 0x79, 0x1a, 0x3b,
0xd8, 0x17, 0x33, 0xca, 0x41, 0x15, 0x95, 0xe9, 0xa2, 0x3e, 0xbc, 0x0c, 0x4b, 0x25, 0x0e, 0xe2,
0x22, 0x5a, 0xce, 0xc1, 0x76, 0x4c, 0xc3, 0xab, 0xe0, 0x2a, 0xcd, 0xe3, 0xe2, 0x4a, 0x3b, 0x4f,
0xea, 0xa3, 0x62, 0xbe, 0x58, 0xd6, 0x49, 0x7c, 0x5a, 0xdf, 0x64, 0x49, 0x3f, 0xd6, 0xfe, 0xbd,
0x4c, 0xca, 0x9b, 0xd3, 0x24, 0x4b, 0xa2, 0xba, 0x28, 0xfb, 0x3d, 0xd2, 0xb1, 0x37, 0x18, 0x8c,
0xcb, 0xa4, 0x5a, 0x04, 0xbd, 0x1e, 0xdf, 0x9e, 0x67, 0x61, 0x30, 0x0d, 0xb3, 0x2a, 0x11, 0xa3,
0xb2, 0x33, 0xaa, 0xda, 0xa3, 0xaa, 0x2e, 0x5a, 0xa3, 0xe5, 0xd5, 0x65, 0x9b, 0x36, 0x99, 0x06,
0x75, 0xb9, 0x14, 0x83, 0x69, 0x5a, 0x56, 0x75, 0x56, 0x84, 0xf1, 0x7a, 0x2a, 0x0b, 0xab, 0xba,
0xba, 0x0e, 0x74, 0x71, 0xec, 0x69, 0x1d, 0xd6, 0x89, 0x1c, 0x44, 0x97, 0xf2, 0x4b, 0x36, 0xc7,
0x97, 0xf0, 0x88, 0xe4, 0x9a, 0xf0, 0x67, 0xcc, 0x9f, 0xd3, 0x65, 0x1e, 0xd5, 0x29, 0x2c, 0x76,
0xfe, 0x2e, 0xee, 0x87, 0x83, 0xdb, 0x32, 0xa9, 0x97, 0x65, 0xae, 0xc4, 0xa4, 0xee, 0xcf, 0x59,
0x42, 0x66, 0x78, 0x75, 0xc3, 0x49, 0x77, 0xab, 0xa5, 0xff, 0x3a, 0x2a, 0xb2, 0xfe, 0xe0, 0x16,
0x87, 0x71, 0xab, 0x9c, 0x94, 0xc5, 0x22, 0x29, 0xeb, 0x9b, 0x5f, 0xc3, 0x6c, 0x99, 0xf4, 0x7b,
0xaa, 0x1a, 0x82, 0xde, 0x1b, 0x10, 0x9b, 0x87, 0xe9, 0x13, 0x41, 0x8f, 0x1f, 0xa3, 0xc7, 0x92,
0xbe, 0x69, 0x5f, 0xf2, 0x91, 0x3f, 0x36, 0x7d, 0xe4, 0xcf, 0xde, 0x40, 0x83, 0x1f, 0xbd, 0xac,
0xeb, 0x32, 0x9d, 0x00, 0x9c, 0x7e, 0x4f, 0x3a, 0x4d, 0x6f, 0x38, 0x39, 0x6a, 0x09, 0x0d, 0xa3,
0x94, 0xf5, 0x72, 0x01, 0xb9, 0xc9, 0x1a, 0xe1, 0xee, 0xa8, 0x0a, 0xad, 0xc7, 0x47, 0xbf, 0xf6,
0xf5, 0xc1, 0x18, 0x1c, 0xdf, 0x81, 0x4b, 0x79, 0x19, 0x66, 0xfd, 0xde, 0x9b, 0x77, 0xbf, 0xf4,
0x07, 0xbd, 0xa1, 0xad, 0xeb, 0xa0, 0xf0, 0xc1, 0x9a, 0x2d, 0x1f, 0xde, 0xe6, 0x45, 0x14, 0x46,
0xb3, 0x24, 0xe8, 0x7d, 0xb7, 0xfa, 0xb6, 0xf7, 0x21, 0xac, 0x67, 0x5a, 0x19, 0x42, 0x8a, 0x79,
0x7f, 0xf0, 0x83, 0xa1, 0xf3, 0x7f, 0x63, 0x21, 0x5c, 0x9e, 0x5c, 0x29, 0x9f, 0x3e, 0xbc, 0x7f,
0x5b, 0xd7, 0x8b, 0x8f, 0x09, 0xe4, 0xa9, 0x6a, 0x70, 0x0e, 0xb5, 0x22, 0x2f, 0x11, 0x40, 0x37,
0x15, 0xe1, 0x8b, 0xf8, 0xc9, 0xcf, 0x93, 0xa0, 0x61, 0x04, 0x26, 0xe9, 0xb4, 0x5f, 0xcf, 0xd2,
0x4a, 0xe3, 0x6b, 0x84, 0x0f, 0x04, 0xd6, 0x7a, 0x9a, 0x76, 0x2d, 0xab, 0x20, 0x60, 0x90, 0xb2,
0xb5, 0xb6, 0x5a, 0x14, 0x79, 0x95, 0x80, 0xd9, 0x37, 0x41, 0xbe, 0xcc, 0xb2, 0xc1, 0x6d, 0xac,
0x1d, 0x4d, 0xb5, 0xd3, 0x97, 0xda, 0x25, 0xc1, 0x11, 0x6c, 0xae, 0x6a, 0xf9, 0x45, 0xf5, 0xea,
0xe6, 0x2c, 0x3c, 0x3f, 0x06, 0x1e, 0xfd, 0x5e, 0x18, 0xf5, 0x06, 0x7f, 0xe8, 0x7f, 0x6a, 0xd1,
0x2c, 0xcd, 0xe2, 0xe3, 0x22, 0x4e, 0x2a, 0x1a, 0xe5, 0xf8, 0xc2, 0x51, 0x1d, 0x8b, 0x43, 0x3f,
0xee, 0x76, 0x68, 0x94, 0x6d, 0x73, 0xe8, 0x9b, 0xaf, 0x38, 0xd4, 0x78, 0xee, 0xd0, 0x57, 0x5f,
0x71, 0x28, 0x7b, 0xfc, 0xd0, 0x07, 0xcc, 0xfd, 0xc8, 0x49, 0x57, 0x97, 0x4f, 0xea, 0x7c, 0x18,
0xe8, 0x0d, 0x42, 0xbf, 0xed, 0x26, 0xe2, 0x33, 0x07, 0x8f, 0x29, 0xe1, 0x50, 0x3e, 0xb9, 0x4b,
0x90, 0x74, 0x6e, 0x57, 0xe9, 0xe7, 0x0e, 0xac, 0xce, 0x3e, 0x21, 0xc2, 0x28, 0x1c, 0x92, 0xf8,
0x5d, 0x1e, 0x27, 0xd7, 0xdb, 0xb2, 0x9c, 0x5e, 0x3f, 0xcd, 0x12, 0x47, 0xbf, 0x3e, 0xf9, 0xca,
0xa3, 0x17, 0xdb, 0xb8, 0xc6, 0xa7, 0xdd, 0x4c, 0x54, 0x3d, 0x23, 0x2f, 0x3f, 0xf4, 0xdd, 0x8e,
0x87, 0xa6, 0xcf, 0x1c, 0x4a, 0x05, 0x62, 0x5b, 0xe7, 0xc8, 0x65, 0x40, 0xa4, 0x79, 0x9e, 0x94,
0x6f, 0xcf, 0x3e, 0xbc, 0x1f, 0x7c, 0x13, 0xe8, 0x3f, 0x12, 0x6a, 0x23, 0x51, 0x2b, 0xe0, 0x67,
0xab, 0xea, 0x20, 0xfd, 0xe4, 0xf8, 0x08, 0xac, 0x93, 0xe8, 0x22, 0x89, 0xb7, 0x67, 0x33, 0x7d,
0x86, 0x8d, 0xb0, 0xee, 0xf1, 0x6e, 0x86, 0xc8, 0xe3, 0x6d, 0x20, 0x3b, 0xdb, 0xf1, 0xd0, 0xfa,
0xb9, 0x43, 0x9b, 0x62, 0x2e, 0x37, 0x23, 0x0d, 0x22, 0xcb, 0x53, 0xc5, 0x68, 0xca, 0xce, 0xaa,
0x62, 0x0d, 0xb7, 0x0d, 0xf6, 0xf0, 0x49, 0x9e, 0x83, 0x6d, 0x99, 0x4e, 0x76, 0x62, 0x3a, 0xf9,
0x67, 0x98, 0x46, 0x3b, 0x31, 0x7d, 0x3a, 0xab, 0x6f, 0xcd, 0x34, 0xde, 0x89, 0xe9, 0xd3, 0x7e,
0xb2, 0x35, 0xd3, 0x6a, 0x27, 0xa6, 0xd5, 0x3f, 0xc3, 0xb4, 0xde, 0x89, 0xe9, 0xd3, 0xce, 0xbb,
0x3d, 0xa6, 0xaf, 0xf3, 0xed, 0x79, 0x4e, 0x9f, 0xe1, 0xb9, 0x6e, 0x73, 0x16, 0xd4, 0xa0, 0xa3,
0xcb, 0xd9, 0x36, 0x6d, 0xcc, 0x9f, 0x01, 0x0e, 0x5d, 0xd1, 0xaa, 0x73, 0x15, 0x79, 0x64, 0xd5,
0xa9, 0xca, 0x2f, 0x5b, 0x27, 0xa8, 0xf2, 0xa1, 0x04, 0x65, 0x8c, 0x9a, 0x83, 0xf6, 0xb6, 0x3f,
0xa9, 0x7a, 0xe8, 0x24, 0x86, 0x93, 0xe2, 0xee, 0x8e, 0x23, 0xb4, 0xd8, 0x95, 0xd8, 0x03, 0xc5,
0xa2, 0x8d, 0x5d, 0xdb, 0xa6, 0xab, 0x78, 0x93, 0xdd, 0xf8, 0x5f, 0xbf, 0xa2, 0x5b, 0xc4, 0xbf,
0x8d, 0xd4, 0x4d, 0x47, 0xed, 0xa1, 0x5d, 0x7c, 0x77, 0xd6, 0xbb, 0x43, 0xdf, 0xb7, 0x48, 0x72,
0xb4, 0x99, 0x3f, 0x9f, 0xf5, 0x86, 0x3d, 0xf4, 0xad, 0xbd, 0x3d, 0x4e, 0x96, 0xad, 0xe4, 0x90,
0x12, 0x33, 0x75, 0x87, 0x55, 0x92, 0xc7, 0x7d, 0xde, 0xc3, 0x35, 0x37, 0x90, 0x56, 0x1b, 0x7a,
0xd4, 0x5f, 0x1f, 0xfa, 0x12, 0xdd, 0x67, 0xab, 0xc7, 0x1b, 0x37, 0xf3, 0x1f, 0xe5, 0xfc, 0xc7,
0x8d, 0xf9, 0x37, 0x72, 0xfe, 0xcd, 0xc6, 0xfc, 0x2b, 0x39, 0x2f, 0x9b, 0x25, 0x52, 0x01, 0x7d,
0xc3, 0x9a, 0xcf, 0x6f, 0x92, 0x2e, 0x3b, 0x95, 0x3b, 0xd2, 0xf5, 0x5e, 0x7f, 0xfc, 0xa9, 0x25,
0xd8, 0xeb, 0x4f, 0x62, 0xc7, 0x66, 0xc3, 0xb1, 0xe2, 0x78, 0x2a, 0x17, 0x34, 0x95, 0x7d, 0x45,
0x78, 0x27, 0x09, 0x4d, 0x75, 0x1e, 0x3f, 0xc8, 0x2b, 0xa9, 0x3f, 0xbe, 0x79, 0xd5, 0xdc, 0x00,
0x86, 0xd9, 0x30, 0x1f, 0x7e, 0x19, 0xce, 0x87, 0xc9, 0x30, 0x1a, 0x2e, 0x79, 0xe3, 0x7d, 0x11,
0x88, 0x22, 0xf4, 0x56, 0x1c, 0x32, 0xbc, 0x96, 0xe3, 0x53, 0x39, 0x2e, 0x02, 0x66, 0xdb, 0xe3,
0x2f, 0x01, 0x6f, 0xdd, 0xa7, 0x59, 0x81, 0xeb, 0xc1, 0xc5, 0x0f, 0xce, 0x60, 0x3c, 0x0f, 0xf0,
0xa1, 0x7e, 0x19, 0x27, 0x41, 0xf1, 0x43, 0xdf, 0x50, 0xaf, 0x07, 0xe3, 0x48, 0x7c, 0x9b, 0xff,
0x80, 0xef, 0x4b, 0xf1, 0x9d, 0x86, 0x03, 0x1a, 0x57, 0x57, 0x69, 0x1d, 0xcd, 0xfa, 0x5f, 0xbe,
0x75, 0x06, 0xb7, 0x51, 0x58, 0x25, 0x8a, 0x3e, 0x0a, 0x83, 0x62, 0x98, 0x05, 0xcb, 0x61, 0x1e,
0x24, 0xe3, 0x09, 0xfa, 0xf7, 0x8b, 0x31, 0x27, 0x18, 0x20, 0x44, 0x20, 0x14, 0x9b, 0x04, 0x06,
0x42, 0x22, 0x09, 0xcb, 0x36, 0xc1, 0x94, 0x84, 0x08, 0x84, 0xa2, 0x4d, 0xb0, 0x40, 0x58, 0x82,
0x90, 0x6c, 0x12, 0x6c, 0xc9, 0x9c, 0x08, 0xd1, 0x5d, 0xb7, 0x4f, 0x0f, 0x37, 0x5a, 0xec, 0x6c,
0xa3, 0x3b, 0xce, 0xc7, 0xe4, 0x57, 0x2d, 0x03, 0x9f, 0xb4, 0xc1, 0x3c, 0x11, 0x90, 0x6c, 0xb6,
0x78, 0x9b, 0xa8, 0x20, 0xd3, 0x84, 0xfc, 0x42, 0x82, 0x0b, 0xf0, 0xe0, 0xf6, 0x28, 0x2b, 0xaa,
0xe4, 0x34, 0xa9, 0xeb, 0x34, 0x3f, 0xaf, 0xb0, 0x8c, 0xae, 0xa5, 0xbd, 0x2a, 0x7b, 0x49, 0xb7,
0x3c, 0x9e, 0xfb, 0xe2, 0xb4, 0x5a, 0x64, 0xe1, 0x4d, 0xd0, 0xcb, 0x8b, 0x3c, 0xe9, 0x8d, 0x05,
0xbd, 0x3c, 0x9f, 0x3c, 0xbd, 0x60, 0x56, 0x3d, 0x4d, 0xcf, 0x7e, 0x7b, 0x92, 0x5e, 0x67, 0x9f,
0x9e, 0xa1, 0x9f, 0x3c, 0x43, 0x3f, 0x7e, 0x8c, 0x0e, 0xc5, 0xc3, 0x03, 0xc6, 0x0d, 0xc0, 0x83,
0xe7, 0x71, 0x79, 0x26, 0x59, 0x11, 0x5d, 0xf4, 0x9e, 0xb0, 0x88, 0x5c, 0x20, 0xfd, 0x2b, 0x5c,
0x79, 0xd7, 0x13, 0x36, 0x12, 0x5b, 0xc6, 0xfc, 0xe5, 0x40, 0xc7, 0xf1, 0x1e, 0x37, 0xdb, 0x7a,
0x8b, 0xd1, 0x75, 0xc9, 0xc7, 0x2d, 0x21, 0xb7, 0x74, 0xfc, 0xf4, 0x71, 0xbb, 0xde, 0x5f, 0x6d,
0x8d, 0x1e, 0xb7, 0xa2, 0x54, 0x3a, 0xba, 0x84, 0xb7, 0xce, 0xe3, 0x89, 0x24, 0x4f, 0xd3, 0x2c,
0x0b, 0xfa, 0xd9, 0xfc, 0x50, 0x1f, 0xfc, 0x18, 0x1e, 0x8d, 0xe2, 0xa3, 0xb5, 0xbf, 0xc1, 0x0e,
0x6c, 0x96, 0x5c, 0xf7, 0x29, 0xec, 0x43, 0x91, 0x0b, 0x26, 0x41, 0xf8, 0x77, 0x3f, 0x3a, 0x38,
0xf0, 0x06, 0x7f, 0xf7, 0x93, 0x83, 0x03, 0xc3, 0xa1, 0x0c, 0x4a, 0xef, 0x41, 0x7a, 0x2f, 0x7a,
0x7b, 0x7d, 0xc3, 0x71, 0x5d, 0x97, 0x19, 0xce, 0xde, 0x64, 0xa0, 0xd5, 0xc5, 0x69, 0x5d, 0xc2,
0x35, 0x31, 0x09, 0x49, 0xb2, 0x34, 0x4a, 0xfa, 0x46, 0xcb, 0x97, 0x33, 0x90, 0xca, 0x30, 0x6e,
0xce, 0x96, 0x87, 0x60, 0x36, 0x09, 0x4b, 0x95, 0x28, 0x29, 0x0a, 0x42, 0xbf, 0xb7, 0x37, 0x39,
0xda, 0x43, 0x2a, 0xdf, 0xeb, 0x8a, 0xb2, 0xd7, 0x1b, 0xb4, 0x32, 0x36, 0x25, 0x30, 0x74, 0xf0,
0x93, 0x22, 0xbe, 0x91, 0x3a, 0x4d, 0xc2, 0xe8, 0xe2, 0xbc, 0x2c, 0x96, 0x79, 0x1c, 0x34, 0x7c,
0x5a, 0xc1, 0x3a, 0x6c, 0x05, 0xea, 0xb0, 0x15, 0xa4, 0xfc, 0x5d, 0xc5, 0xdb, 0xd3, 0xed, 0x96,
0x4e, 0xaf, 0xbb, 0x06, 0xe4, 0xe1, 0xbe, 0x99, 0x8d, 0x57, 0x36, 0xc5, 0xdd, 0xa5, 0xbb, 0x1c,
0x77, 0x99, 0x15, 0xa9, 0x7e, 0x94, 0x24, 0xfd, 0x53, 0x94, 0xea, 0xae, 0x93, 0x2e, 0x90, 0x58,
0x8d, 0x15, 0xc6, 0x7c, 0x27, 0x36, 0xac, 0x48, 0xec, 0x1e, 0xe9, 0xbe, 0xcf, 0x3e, 0x70, 0x44,
0xb8, 0xed, 0x11, 0xec, 0x6b, 0xa4, 0x08, 0x8f, 0x1e, 0x70, 0xec, 0xdd, 0xa4, 0x08, 0x8f, 0xee,
0xea, 0xf3, 0xae, 0xbd, 0x5a, 0xf5, 0x79, 0x6d, 0xef, 0xe9, 0xe2, 0xd3, 0x86, 0xfb, 0xf7, 0x1f,
0xc1, 0x47, 0x86, 0xc5, 0x48, 0x26, 0x99, 0x69, 0x31, 0xdd, 0x3c, 0xfd, 0xde, 0x2e, 0xc7, 0x5a,
0xb1, 0x99, 0x97, 0xcf, 0xad, 0x3e, 0x30, 0xee, 0xc5, 0xd5, 0xd9, 0xf9, 0x99, 0x78, 0x09, 0xd5,
0x15, 0x7d, 0x55, 0x0f, 0xce, 0x02, 0xbd, 0x37, 0x6e, 0xbf, 0x5a, 0xd2, 0xc5, 0xab, 0x86, 0x35,
0x9d, 0xf5, 0x1e, 0x6c, 0x11, 0x4e, 0xaf, 0x5e, 0x7f, 0xea, 0xaf, 0x22, 0xf5, 0x01, 0x61, 0xf6,
0x42, 0xca, 0xa2, 0x93, 0x20, 0x50, 0x8d, 0xbf, 0xff, 0xc6, 0x87, 0xeb, 0x37, 0xa1, 0xf7, 0xe0,
0xdb, 0x8b, 0x09, 0x5f, 0x7d, 0x00, 0xd1, 0x1e, 0xa2, 0xea, 0x77, 0x44, 0x3d, 0x74, 0xbd, 0x87,
0xc9, 0x8e, 0x7d, 0x47, 0x2d, 0x4b, 0x5b, 0xed, 0xb7, 0xa7, 0xaf, 0x84, 0xe2, 0xd1, 0x25, 0x25,
0x72, 0x7c, 0x0c, 0x7e, 0xe4, 0x6f, 0x09, 0x47, 0x78, 0x22, 0x3b, 0x70, 0x2d, 0xf1, 0x35, 0x9b,
0xa3, 0xd3, 0x6b, 0x69, 0x45, 0x51, 0x70, 0x72, 0x4a, 0x9a, 0xf1, 0x30, 0x7b, 0xfd, 0x5a, 0xda,
0x65, 0xd5, 0x77, 0x77, 0xa6, 0x07, 0x42, 0xcb, 0xce, 0x1c, 0x50, 0xd8, 0xd8, 0x6b, 0xdc, 0x6d,
0xae, 0x39, 0x64, 0xf6, 0xe6, 0x22, 0x66, 0xb7, 0xe4, 0x38, 0x79, 0x59, 0xaf, 0x8a, 0xb6, 0xd8,
0xf9, 0x6a, 0xf5, 0xc6, 0x00, 0x7e, 0xf4, 0xdd, 0xc9, 0xcb, 0xc0, 0x80, 0x1f, 0xd1, 0x27, 0xf0,
0x6b, 0xaf, 0x3b, 0xea, 0xae, 0x3b, 0x92, 0xeb, 0x8e, 0x36, 0xd7, 0xbd, 0xee, 0xae, 0xfb, 0x24,
0xd7, 0x7d, 0xc2, 0xba, 0x96, 0x18, 0xa7, 0x00, 0x1d, 0xa6, 0xe0, 0xe2, 0xf0, 0xa2, 0xb8, 0xf6,
0x9c, 0x93, 0xd3, 0xa0, 0x39, 0xb1, 0xa3, 0x48, 0xd7, 0x7f, 0x4e, 0xde, 0x3f, 0xb2, 0x6a, 0xc3,
0x9d, 0x7e, 0x41, 0x33, 0xbd, 0x6e, 0x30, 0x6e, 0xe9, 0xb5, 0x3b, 0x7f, 0x8f, 0x5e, 0x75, 0x33,
0x16, 0xc2, 0x35, 0x8a, 0x5f, 0x3d, 0x5c, 0xb3, 0xab, 0x7a, 0xba, 0x49, 0x48, 0x73, 0xca, 0xf2,
0xbc, 0x9c, 0x57, 0x93, 0x29, 0x1d, 0x8b, 0x25, 0x65, 0x14, 0xf4, 0xf6, 0x2b, 0xc9, 0x0b, 0x85,
0x79, 0x32, 0x95, 0x2f, 0xd5, 0xd6, 0xcd, 0x4f, 0xb7, 0xdd, 0xb9, 0x5d, 0xff, 0x08, 0xb0, 0x21,
0x4e, 0xfc, 0xa0, 0x38, 0x0d, 0xd7, 0x07, 0x04, 0xe2, 0x92, 0xb6, 0xdd, 0xf4, 0xb4, 0xff, 0x78,
0x97, 0xc5, 0x0d, 0xd9, 0x35, 0xcc, 0x5d, 0x7b, 0xef, 0x31, 0xbd, 0xef, 0xa7, 0x37, 0x55, 0xdf,
0xe0, 0x41, 0x3a, 0x52, 0x3a, 0x5f, 0x59, 0xfe, 0x18, 0x96, 0xdf, 0xeb, 0xbc, 0x13, 0x5a, 0xf5,
0xe5, 0xc7, 0x67, 0x2b, 0xd2, 0x59, 0x87, 0xd4, 0xdf, 0x78, 0x39, 0x45, 0xae, 0x71, 0xfc, 0x5a,
0xb8, 0x06, 0x3e, 0xe1, 0x1a, 0x1d, 0x70, 0xc1, 0x42, 0x7f, 0x38, 0x3b, 0x40, 0x38, 0x92, 0x4d,
0xdc, 0xfb, 0xf6, 0xb8, 0x6c, 0xfc, 0xfb, 0xa1, 0xd9, 0xcc, 0x22, 0xa4, 0x1f, 0x2e, 0x39, 0xab,
0x5b, 0xc5, 0x71, 0xa0, 0x7f, 0xf7, 0xf1, 0x98, 0xfc, 0xb6, 0x53, 0x4f, 0x36, 0xe9, 0x46, 0xaf,
0x5b, 0x2c, 0x5a, 0x74, 0xe3, 0xfe, 0x7e, 0xf3, 0x1e, 0xdd, 0x78, 0x58, 0x03, 0x51, 0x9f, 0x45,
0x7e, 0x4b, 0x86, 0x17, 0xb8, 0x93, 0x84, 0x41, 0x58, 0x9e, 0xf3, 0x7b, 0x3f, 0x5d, 0xa1, 0xf7,
0x71, 0xe9, 0x18, 0xa6, 0xad, 0x29, 0x43, 0x4c, 0x65, 0xad, 0x29, 0x26, 0xa6, 0xa6, 0x43, 0xba,
0x11, 0xf0, 0xeb, 0xc9, 0x3c, 0xbc, 0xee, 0x87, 0xc3, 0x74, 0x98, 0x0d, 0x86, 0xf3, 0x20, 0x57,
0xc5, 0x5c, 0x9a, 0x37, 0x73, 0xd1, 0xfa, 0x97, 0x83, 0x49, 0x93, 0x2d, 0xfb, 0xb9, 0x3a, 0x19,
0xec, 0x3b, 0xfb, 0xf3, 0x3d, 0x63, 0x9f, 0xf1, 0x1b, 0xe9, 0x3c, 0xa0, 0xf7, 0xcd, 0xd3, 0xa0,
0x68, 0x92, 0x75, 0x11, 0xcc, 0xf7, 0x73, 0xdc, 0x73, 0x22, 0xc4, 0xe7, 0xf8, 0x02, 0x1f, 0xe9,
0x00, 0xf7, 0xa1, 0xa8, 0x9f, 0x89, 0x98, 0x0d, 0x82, 0x20, 0xa7, 0xf5, 0x5f, 0xd4, 0x0b, 0xb1,
0x1e, 0x93, 0x69, 0x33, 0xd9, 0x37, 0xf6, 0xcd, 0xc1, 0x5e, 0xa2, 0x7e, 0x59, 0x91, 0xb2, 0x15,
0x89, 0x11, 0xe9, 0x42, 0x4d, 0x70, 0x13, 0xa6, 0x8b, 0x30, 0x25, 0xe7, 0xe9, 0x1e, 0xb2, 0x59,
0xb3, 0x72, 0x7a, 0x88, 0x54, 0x37, 0x55, 0x31, 0xc3, 0x17, 0x14, 0x87, 0xab, 0xd7, 0xe0, 0xf2,
0xae, 0x16, 0x4c, 0xef, 0x3a, 0x77, 0xb5, 0xa0, 0x68, 0x05, 0x19, 0x4f, 0xb2, 0xf7, 0xda, 0xe2,
0x15, 0x3c, 0xbf, 0x3d, 0x86, 0xfb, 0xab, 0xc7, 0x00, 0xff, 0xb8, 0xe1, 0x0b, 0x2d, 0xac, 0x4f,
0x36, 0x3a, 0xd7, 0x15, 0xe1, 0xa8, 0xd7, 0xbd, 0x76, 0xb5, 0x0e, 0x83, 0x6b, 0x6f, 0xf8, 0xc4,
0xf2, 0xe8, 0xf7, 0x7e, 0x93, 0x0c, 0x3b, 0x39, 0xf7, 0xf7, 0x76, 0xc0, 0x1c, 0xfd, 0x2e, 0x02,
0x06, 0x9f, 0xab, 0x9c, 0x8b, 0x44, 0x68, 0x88, 0x8b, 0xd7, 0x89, 0xb1, 0x11, 0x8c, 0x27, 0x4c,
0x12, 0xd8, 0x26, 0xe1, 0x4c, 0x12, 0x9a, 0x18, 0xdd, 0x90, 0xe6, 0x63, 0xeb, 0x5a, 0x77, 0x42,
0x91, 0x28, 0x17, 0x8c, 0x0f, 0xf6, 0xe5, 0x0f, 0xa7, 0xca, 0x01, 0x4f, 0x3f, 0x87, 0xa3, 0xb2,
0x28, 0xea, 0x5b, 0xf1, 0x42, 0x75, 0xf4, 0x22, 0xf6, 0x27, 0xa6, 0xa1, 0x8f, 0xc5, 0xab, 0xce,
0xd1, 0x0b, 0x7d, 0x62, 0x46, 0x76, 0x3c, 0x16, 0x2f, 0x21, 0x47, 0x2f, 0x8c, 0x98, 0xb9, 0xa6,
0x31, 0x16, 0xaf, 0x07, 0x47, 0x2f, 0x4c, 0xe6, 0x45, 0x11, 0x0d, 0x2b, 0xb1, 0x58, 0xa7, 0x8d,
0x75, 0x97, 0x14, 0xbd, 0xce, 0x47, 0xbf, 0x26, 0x65, 0x1c, 0xe6, 0xe1, 0x9d, 0x16, 0xd5, 0x65,
0xf6, 0x79, 0x52, 0x5c, 0xdf, 0xce, 0x11, 0x07, 0x69, 0x3e, 0x0a, 0x97, 0x75, 0x31, 0xbe, 0x4a,
0xe3, 0x7a, 0x36, 0xf2, 0xf4, 0xcb, 0xab, 0xf1, 0xba, 0x5f, 0x16, 0xbf, 0x14, 0x8e, 0x10, 0x60,
0x7d, 0xc1, 0x7c, 0x30, 0x5e, 0x14, 0x55, 0x4a, 0xba, 0x8d, 0xc2, 0x49, 0x55, 0x64, 0xcb, 0x3a,
0x19, 0xd7, 0xc5, 0x62, 0x64, 0xdb, 0xdf, 0x8e, 0xb3, 0x64, 0x5a, 0x8f, 0x6c, 0xfd, 0xdb, 0x71,
0x5d, 0x86, 0x79, 0x35, 0x2d, 0xca, 0xf9, 0x88, 0x7f, 0xcb, 0x90, 0x3a, 0xfa, 0x2a, 0x08, 0x43,
0x7a, 0xa0, 0x57, 0x4e, 0xb3, 0x3a, 0x29, 0x47, 0x71, 0x59, 0x2c, 0xd4, 0x6a, 0x16, 0xc6, 0xc5,
0x15, 0xa8, 0x8b, 0x6b, 0x85, 0x3f, 0xe8, 0xbf, 0x60, 0x47, 0xda, 0x0c, 0x06, 0x77, 0x5a, 0x15,
0x57, 0xb7, 0x42, 0x38, 0x43, 0xc7, 0xe1, 0xb3, 0x24, 0x3d, 0x9f, 0xd5, 0x23, 0x83, 0x5d, 0xce,
0xc6, 0x42, 0x7e, 0x95, 0x04, 0xc0, 0x10, 0x4b, 0x33, 0xa9, 0x92, 0xca, 0x65, 0xe1, 0x7a, 0xc9,
0x89, 0x92, 0xef, 0x6a, 0xcf, 0xc8, 0x5d, 0x52, 0x71, 0xd7, 0xbd, 0xbc, 0xba, 0x7b, 0x81, 0xdb,
0xe1, 0x6d, 0x8b, 0xac, 0x83, 0xbc, 0x36, 0xc6, 0x68, 0xf3, 0x3a, 0x52, 0x17, 0x0a, 0x3f, 0x76,
0x38, 0xc9, 0xb0, 0x68, 0x78, 0x93, 0x64, 0x59, 0x71, 0x35, 0xa0, 0x53, 0x3e, 0xde, 0x6e, 0xbf,
0xad, 0x84, 0x4b, 0xd2, 0x9e, 0x37, 0x3b, 0xec, 0x39, 0x2f, 0x93, 0x24, 0xe7, 0xbb, 0x5e, 0xed,
0xb0, 0x6b, 0x42, 0xdd, 0x10, 0x6d, 0xfa, 0x6d, 0x87, 0x4d, 0x57, 0xb3, 0xb4, 0x16, 0xbb, 0xde,
0x6e, 0xb5, 0x0b, 0xea, 0x0c, 0x8b, 0x92, 0x7e, 0x62, 0x95, 0x06, 0x11, 0xc2, 0x0e, 0xa3, 0x9b,
0x30, 0xe7, 0x12, 0x0c, 0x2f, 0xd3, 0x22, 0x4b, 0xea, 0x95, 0xde, 0xa7, 0x5b, 0x1d, 0x8b, 0x43,
0x6e, 0x5a, 0x6a, 0x1f, 0xef, 0xa0, 0x01, 0x32, 0xf4, 0xbf, 0x97, 0x45, 0x5a, 0x09, 0x2d, 0xce,
0x76, 0xd8, 0xd9, 0x20, 0x8a, 0xdb, 0x6b, 0x91, 0x55, 0xff, 0xb1, 0x6f, 0x7d, 0xfa, 0x2a, 0xb3,
0xbf, 0xdb, 0xd5, 0x97, 0xe8, 0xe6, 0x7b, 0x3b, 0x2d, 0xf2, 0x5a, 0x9d, 0x86, 0xf3, 0x34, 0xbb,
0x69, 0xc2, 0xf7, 0x75, 0x3e, 0x18, 0xbe, 0x4d, 0xb2, 0xcb, 0xa4, 0x4e, 0xa3, 0x70, 0x58, 0x21,
0x32, 0xd5, 0x2a, 0x29, 0xd3, 0xe9, 0xb8, 0x4e, 0xae, 0x6b, 0x35, 0xcc, 0xd2, 0xf3, 0x7c, 0x14,
0x25, 0xf4, 0xe3, 0xfc, 0x53, 0x4e, 0x2f, 0x0e, 0xa3, 0xbc, 0x34, 0x10, 0x4c, 0x07, 0xab, 0x90,
0xa4, 0xf0, 0x94, 0x29, 0x45, 0x6f, 0x27, 0x91, 0x32, 0x59, 0x24, 0x61, 0x3d, 0xca, 0x0b, 0xf9,
0xad, 0x4d, 0x0b, 0xeb, 0x3a, 0x8c, 0x66, 0x54, 0x8b, 0x47, 0xd3, 0xf4, 0x3a, 0x89, 0xc7, 0xed,
0x8c, 0x43, 0x49, 0x6c, 0x70, 0x47, 0x7f, 0x47, 0x70, 0xdb, 0xe2, 0x81, 0x3a, 0x56, 0x86, 0xf3,
0xe4, 0x56, 0x76, 0x6c, 0x23, 0x6a, 0xd8, 0xc6, 0x93, 0xa2, 0x8c, 0x91, 0x54, 0xf4, 0xdd, 0xb2,
0xcb, 0xf8, 0x91, 0x0c, 0x28, 0xb9, 0x39, 0x94, 0x00, 0xfe, 0xe3, 0x84, 0x77, 0x57, 0x5d, 0x9e,
0xdf, 0x52, 0x2f, 0x2a, 0xb5, 0x8a, 0x79, 0x1e, 0x95, 0x29, 0x8d, 0xad, 0xb9, 0x19, 0xfa, 0x25,
0xfa, 0x8c, 0x1d, 0xd3, 0x63, 0x9a, 0x2f, 0x96, 0xf5, 0xed, 0x6e, 0x4a, 0x3f, 0xe2, 0x1b, 0x6b,
0x8f, 0xb8, 0x9b, 0x2c, 0xeb, 0xba, 0xc8, 0xdb, 0x8e, 0xd7, 0x82, 0xbd, 0x03, 0x91, 0x50, 0x46,
0x5a, 0x5f, 0xb3, 0xa3, 0x99, 0x02, 0x23, 0xa5, 0xb1, 0xd2, 0x5e, 0x2f, 0x83, 0x62, 0x52, 0xe0,
0xd0, 0xf9, 0xc8, 0x80, 0x4d, 0xff, 0x69, 0x79, 0xc5, 0x55, 0x73, 0x1b, 0x79, 0xeb, 0x2d, 0xe4,
0xfd, 0xa7, 0xa5, 0xe3, 0x18, 0xfd, 0xc1, 0xff, 0xd6, 0x2a, 0x5f, 0xce, 0x27, 0x49, 0xf9, 0xe7,
0x3f, 0x25, 0xa9, 0x70, 0x22, 0x33, 0x99, 0xb7, 0x79, 0xf0, 0xe4, 0xfb, 0xe7, 0xad, 0x7a, 0x95,
0x4c, 0x2e, 0x52, 0x04, 0xf6, 0x02, 0x31, 0x87, 0xb9, 0x28, 0x11, 0x91, 0x22, 0x5d, 0x5e, 0xb5,
0xa0, 0x88, 0x7e, 0x7f, 0xdf, 0x68, 0x5a, 0x44, 0xcb, 0xea, 0xb6, 0x58, 0xd6, 0x14, 0xf9, 0xa3,
0x87, 0x56, 0x8c, 0x9a, 0xa3, 0x2b, 0xc8, 0x93, 0x94, 0x6a, 0xb9, 0xcc, 0xf3, 0x70, 0x92, 0x25,
0x2a, 0xbc, 0x3f, 0xba, 0x78, 0xac, 0x58, 0x47, 0xcb, 0xb2, 0x82, 0x72, 0x8b, 0x22, 0xdd, 0x4c,
0x31, 0x2d, 0x8d, 0xb6, 0xe0, 0x56, 0xcf, 0x60, 0xc3, 0x1d, 0x3d, 0x7e, 0x1d, 0x61, 0x4d, 0x4e,
0x36, 0x36, 0x6a, 0xbb, 0x58, 0x1e, 0x0a, 0x14, 0xba, 0x92, 0x3e, 0x6d, 0x48, 0x9e, 0xeb, 0xe1,
0xd3, 0x0f, 0x4a, 0x3e, 0x2f, 0xfe, 0x52, 0xf9, 0xe0, 0x7f, 0xcb, 0x34, 0x2d, 0x06, 0xff, 0x6d,
0xb3, 0x6c, 0xa3, 0x7e, 0xf5, 0xb5, 0x7a, 0xf3, 0x3c, 0xba, 0x08, 0x4b, 0xfa, 0x63, 0x42, 0x11,
0x02, 0xb2, 0x15, 0x6d, 0x13, 0xee, 0xcd, 0x3c, 0x26, 0x04, 0x25, 0x5f, 0x15, 0x95, 0x3c, 0x29,
0xdb, 0x81, 0xf7, 0xe2, 0x59, 0xe3, 0xca, 0x9d, 0x4b, 0x00, 0xbf, 0xf3, 0x4e, 0x81, 0xc7, 0xce,
0x56, 0x6d, 0x41, 0x72, 0x87, 0x4b, 0x02, 0xbf, 0x1b, 0xc8, 0x3b, 0x82, 0x92, 0xc6, 0xc1, 0xac,
0xc8, 0x60, 0x8b, 0x2f, 0xc8, 0x2c, 0x7c, 0x86, 0x73, 0xa5, 0xf2, 0xbd, 0x1f, 0x55, 0xd5, 0x61,
0xb3, 0xfe, 0x60, 0x5f, 0xfc, 0xb9, 0x28, 0x35, 0x02, 0x4a, 0x91, 0xf3, 0xdf, 0x8d, 0x57, 0x7f,
0xc5, 0xa7, 0x44, 0xf4, 0x83, 0x6c, 0xf0, 0xf9, 0x33, 0x6a, 0x67, 0x9a, 0x7f, 0xa6, 0xcd, 0x9f,
0x3f, 0xfe, 0xfc, 0xf2, 0xa7, 0xdf, 0x3f, 0x7f, 0x26, 0x46, 0x8b, 0x30, 0x97, 0x2b, 0xe8, 0xd7,
0xda, 0xc3, 0xf7, 0xd8, 0x9b, 0xe6, 0xe7, 0x9a, 0xa6, 0xe1, 0x70, 0xd0, 0x68, 0xc9, 0xe5, 0xb9,
0xc2, 0xf9, 0x04, 0xf7, 0x2b, 0xa3, 0x50, 0x57, 0x6f, 0xb4, 0xd0, 0xc7, 0xc5, 0x65, 0x52, 0x4e,
0x61, 0xf9, 0xd1, 0x2c, 0x8d, 0xe3, 0x24, 0x57, 0x30, 0xac, 0xb0, 0x23, 0x30, 0x34, 0x43, 0xb9,
0x9e, 0x67, 0x79, 0x15, 0xcc, 0xea, 0x7a, 0x31, 0xda, 0xdf, 0xbf, 0xba, 0xba, 0xd2, 0xae, 0x4c,
0xad, 0x28, 0xcf, 0xf7, 0x19, 0x6e, 0x30, 0xfb, 0xe0, 0x02, 0x5e, 0x71, 0x32, 0xad, 0x0e, 0x95,
0xfd, 0xfd, 0xf7, 0xbc, 0x09, 0xa1, 0xbf, 0x48, 0xad, 0xb4, 0xa8, 0x98, 0xef, 0x4f, 0xd1, 0x08,
0x42, 0x92, 0x9b, 0xf9, 0xa4, 0xc8, 0xc8, 0x2c, 0x59, 0x5e, 0xaa, 0x0b, 0xc2, 0x57, 0x15, 0x97,
0x55, 0xe5, 0x32, 0x4d, 0xae, 0x5e, 0x15, 0xd7, 0x41, 0x4f, 0x57, 0x74, 0xc5, 0xd0, 0x99, 0xc5,
0x1f, 0xbd, 0xc3, 0x03, 0x7a, 0x89, 0xac, 0xc4, 0x41, 0xef, 0x83, 0xe5, 0x39, 0x9a, 0xa5, 0x38,
0x86, 0xa5, 0x59, 0x91, 0x8a, 0xa7, 0x61, 0x7a, 0x8a, 0xae, 0x32, 0x5b, 0x73, 0x54, 0xc3, 0xd0,
0x2c, 0xc7, 0x10, 0xdf, 0xe9, 0x71, 0xa9, 0x5a, 0x8e, 0xae, 0x79, 0x91, 0xde, 0xac, 0xe3, 0x0b,
0x18, 0xa7, 0x29, 0xab, 0x55, 0x15, 0x1f, 0x0a, 0x92, 0xb2, 0x22, 0x5d, 0xca, 0xad, 0x0a, 0xdf,
0xea, 0xab, 0x2d, 0xfa, 0x7a, 0xff, 0x5f, 0x10, 0x6c, 0x9f, 0x24, 0xbb, 0x27, 0x9f, 0xef, 0x32,
0xec, 0x56, 0x21, 0xbc, 0x66, 0xbb, 0x90, 0xcf, 0xf0, 0x7d, 0xd5, 0xf4, 0x35, 0xdf, 0xb2, 0x54,
0xe6, 0x1a, 0x9a, 0x6d, 0xe2, 0x40, 0xa6, 0x59, 0xae, 0x8a, 0x75, 0x36, 0xf3, 0x9a, 0x0f, 0x39,
0x69, 0x38, 0x9e, 0xe6, 0x3b, 0xcd, 0x48, 0x6c, 0xc0, 0x21, 0x9e, 0xa5, 0x99, 0xbe, 0xad, 0x30,
0x47, 0xf3, 0x6c, 0x48, 0xe4, 0xc0, 0x04, 0xae, 0xa7, 0xb8, 0xae, 0xe6, 0xb8, 0x38, 0xd6, 0xc4,
0x2a, 0xdf, 0x52, 0x2c, 0x5f, 0x33, 0x6c, 0x15, 0x34, 0xdf, 0xb0, 0xa1, 0x15, 0x0e, 0x72, 0x49,
0x7a, 0x13, 0x5b, 0x61, 0x4c, 0xdf, 0xd1, 0x1c, 0x66, 0xa9, 0x86, 0xe9, 0x68, 0xb6, 0x6e, 0x28,
0x98, 0xb6, 0x6d, 0xd5, 0xd2, 0x74, 0xd3, 0x52, 0x98, 0xab, 0x79, 0xba, 0xad, 0x98, 0x9a, 0xe3,
0x99, 0x0a, 0x0e, 0xf3, 0xc8, 0x60, 0xae, 0xc6, 0x4c, 0xab, 0x52, 0xc5, 0xa4, 0x58, 0xa1, 0x8a,
0x49, 0xb9, 0x04, 0x3a, 0x92, 0x5e, 0xb6, 0xad, 0x58, 0x86, 0xe6, 0xd8, 0x24, 0x87, 0xab, 0xb9,
0x3a, 0xc3, 0x56, 0x58, 0x60, 0x3d, 0x34, 0x0d, 0x5f, 0xd3, 0x19, 0x23, 0x54, 0x3d, 0x30, 0xd5,
0x61, 0x71, 0x48, 0xca, 0x20, 0x93, 0x69, 0x62, 0x61, 0xfb, 0x59, 0xf1, 0xa7, 0xda, 0xa1, 0xab,
0xfc, 0xc9, 0x91, 0x74, 0x34, 0xc3, 0x73, 0x55, 0x1f, 0xc7, 0xda, 0x30, 0x8d, 0xab, 0x99, 0xae,
0xe4, 0x62, 0xa8, 0x82, 0x09, 0xa9, 0x07, 0xb5, 0x98, 0xd0, 0x4b, 0x65, 0x86, 0xc6, 0x1c, 0xd8,
0xd4, 0xd3, 0x98, 0xe7, 0x49, 0xe1, 0x55, 0xa9, 0x1f, 0xad, 0x60, 0x72, 0xb9, 0x42, 0x2b, 0x7c,
0xb9, 0x5c, 0x68, 0xe7, 0x8a, 0xe5, 0x36, 0xb7, 0xb1, 0x05, 0xcc, 0x4d, 0xcd, 0xe0, 0x92, 0xbb,
0x9a, 0x05, 0xc3, 0xc3, 0xe0, 0x1e, 0x13, 0x36, 0x35, 0xf1, 0x09, 0x9b, 0x5a, 0xbe, 0xaf, 0xd8,
0xf0, 0x1b, 0xc3, 0x51, 0x1c, 0x57, 0x33, 0x0c, 0xb1, 0xd3, 0xb5, 0xf9, 0x16, 0x06, 0x33, 0xc8,
0xa1, 0x00, 0xcb, 0x13, 0x3e, 0x0e, 0x0f, 0x11, 0xbe, 0x41, 0x67, 0x41, 0x01, 0x9d, 0x49, 0xe4,
0x15, 0xe9, 0x2a, 0xc2, 0x39, 0x14, 0xfe, 0xe1, 0x48, 0xe7, 0x50, 0xba, 0xce, 0x21, 0x46, 0x2d,
0x7f, 0xdc, 0x17, 0x41, 0x76, 0xb8, 0x19, 0x6d, 0x59, 0x5a, 0xd5, 0x5b, 0x44, 0x99, 0xef, 0x7b,
0xf0, 0x62, 0x0f, 0xf6, 0x64, 0x33, 0xd5, 0x75, 0xbc, 0xe7, 0x42, 0xad, 0x7a, 0x30, 0xb4, 0x66,
0xb4, 0x53, 0x04, 0x10, 0x18, 0xad, 0xe3, 0xcc, 0x58, 0xc7, 0x59, 0xa5, 0xb6, 0x66, 0x9e, 0x8e,
0x2c, 0x21, 0x93, 0xed, 0x98, 0xff, 0xef, 0x64, 0x32, 0x75, 0xf7, 0x29, 0x99, 0xd8, 0xd7, 0xca,
0xc4, 0xbe, 0x4a, 0x26, 0xd7, 0xa1, 0x48, 0xb2, 0x3d, 0x4a, 0x90, 0x16, 0xd3, 0x4c, 0x8b, 0x12,
0x10, 0x4d, 0xaa, 0x26, 0x32, 0x06, 0xf9, 0x13, 0x7d, 0xa7, 0x47, 0xd5, 0x9a, 0x50, 0x56, 0xb3,
0x8a, 0x38, 0x80, 0x93, 0x94, 0x35, 0xa9, 0x35, 0xb1, 0x5e, 0xf6, 0x97, 0xe0, 0xc6, 0x6c, 0x47,
0x68, 0x0e, 0x8f, 0x17, 0x9a, 0x73, 0x05, 0x3c, 0x7b, 0x2d, 0x69, 0x25, 0x26, 0xd6, 0x1a, 0xad,
0x1f, 0x6a, 0x8b, 0xa4, 0xb6, 0x26, 0xd6, 0x66, 0x53, 0x9f, 0xd4, 0x55, 0x16, 0x83, 0x07, 0x74,
0x35, 0xd6, 0xba, 0x62, 0x5e, 0x2e, 0x78, 0x50, 0xe9, 0xea, 0x41, 0xa5, 0x91, 0xff, 0xf9, 0x26,
0xff, 0x49, 0xed, 0x6d, 0x83, 0xfd, 0x1f, 0x6a, 0xef, 0xb9, 0xfa, 0xf3, 0xda, 0x3f, 0x8b, 0xb4,
0xb1, 0x25, 0xd2, 0x2b, 0x1f, 0xff, 0x2f, 0xe9, 0xfa, 0x58, 0x1e, 0x9b, 0x17, 0x45, 0xbe, 0x45,
0x1e, 0xb3, 0x99, 0x05, 0xa1, 0x69, 0x96, 0xa4, 0xd6, 0x35, 0xc3, 0xf5, 0x49, 0x6c, 0x24, 0x4d,
0xdf, 0xf1, 0x54, 0xdb, 0x42, 0x06, 0x07, 0xb6, 0x18, 0xea, 0x3e, 0x6a, 0x8b, 0x6d, 0xa2, 0x8a,
0x54, 0xf2, 0x13, 0x45, 0x45, 0x47, 0x11, 0x6d, 0x46, 0x72, 0x11, 0x95, 0x20, 0xc3, 0xd6, 0x18,
0x92, 0x35, 0xca, 0x03, 0xc3, 0xf1, 0xba, 0x41, 0xbc, 0x1d, 0xcd, 0xf7, 0x91, 0xc6, 0x61, 0x19,
0x1b, 0x85, 0xc2, 0x34, 0x79, 0x39, 0xb7, 0x50, 0x75, 0x1d, 0xd4, 0x00, 0x93, 0x57, 0x68, 0x0f,
0xcd, 0x93, 0x4d, 0x35, 0xd9, 0xd7, 0x4c, 0x66, 0xf3, 0x1c, 0x6f, 0x32, 0x53, 0x41, 0x41, 0xd3,
0x6d, 0x9d, 0x2a, 0x92, 0x85, 0xbe, 0x01, 0xb5, 0xc4, 0xb1, 0x54, 0xdb, 0xd6, 0x2c, 0xd2, 0x06,
0xb5, 0xc4, 0x02, 0x1b, 0xb0, 0x66, 0xb0, 0x9a, 0x8f, 0xc2, 0x82, 0x02, 0xa5, 0xf9, 0xa8, 0xdc,
0x4c, 0xe7, 0x2b, 0x74, 0xcd, 0x34, 0x1d, 0xea, 0x08, 0x7c, 0xb4, 0x19, 0xa8, 0x5f, 0x8c, 0x55,
0xa8, 0x84, 0x54, 0xb9, 0x51, 0x24, 0x50, 0xaa, 0x4c, 0x4d, 0x77, 0x50, 0xda, 0x3d, 0x48, 0x89,
0x08, 0x85, 0x58, 0x3a, 0x26, 0xd1, 0x1c, 0x98, 0x68, 0x69, 0x2c, 0x0f, 0xb5, 0xd3, 0x57, 0x0c,
0x58, 0x1c, 0x15, 0x7a, 0x35, 0xf4, 0x4d, 0x01, 0x2e, 0x8a, 0x14, 0x8a, 0x92, 0x8b, 0x09, 0xa8,
0x0b, 0x31, 0x4d, 0x7c, 0x74, 0x9e, 0xa8, 0x70, 0x96, 0x61, 0x91, 0xe5, 0x71, 0xbe, 0xeb, 0x02,
0x4b, 0x1b, 0x27, 0x18, 0xcd, 0x09, 0xe2, 0x40, 0x0f, 0x52, 0x1b, 0xba, 0x8b, 0x5a, 0x0c, 0x13,
0x90, 0xd4, 0x86, 0x89, 0x6e, 0xc2, 0x72, 0x7c, 0x29, 0x94, 0x90, 0x90, 0x5c, 0xc6, 0xa0, 0x9e,
0x01, 0x64, 0x8b, 0xeb, 0x61, 0x49, 0xa5, 0x22, 0x3a, 0xd5, 0x43, 0xde, 0x23, 0x14, 0x5c, 0x93,
0x0a, 0xa5, 0x67, 0xc1, 0x88, 0x3a, 0x74, 0xf0, 0xa4, 0x65, 0x1c, 0x69, 0x28, 0x9b, 0x8c, 0x68,
0x38, 0x8e, 0x62, 0xc1, 0x87, 0xd0, 0x03, 0xc1, 0x04, 0x36, 0xe2, 0x03, 0xea, 0x01, 0x9a, 0xc6,
0xdc, 0x8d, 0xf9, 0x7d, 0x1d, 0x75, 0x13, 0xb6, 0x80, 0x8e, 0xbe, 0x47, 0xfd, 0x99, 0x66, 0x52,
0x69, 0xdf, 0x00, 0x52, 0x8e, 0xff, 0xfa, 0x00, 0x33, 0xa3, 0x83, 0x72, 0x69, 0xbd, 0xe7, 0x53,
0xb2, 0x43, 0x35, 0x76, 0x2d, 0x83, 0xb0, 0xf5, 0x3d, 0xe4, 0x73, 0x70, 0x31, 0x40, 0x66, 0xf0,
0x31, 0x1d, 0xcd, 0x59, 0x33, 0xb6, 0x18, 0x9d, 0x4b, 0x46, 0x62, 0x0e, 0x64, 0x03, 0xc2, 0x8c,
0x2a, 0xb5, 0x0d, 0x19, 0xc9, 0x2b, 0x3a, 0x4f, 0xc3, 0xf3, 0x79, 0x63, 0xa5, 0x23, 0x79, 0xc3,
0x92, 0x86, 0x25, 0xdb, 0x00, 0xbb, 0x39, 0xa5, 0x39, 0x55, 0xb5, 0x31, 0x4d, 0xc2, 0xfa, 0xd4,
0x44, 0xa0, 0xc9, 0x24, 0xbb, 0xa3, 0x3d, 0xf3, 0x39, 0xd1, 0x40, 0xe3, 0x6a, 0x83, 0x65, 0x33,
0xc4, 0xa7, 0x4f, 0x8d, 0x17, 0x22, 0x8b, 0xc1, 0xd6, 0xd8, 0xad, 0x53, 0xa3, 0x88, 0x28, 0x87,
0xe8, 0x26, 0x50, 0x43, 0xe6, 0xa0, 0x92, 0xe4, 0xa3, 0xe3, 0x71, 0x7c, 0xfe, 0xe1, 0xca, 0x49,
0x6a, 0x9e, 0xc8, 0xc1, 0x9d, 0xd5, 0x58, 0x6c, 0xa2, 0x64, 0x0a, 0x11, 0x0d, 0xd3, 0x00, 0xba,
0x20, 0xea, 0x80, 0x12, 0xdd, 0x4c, 0x57, 0x80, 0x2d, 0x82, 0x38, 0x2a, 0xce, 0xb7, 0x88, 0x61,
0x13, 0x50, 0x91, 0x5b, 0xe9, 0x60, 0xe3, 0xda, 0x76, 0x84, 0x08, 0x30, 0x74, 0x0a, 0x63, 0x0b,
0x61, 0xe8, 0xa9, 0x3a, 0xfa, 0x35, 0xa6, 0x3a, 0xe4, 0x6b, 0x2a, 0xb5, 0x5e, 0x2a, 0xd0, 0xf7,
0x5d, 0x9e, 0x59, 0x3c, 0xdd, 0x24, 0x9c, 0x0d, 0xca, 0xdf, 0xc0, 0xc5, 0xa2, 0x56, 0x0f, 0x0e,
0x83, 0xa0, 0x46, 0xe4, 0x33, 0x83, 0x96, 0xf8, 0x0c, 0x49, 0x11, 0x51, 0x81, 0x86, 0xdc, 0x21,
0xdf, 0x64, 0x8c, 0x08, 0x3e, 0x75, 0xc7, 0x94, 0x07, 0x4d, 0x8b, 0xdc, 0x11, 0xce, 0x0c, 0x3b,
0xf8, 0x0c, 0x7e, 0x47, 0xe0, 0xf9, 0xbc, 0x7d, 0x84, 0x9f, 0xc9, 0x11, 0x1c, 0xdd, 0xf7, 0xc9,
0xc2, 0xb6, 0xc3, 0x8d, 0x44, 0x02, 0x90, 0x61, 0xd1, 0xda, 0x59, 0xdd, 0x27, 0xfc, 0x9b, 0x51,
0xde, 0xc4, 0xc9, 0x3a, 0xf9, 0x29, 0x50, 0x5d, 0x6d, 0x97, 0x87, 0xe1, 0xc3, 0x30, 0x4d, 0x85,
0xfa, 0x74, 0xea, 0xee, 0x28, 0x52, 0x15, 0x50, 0x98, 0x14, 0x87, 0x49, 0xd9, 0x90, 0x78, 0x0c,
0x44, 0x15, 0xf9, 0x14, 0xa3, 0xdc, 0x6f, 0xe9, 0x9e, 0xea, 0x91, 0xd7, 0xd8, 0x42, 0x37, 0x43,
0xaa, 0x4a, 0xd1, 0x66, 0x9a, 0x26, 0x1d, 0xeb, 0x7b, 0x3e, 0xa2, 0xcd, 0x87, 0xb3, 0x3a, 0x54,
0xd2, 0x14, 0x83, 0x78, 0x70, 0xcb, 0xe0, 0x83, 0xb7, 0xef, 0xb8, 0xa4, 0x10, 0xde, 0x26, 0x77,
0x73, 0x17, 0xe7, 0x23, 0xe8, 0xe8, 0x6a, 0x20, 0x47, 0xe8, 0xb9, 0x41, 0xab, 0x54, 0x1e, 0xb9,
0x60, 0x67, 0xaa, 0x9c, 0xe0, 0x71, 0x82, 0xa5, 0x47, 0xc4, 0xc4, 0xe5, 0xb1, 0xce, 0x5c, 0x0a,
0x29, 0xe4, 0x58, 0x46, 0x3a, 0x3b, 0xa6, 0xa7, 0x1a, 0x42, 0x21, 0xce, 0xcc, 0x55, 0x24, 0x36,
0x02, 0x29, 0x45, 0x62, 0x23, 0x90, 0x52, 0x3a, 0xe2, 0x23, 0xc0, 0x04, 0x38, 0x1c, 0x29, 0x45,
0xa0, 0xc3, 0x91, 0x52, 0x04, 0x3a, 0xc2, 0x1c, 0x0a, 0x87, 0xc7, 0xe4, 0x50, 0xf1, 0xd6, 0x1d,
0x49, 0x4f, 0xd8, 0x53, 0xe1, 0xd6, 0x6d, 0x06, 0x02, 0x20, 0xd9, 0x76, 0x37, 0x40, 0x29, 0x2d,
0x88, 0xe8, 0xaa, 0x46, 0x20, 0x39, 0xaa, 0xc0, 0x86, 0x03, 0x45, 0x41, 0xb4, 0x06, 0x5a, 0x15,
0x66, 0xe3, 0x38, 0x21, 0x40, 0x00, 0x0d, 0xe3, 0x38, 0x59, 0x14, 0x0c, 0x2d, 0xb7, 0xe1, 0xd8,
0x08, 0xa0, 0x1c, 0x9e, 0x85, 0x28, 0xa2, 0x05, 0x52, 0x4a, 0xc7, 0x0d, 0x0d, 0x92, 0xce, 0xa7,
0x88, 0x04, 0x52, 0x3c, 0xef, 0x11, 0x3a, 0x84, 0x94, 0xd2, 0x98, 0xcb, 0x11, 0x1f, 0x16, 0x99,
0x99, 0x0e, 0x76, 0x75, 0x9e, 0x10, 0x57, 0x48, 0x21, 0xcb, 0x72, 0x08, 0xe4, 0xa8, 0x02, 0x34,
0x02, 0x24, 0x3e, 0x0f, 0xf3, 0x0b, 0x04, 0x23, 0xb0, 0xf3, 0x38, 0x4a, 0x06, 0xe5, 0x5a, 0xc7,
0xb2, 0x05, 0x4a, 0xb6, 0x60, 0xe4, 0x0b, 0x94, 0x7c, 0x45, 0xe2, 0xc2, 0x51, 0x62, 0x0a, 0xe1,
0x62, 0x4b, 0x90, 0xa4, 0xe4, 0xa6, 0xd2, 0x48, 0x6e, 0xf0, 0xe4, 0xc2, 0x41, 0x22, 0x31, 0x51,
0x0a, 0x38, 0x48, 0xba, 0x27, 0x8c, 0xa1, 0x48, 0xa7, 0x25, 0x60, 0x98, 0x40, 0xc9, 0xe1, 0xd6,
0x44, 0x46, 0xe7, 0x38, 0xb9, 0xcd, 0xa8, 0x09, 0xa2, 0x06, 0xa3, 0x36, 0x36, 0xdd, 0x27, 0xc7,
0x87, 0xb2, 0xa4, 0xc0, 0x86, 0x03, 0x25, 0xb7, 0x4b, 0xa0, 0x14, 0x0e, 0x8e, 0x27, 0x90, 0x52,
0x04, 0x38, 0x1c, 0x29, 0x29, 0x8f, 0x25, 0x85, 0xe3, 0xd8, 0x38, 0x12, 0x29, 0xc5, 0x12, 0x59,
0x55, 0x20, 0x25, 0x14, 0x6c, 0x80, 0x52, 0x04, 0x34, 0x22, 0x88, 0x80, 0x93, 0x0f, 0xcb, 0x0a,
0x9c, 0xa4, 0x63, 0x4b, 0xa4, 0x54, 0x89, 0x8d, 0x40, 0x4a, 0x86, 0x87, 0x44, 0x4a, 0xed, 0x20,
0x55, 0x49, 0x7c, 0x70, 0xad, 0x6e, 0xcd, 0xe3, 0x3e, 0x2d, 0x62, 0x48, 0x21, 0xa4, 0x14, 0x11,
0x42, 0x8a, 0x08, 0x21, 0xa5, 0xcd, 0xc9, 0x97, 0xe9, 0x4d, 0x42, 0x25, 0xd3, 0x9b, 0x80, 0x4a,
0xed, 0x88, 0xce, 0xb3, 0x9b, 0x2d, 0x80, 0xb2, 0x55, 0x89, 0x8d, 0x40, 0x4a, 0x6d, 0x19, 0x83,
0xa9, 0x12, 0x1c, 0x8e, 0x94, 0xda, 0xc1, 0x46, 0x6d, 0x19, 0x17, 0xa3, 0x26, 0x88, 0x64, 0xc4,
0xb4, 0xb3, 0x9d, 0x84, 0x48, 0x57, 0x64, 0xa6, 0x13, 0xd8, 0xc8, 0x6c, 0xd7, 0x05, 0x5a, 0xba,
0x34, 0x01, 0xe5, 0x88, 0x04, 0xd7, 0x00, 0xc5, 0xc5, 0x69, 0xc2, 0x5b, 0x62, 0x23, 0x90, 0x52,
0x25, 0x36, 0x02, 0xa9, 0x55, 0x8a, 0x90, 0xc9, 0x9c, 0xf1, 0x73, 0x09, 0x29, 0x47, 0xa6, 0x38,
0x81, 0x94, 0xda, 0x76, 0x6e, 0x57, 0x64, 0x38, 0x4f, 0x22, 0x25, 0x32, 0x9c, 0xdd, 0x4e, 0x64,
0x4d, 0xb4, 0x78, 0x95, 0x4a, 0xef, 0x3c, 0x44, 0xae, 0xeb, 0x84, 0x17, 0x1a, 0x29, 0xdc, 0xd9,
0xa9, 0xad, 0x94, 0x2f, 0x53, 0xb8, 0xc0, 0x50, 0x99, 0x7a, 0x35, 0x48, 0x4a, 0x75, 0x82, 0x0f,
0xfe, 0xfa, 0x80, 0xab, 0x81, 0x6c, 0xd0, 0x6d, 0x7a, 0x43, 0x61, 0xf3, 0xd4, 0x83, 0x9a, 0x89,
0x08, 0x32, 0x99, 0x78, 0x27, 0x85, 0x5e, 0xca, 0x37, 0xd0, 0xbc, 0xa1, 0x38, 0x93, 0xb3, 0xd2,
0xcb, 0x04, 0x17, 0x28, 0x30, 0x72, 0x1c, 0xdc, 0xf8, 0x75, 0xfe, 0xea, 0xc2, 0x73, 0xf9, 0xab,
0x04, 0xe6, 0x8b, 0xda, 0x63, 0x39, 0xaa, 0x8d, 0xfe, 0x84, 0xa7, 0x42, 0xdd, 0xa5, 0xe5, 0x16,
0x8a, 0x34, 0xb5, 0x77, 0xae, 0x2d, 0x07, 0xa8, 0xf2, 0x86, 0xc7, 0xc4, 0x5b, 0x23, 0xc7, 0x47,
0xa3, 0x43, 0x0d, 0xa1, 0xc5, 0xbb, 0x59, 0xea, 0x97, 0x4c, 0xaa, 0xdb, 0xe2, 0xbb, 0x4b, 0x0d,
0x9a, 0x88, 0x27, 0x7a, 0xbb, 0xc1, 0x38, 0x33, 0xbe, 0xdd, 0x50, 0xe8, 0x2c, 0x8f, 0xfb, 0xbd,
0xcb, 0x53, 0x99, 0x0b, 0x0b, 0x33, 0xec, 0x32, 0xe9, 0x7c, 0xc7, 0x61, 0x8a, 0x94, 0x46, 0xca,
0x66, 0x51, 0x77, 0x44, 0xc6, 0x67, 0xe4, 0x49, 0xa4, 0x11, 0x5a, 0x34, 0xe8, 0x82, 0x0c, 0x2e,
0x47, 0x42, 0x5d, 0xdc, 0x47, 0x44, 0x84, 0x0a, 0x63, 0x18, 0x52, 0xfb, 0x86, 0x1a, 0x09, 0xfd,
0x55, 0x69, 0x0c, 0xa1, 0xbf, 0xca, 0xad, 0xe1, 0xa8, 0x5d, 0x8e, 0xc2, 0x00, 0x0a, 0x59, 0x83,
0x09, 0x03, 0x58, 0x42, 0xe8, 0xc6, 0x00, 0x62, 0x20, 0x8d, 0x20, 0x7a, 0x7c, 0x47, 0x6d, 0x1b,
0x43, 0x9a, 0x41, 0x24, 0x7e, 0x18, 0x82, 0xeb, 0xaf, 0x4a, 0x63, 0xb4, 0x8e, 0x6a, 0xf4, 0x57,
0xa5, 0x35, 0x84, 0x01, 0x54, 0x61, 0x8d, 0x2e, 0x36, 0xc2, 0x00, 0x8a, 0xb0, 0x86, 0xd0, 0x5f,
0xe9, 0xea, 0x2f, 0x87, 0x55, 0x63, 0x00, 0xb5, 0xb1, 0x87, 0xf0, 0x06, 0x69, 0xad, 0x48, 0x5a,
0x80, 0x3a, 0x62, 0x82, 0x94, 0xbb, 0x83, 0xc2, 0xcd, 0x61, 0x49, 0x96, 0x12, 0x00, 0x61, 0x00,
0x85, 0x5b, 0xc3, 0xe6, 0x52, 0xf3, 0x4b, 0x88, 0xee, 0x32, 0x39, 0x90, 0x78, 0xea, 0x8a, 0xb0,
0x84, 0xb4, 0x40, 0xd7, 0x11, 0xc4, 0x77, 0x57, 0x76, 0x57, 0xc2, 0x02, 0x2a, 0xe3, 0xcc, 0xa4,
0x05, 0x84, 0x39, 0xb8, 0x01, 0xfe, 0xa7, 0xb4, 0x2b, 0xec, 0x8d, 0xdb, 0x56, 0xb6, 0x7f, 0x85,
0xb8, 0x0f, 0xb8, 0x70, 0xf1, 0x2a, 0x47, 0x24, 0x45, 0x4a, 0x2a, 0x52, 0x01, 0xb1, 0xfb, 0xd2,
0xe6, 0xa2, 0x49, 0x0c, 0xdb, 0xef, 0x36, 0xf7, 0xd3, 0x83, 0x2c, 0xcb, 0xce, 0x22, 0xf2, 0xae,
0xb1, 0x5a, 0x27, 0x75, 0x7e, 0xfd, 0x9b, 0x33, 0x43, 0xed, 0x4a, 0xf2, 0xd2, 0xd9, 0xa6, 0x1f,
0xbc, 0x6b, 0xad, 0xa8, 0x21, 0x39, 0xc3, 0x19, 0xce, 0x9c, 0x19, 0x49, 0x56, 0x31, 0x3b, 0x32,
0x61, 0x80, 0x0a, 0xdc, 0x08, 0xa3, 0x09, 0x63, 0x63, 0x06, 0x28, 0xe6, 0x46, 0x58, 0xe0, 0xb9,
0x9a, 0xb0, 0x43, 0x0d, 0xcb, 0x21, 0x19, 0xb4, 0x41, 0x94, 0x23, 0x99, 0x9c, 0xce, 0x1b, 0x9e,
0x7f, 0xd0, 0x0d, 0x15, 0xd4, 0x21, 0x70, 0x63, 0xda, 0xa3, 0x30, 0x40, 0x94, 0x43, 0x18, 0x90,
0x25, 0x63, 0x06, 0x84, 0x83, 0x61, 0x39, 0xc8, 0x9c, 0xd5, 0x98, 0x19, 0xe1, 0x53, 0x4c, 0x58,
0x5a, 0x26, 0xa2, 0x0e, 0x81, 0x19, 0xa2, 0x0e, 0x81, 0x97, 0x32, 0xff, 0xa0, 0x1c, 0x81, 0x01,
0xa2, 0x1c, 0x6a, 0x2a, 0x1b, 0x61, 0x80, 0x28, 0x87, 0x9a, 0xa8, 0xc3, 0x30, 0xff, 0x70, 0xb8,
0x2f, 0xc6, 0x87, 0x05, 0xf1, 0x9e, 0x02, 0x9a, 0xe6, 0xdb, 0x8b, 0xb8, 0x8f, 0x2b, 0x79, 0x64,
0x9e, 0xd1, 0x75, 0x20, 0xa6, 0x2b, 0x83, 0x38, 0x9a, 0x6f, 0x5b, 0xfc, 0x3e, 0xb6, 0x5b, 0x37,
0xdf, 0xf4, 0xb8, 0xfa, 0xa8, 0xbb, 0x7c, 0x40, 0x00, 0xf1, 0xb1, 0xad, 0xd7, 0x87, 0xc0, 0x99,
0x13, 0x50, 0x1e, 0x5b, 0x2b, 0xc3, 0xe9, 0xc7, 0x8e, 0xb6, 0x14, 0xcd, 0x6b, 0x04, 0x7b, 0x13,
0x60, 0x61, 0x44, 0xd0, 0xd4, 0xa0, 0x20, 0x6f, 0xda, 0x1c, 0x63, 0x03, 0x04, 0xa2, 0x5c, 0xd0,
0x8e, 0x20, 0xb8, 0xb9, 0x21, 0x71, 0x69, 0x03, 0xa7, 0x56, 0x53, 0xbc, 0xc1, 0xdc, 0xf3, 0x19,
0xb9, 0xc1, 0xb4, 0xb9, 0x62, 0xb3, 0x44, 0x60, 0x8b, 0x6f, 0xda, 0xa5, 0x3c, 0x02, 0x0a, 0xc3,
0x80, 0xbf, 0x2e, 0xb6, 0x56, 0xc4, 0x52, 0x58, 0x5a, 0xd0, 0x6e, 0x40, 0xd7, 0xe4, 0x0e, 0x71,
0x3c, 0x07, 0x4b, 0xb4, 0x2f, 0x0c, 0x87, 0xc6, 0xd2, 0xa8, 0x0a, 0x66, 0x75, 0x26, 0x7b, 0x80,
0xa6, 0x98, 0xd9, 0x21, 0xc2, 0xa4, 0xbe, 0xe0, 0x74, 0xd0, 0x67, 0xf8, 0x1f, 0xcb, 0x90, 0xd4,
0x10, 0x53, 0xb6, 0x0c, 0x16, 0x42, 0x31, 0xb0, 0xd5, 0x3b, 0xc4, 0xa0, 0x1a, 0x0a, 0x52, 0x92,
0xd3, 0x45, 0xbb, 0xaa, 0x71, 0x9a, 0x7d, 0x89, 0x9c, 0xf5, 0x34, 0x23, 0x43, 0x91, 0xc1, 0x41,
0xb1, 0x08, 0xe1, 0x1d, 0xf9, 0x9a, 0xb4, 0xad, 0xf9, 0x74, 0xd8, 0x09, 0x80, 0xc0, 0x38, 0x72,
0x09, 0x29, 0x92, 0x0d, 0x26, 0xdd, 0x23, 0x04, 0xc8, 0x91, 0x29, 0x08, 0x0d, 0x33, 0x2f, 0xb8,
0x09, 0x29, 0x45, 0x96, 0x2b, 0x0a, 0x9d, 0x0b, 0xf2, 0xf7, 0x43, 0x77, 0xd2, 0x7b, 0xba, 0x3d,
0x0c, 0xd3, 0x48, 0x65, 0xe8, 0xc3, 0x74, 0xd4, 0x68, 0x3a, 0x74, 0x8e, 0xf4, 0x05, 0xb1, 0x27,
0x7a, 0x86, 0x11, 0x70, 0x05, 0xbb, 0xce, 0xc2, 0x13, 0x15, 0x78, 0x82, 0xd0, 0x1f, 0xf9, 0x0e,
0x4f, 0x5a, 0xed, 0xc8, 0xef, 0x87, 0x6f, 0x01, 0xd7, 0x99, 0x24, 0x9b, 0xf9, 0x81, 0xd9, 0x6a,
0x60, 0xb6, 0x86, 0x25, 0x80, 0xcb, 0x96, 0x92, 0x96, 0x72, 0x5a, 0x22, 0x65, 0xd7, 0x01, 0xe8,
0x79, 0x0e, 0xb3, 0x26, 0x92, 0x54, 0x83, 0x24, 0x2d, 0xfc, 0x1d, 0xb2, 0xfd, 0x88, 0x15, 0x25,
0x8a, 0xe0, 0xa5, 0x20, 0xeb, 0x42, 0x0e, 0xbe, 0xbe, 0x0d, 0x53, 0x80, 0x7e, 0x34, 0xbc, 0x34,
0xca, 0x02, 0xea, 0x68, 0xe0, 0xa0, 0xc1, 0x7f, 0xf3, 0x3a, 0x0b, 0x07, 0xf2, 0x09, 0xfb, 0xe2,
0xb0, 0x70, 0x78, 0xda, 0x96, 0x9c, 0x04, 0x03, 0x47, 0x82, 0xf6, 0x2b, 0x83, 0x34, 0x03, 0xfd,
0x6e, 0x69, 0xb8, 0x96, 0xa6, 0x0c, 0xc7, 0x8d, 0xa2, 0xe9, 0x42, 0x73, 0xe8, 0x4e, 0xd6, 0x05,
0x60, 0x10, 0x19, 0xc4, 0x63, 0xef, 0x88, 0x4b, 0xe4, 0x61, 0xb0, 0x3f, 0x4e, 0x7c, 0x81, 0x97,
0x47, 0xb6, 0xd2, 0x41, 0x61, 0xca, 0x63, 0x5b, 0x80, 0xb4, 0x05, 0x24, 0x02, 0xa4, 0x0e, 0xcc,
0x0b, 0x8d, 0x93, 0xa1, 0x31, 0x85, 0xcd, 0x70, 0xd1, 0x11, 0xd7, 0x1b, 0xc0, 0xc7, 0xd2, 0x31,
0xcd, 0x1f, 0x51, 0x40, 0xb6, 0x3d, 0x1e, 0x06, 0x92, 0x0e, 0xf3, 0x4a, 0x78, 0x3e, 0x5e, 0xe6,
0x33, 0xf9, 0x2c, 0x72, 0x64, 0x60, 0x78, 0xc3, 0x24, 0xfb, 0xe6, 0x3c, 0x12, 0x21, 0x39, 0x00,
0xad, 0xd2, 0x0a, 0x8b, 0x2d, 0xb1, 0xbe, 0x04, 0x4b, 0x01, 0xf5, 0x91, 0x93, 0x04, 0xe0, 0x91,
0x46, 0x85, 0xb4, 0x0a, 0x11, 0x70, 0x00, 0x32, 0x32, 0x81, 0x6d, 0xf8, 0x88, 0x02, 0xca, 0x14,
0x81, 0x06, 0x3c, 0x55, 0xa7, 0xc3, 0xb9, 0x44, 0xce, 0x35, 0xc8, 0xc7, 0x64, 0xce, 0x23, 0xa1,
0x65, 0x69, 0xd3, 0x05, 0x1e, 0xa3, 0xd1, 0x57, 0xe8, 0x23, 0xf4, 0x39, 0x1c, 0x1f, 0x60, 0x32,
0xfa, 0x4d, 0xbd, 0x3e, 0xc0, 0x62, 0xe4, 0x44, 0x18, 0x19, 0x9a, 0x9d, 0xcd, 0x48, 0x1d, 0xdb,
0x0c, 0x84, 0x7b, 0x9a, 0x13, 0x4b, 0xb4, 0x36, 0x8c, 0x23, 0xef, 0x12, 0xe1, 0x48, 0x47, 0x3a,
0x59, 0xb2, 0xf7, 0x8a, 0x04, 0x55, 0xa9, 0xcd, 0x70, 0xac, 0xc2, 0x31, 0x5c, 0x1a, 0xd0, 0xcb,
0x78, 0xb7, 0x27, 0x16, 0x6b, 0xcd, 0xe9, 0x27, 0x24, 0x2c, 0x72, 0xa0, 0x27, 0x44, 0xd4, 0x6b,
0x60, 0x11, 0x25, 0xed, 0x52, 0xb4, 0x68, 0x72, 0xf6, 0xbe, 0x53, 0xf4, 0xe6, 0x8e, 0x41, 0x97,
0xf8, 0x08, 0x7c, 0xc2, 0xd1, 0x35, 0x65, 0x47, 0x5a, 0xc9, 0xb9, 0xad, 0x12, 0x03, 0x03, 0x1b,
0xa8, 0x0d, 0x05, 0x7d, 0xf2, 0xed, 0x1a, 0x8a, 0x7c, 0x72, 0x22, 0x23, 0x9f, 0x25, 0x6f, 0x38,
0x1a, 0x07, 0xc0, 0x49, 0x4c, 0xce, 0x2a, 0x5e, 0xea, 0xac, 0xd7, 0xb0, 0x3d, 0x46, 0x10, 0x36,
0xc3, 0xe0, 0x1c, 0x67, 0xbc, 0x32, 0x5b, 0x74, 0xa0, 0x8c, 0xe9, 0xa0, 0x23, 0x07, 0xb5, 0x35,
0x38, 0x69, 0x32, 0x86, 0x0e, 0x9b, 0x8c, 0x9d, 0xec, 0x82, 0x31, 0x3d, 0x48, 0x96, 0x11, 0x61,
0xd8, 0x1a, 0x62, 0x16, 0xa6, 0xc2, 0x47, 0xbd, 0x86, 0x85, 0x35, 0xe4, 0x9a, 0x40, 0x6d, 0xe5,
0x94, 0x92, 0x53, 0x9d, 0xd0, 0x53, 0x81, 0x9e, 0x0a, 0xbd, 0x29, 0xe9, 0xad, 0x29, 0x25, 0x88,
0x23, 0x27, 0x9b, 0xd7, 0x88, 0xc9, 0x10, 0xd9, 0x5b, 0xf8, 0x66, 0x18, 0xa2, 0x38, 0xd7, 0x45,
0x2f, 0x30, 0x17, 0x58, 0x69, 0xad, 0xcc, 0x4b, 0xc9, 0xbc, 0xba, 0x81, 0x1f, 0x2a, 0xf0, 0x43,
0x09, 0xbb, 0x54, 0x60, 0x57, 0x03, 0x56, 0x03, 0xb9, 0x02, 0x50, 0xc7, 0xfa, 0x0d, 0x55, 0x82,
0x67, 0xc7, 0x3c, 0x56, 0xc2, 0x63, 0x92, 0x79, 0xa6, 0x11, 0x87, 0x6b, 0x46, 0x19, 0x3c, 0x05,
0x28, 0x64, 0x4a, 0x2c, 0x43, 0x24, 0xa4, 0x76, 0xfc, 0xbf, 0xec, 0x92, 0xc0, 0xc4, 0x11, 0x82,
0x20, 0x79, 0x53, 0xc0, 0xef, 0xa7, 0xa1, 0xb3, 0xea, 0x61, 0x81, 0x30, 0x05, 0x5a, 0x20, 0x30,
0x38, 0x19, 0x90, 0x4b, 0x83, 0x35, 0xe4, 0x81, 0x04, 0x38, 0x07, 0x49, 0x38, 0xc4, 0x39, 0x64,
0x4e, 0x8e, 0xad, 0x04, 0x5f, 0xd4, 0x04, 0xa6, 0x02, 0x02, 0xc3, 0xce, 0x99, 0x23, 0xa2, 0x23,
0x6e, 0x51, 0x20, 0xd5, 0x69, 0xd8, 0x5f, 0x4e, 0x01, 0xca, 0xb7, 0x81, 0xa1, 0xb4, 0x1a, 0x38,
0x33, 0x2d, 0x14, 0x2c, 0xb1, 0xb4, 0x48, 0x41, 0x29, 0x63, 0x30, 0x16, 0xfe, 0x26, 0x11, 0xc9,
0xd9, 0x83, 0xf6, 0xf0, 0xae, 0x11, 0x32, 0x76, 0x0c, 0x18, 0xd2, 0x7a, 0x27, 0xd5, 0x4a, 0xb1,
0xac, 0x87, 0x63, 0x15, 0x8e, 0x11, 0xde, 0x78, 0xee, 0xd9, 0xa2, 0xaf, 0x14, 0xcc, 0xe2, 0xb8,
0xba, 0x10, 0x3a, 0x6a, 0xa0, 0x13, 0xba, 0x57, 0xd2, 0xbd, 0x92, 0x51, 0x65, 0xe1, 0xdb, 0x37,
0x2e, 0x38, 0xa6, 0xc0, 0x6b, 0x69, 0x25, 0x90, 0x53, 0xc9, 0xc2, 0x77, 0x3c, 0x25, 0x15, 0xa6,
0x14, 0xf8, 0xa0, 0x02, 0x1f, 0x94, 0xb0, 0x09, 0x18, 0x01, 0xb3, 0xa9, 0xa1, 0xae, 0x0c, 0x62,
0xf7, 0xb4, 0x00, 0x11, 0x04, 0xd5, 0xcc, 0xce, 0xc0, 0x5b, 0x39, 0x38, 0x04, 0x69, 0x5c, 0xaf,
0xfa, 0xfe, 0x90, 0x7c, 0x41, 0x26, 0xee, 0xa9, 0xb3, 0x64, 0xe9, 0x3a, 0x53, 0xd0, 0xba, 0x29,
0x81, 0xe6, 0xe2, 0x1b, 0xeb, 0x12, 0x58, 0x31, 0x7f, 0x2a, 0xf9, 0x1f, 0xae, 0x5d, 0x8a, 0x3d,
0xc0, 0x7a, 0x66, 0x65, 0xc9, 0xe8, 0xb1, 0x7c, 0xca, 0xb9, 0x70, 0xc0, 0xe7, 0x69, 0xb3, 0xee,
0x02, 0x31, 0xe0, 0xdf, 0x13, 0xe2, 0xc3, 0xef, 0x4d, 0xb2, 0xeb, 0x65, 0x1f, 0x0d, 0x58, 0x21,
0x3e, 0x54, 0xa3, 0x76, 0x6a, 0x18, 0x87, 0x92, 0x36, 0xdd, 0xb4, 0x93, 0x62, 0xde, 0x69, 0x33,
0x22, 0x91, 0xef, 0x25, 0x41, 0x2c, 0xa0, 0x25, 0x8e, 0x89, 0x6a, 0xd2, 0x43, 0xc8, 0x2c, 0x2b,
0x39, 0xab, 0x80, 0x85, 0xc2, 0x07, 0xbd, 0xb6, 0xb2, 0x8a, 0x38, 0xeb, 0x2d, 0x67, 0x12, 0x9c,
0x29, 0xbb, 0x69, 0xaf, 0x85, 0x9a, 0xf7, 0x2e, 0x84, 0x77, 0xe4, 0xc9, 0x05, 0x19, 0xd1, 0xd7,
0x51, 0xfa, 0x5a, 0xe8, 0x37, 0xe3, 0x99, 0xef, 0x93, 0x83, 0xed, 0x66, 0x5c, 0x2d, 0x0e, 0x58,
0x22, 0xf5, 0x7a, 0xbd, 0xfa, 0xc2, 0x45, 0xc2, 0x49, 0xb3, 0x58, 0x37, 0x5d, 0x7b, 0xc0, 0x72,
0x09, 0xd8, 0x39, 0xa7, 0x3c, 0x48, 0x65, 0x9a, 0x11, 0x86, 0x8e, 0x0c, 0xc7, 0x14, 0x3c, 0x57,
0x01, 0x3c, 0x1f, 0x8e, 0xfb, 0x00, 0xc8, 0x2b, 0x06, 0xe4, 0x5d, 0x38, 0x8d, 0xf2, 0x02, 0x69,
0x3e, 0x7c, 0x0b, 0x99, 0xdd, 0xf1, 0x00, 0xdc, 0x87, 0xcb, 0x84, 0x4a, 0xf9, 0xa4, 0x97, 0xe9,
0x65, 0x26, 0x99, 0x92, 0x37, 0xc9, 0x70, 0xdd, 0x24, 0x1d, 0x50, 0x6c, 0xd3, 0x01, 0xdb, 0x66,
0x93, 0x49, 0x98, 0x6d, 0x06, 0x20, 0x5c, 0x36, 0xcc, 0x61, 0xd6, 0xcb, 0xd7, 0x89, 0xcb, 0x1d,
0x32, 0x49, 0xa9, 0x24, 0x8e, 0x92, 0x71, 0x46, 0x29, 0xe1, 0xcf, 0x3e, 0xfc, 0x96, 0x8c, 0x7e,
0x93, 0x4f, 0x72, 0x00, 0xc2, 0xc5, 0x72, 0xac, 0xc6, 0x0d, 0x85, 0x44, 0x1f, 0xcb, 0x50, 0xed,
0x0b, 0xa7, 0x34, 0x0d, 0x8b, 0xd1, 0x53, 0xcd, 0x10, 0x60, 0x67, 0xc8, 0x3f, 0x24, 0x5e, 0xe1,
0x93, 0x97, 0x95, 0x0b, 0xea, 0x3d, 0x52, 0xbc, 0x62, 0xd0, 0x88, 0x54, 0xed, 0x54, 0x3f, 0x2c,
0xbc, 0xb9, 0xd6, 0x90, 0x71, 0x45, 0x9e, 0x07, 0x8e, 0xa9, 0x47, 0xbe, 0xd0, 0x7f, 0x74, 0x86,
0x33, 0x48, 0xcd, 0x50, 0x17, 0xf0, 0x6c, 0xc9, 0xc1, 0xbc, 0xc0, 0xe8, 0x63, 0x12, 0x2e, 0xef,
0x02, 0x39, 0x15, 0xc8, 0x37, 0xbb, 0x91, 0xb8, 0xfd, 0x23, 0x49, 0x32, 0x1e, 0x25, 0x7f, 0x26,
0xac, 0x64, 0x39, 0xeb, 0x95, 0x87, 0x11, 0x87, 0x32, 0xf2, 0x41, 0x9f, 0x8c, 0xb5, 0x2c, 0x9c,
0x4a, 0x72, 0x66, 0x4d, 0x32, 0xe6, 0x0d, 0xed, 0x03, 0xc1, 0x0a, 0xd1, 0x3f, 0xdc, 0x95, 0x11,
0x6d, 0xd3, 0x7f, 0x41, 0xb9, 0xb8, 0xe4, 0xfd, 0x70, 0xed, 0x2a, 0xe0, 0xb3, 0x23, 0x1e, 0x08,
0xda, 0x95, 0x4c, 0xd4, 0x2b, 0x96, 0x9b, 0xda, 0xaa, 0x57, 0x32, 0xd5, 0xaf, 0x99, 0x02, 0x64,
0xc9, 0x4c, 0xbf, 0x66, 0x0a, 0xe4, 0xd5, 0x54, 0xbf, 0x66, 0xea, 0x57, 0xcc, 0xd4, 0xd2, 0xcc,
0xd4, 0xd7, 0xa8, 0xa9, 0x7e, 0xcd, 0x94, 0xdf, 0xa8, 0x99, 0x7e, 0xcd, 0x14, 0xa8, 0x48, 0xa6,
0xfa, 0x95, 0x3c, 0xab, 0x5f, 0x33, 0x1d, 0xd9, 0xa3, 0x4c, 0xfd, 0x53, 0xb5, 0x09, 0xfa, 0x35,
0xd5, 0xcd, 0x3d, 0xd9, 0xde, 0x7e, 0x8f, 0xbe, 0xc6, 0xf5, 0x2b, 0x2f, 0x3c, 0xaf, 0xae, 0x41,
0xbf, 0xa6, 0x8b, 0x68, 0xa4, 0x61, 0xe3, 0xad, 0xad, 0xd8, 0x6e, 0x8f, 0xa3, 0xcd, 0x35, 0x4f,
0xf6, 0x6b, 0xd8, 0x5c, 0xc1, 0x06, 0x15, 0x99, 0x95, 0xde, 0xa8, 0x51, 0x39, 0xd0, 0xae, 0x2c,
0x61, 0x5c, 0x52, 0x23, 0x1a, 0x36, 0x28, 0x58, 0x32, 0xd7, 0xb0, 0xd1, 0x50, 0xdc, 0xfe, 0xa1,
0xa8, 0x91, 0x8a, 0xa9, 0xb1, 0x8a, 0xa9, 0x89, 0x8a, 0xcd, 0xf7, 0xb1, 0x9d, 0x86, 0x8d, 0x79,
0x33, 0xe8, 0x97, 0xfa, 0x2e, 0xfd, 0x5a, 0xaf, 0x9a, 0x4f, 0xed, 0x21, 0x48, 0x88, 0x47, 0x1c,
0xa8, 0xa4, 0xb8, 0x31, 0xc9, 0x69, 0x8d, 0x71, 0x41, 0x04, 0x92, 0x01, 0x2e, 0xe7, 0x2c, 0xa3,
0xe6, 0x0c, 0x51, 0xd1, 0xef, 0x0e, 0x55, 0xf8, 0x09, 0xdf, 0x4a, 0x7e, 0x56, 0xc3, 0xcf, 0xbb,
0xc3, 0xa1, 0xc5, 0xd7, 0xd0, 0x05, 0x97, 0x04, 0x4d, 0x8a, 0x52, 0x9e, 0x14, 0xde, 0xf4, 0xf3,
0x72, 0x9b, 0x67, 0x6a, 0x50, 0x9e, 0xd4, 0x2e, 0xf1, 0xc7, 0xbe, 0x15, 0x08, 0xa9, 0x22, 0x19,
0xc5, 0x35, 0x1f, 0x9e, 0xcb, 0xed, 0x52, 0x0e, 0x3f, 0xb9, 0x76, 0x02, 0x31, 0x16, 0xfb, 0x12,
0x5e, 0x7c, 0x09, 0x04, 0x9d, 0x14, 0x48, 0xc9, 0x27, 0x45, 0x01, 0xce, 0x49, 0xe4, 0x69, 0x28,
0x06, 0x2b, 0x1d, 0x3b, 0x60, 0xe4, 0x79, 0xe6, 0x05, 0x47, 0x52, 0x06, 0x29, 0x84, 0xac, 0xe0,
0x25, 0x02, 0x42, 0xd6, 0x48, 0xc9, 0x9f, 0x41, 0x51, 0x22, 0x2c, 0xcc, 0x70, 0x9c, 0x1f, 0x17,
0x80, 0x98, 0x90, 0xf9, 0x29, 0x51, 0x53, 0x81, 0x72, 0x13, 0x80, 0x1c, 0xce, 0x7b, 0x65, 0x9d,
0x94, 0x88, 0xe4, 0x0c, 0x43, 0x0d, 0x5f, 0xe4, 0x58, 0x6b, 0x72, 0x7c, 0x32, 0x2e, 0xd3, 0xb4,
0xb4, 0x62, 0x68, 0xa5, 0x96, 0x52, 0x05, 0x2a, 0xa9, 0x94, 0x9c, 0xa3, 0xee, 0x04, 0x78, 0x87,
0x46, 0xfa, 0x8e, 0xec, 0x42, 0x41, 0xc1, 0xa1, 0x35, 0x52, 0x8b, 0x83, 0x4a, 0x94, 0x82, 0x57,
0x59, 0x81, 0x72, 0x40, 0x4e, 0x63, 0x90, 0xeb, 0xad, 0xf1, 0x3f, 0x12, 0x05, 0x34, 0x01, 0x94,
0x33, 0xe0, 0xff, 0xaf, 0x6f, 0x4d, 0x86, 0x22, 0x11, 0x04, 0xee, 0xd4, 0x9f, 0x43, 0x90, 0xed,
0xf0, 0x1d, 0xb2, 0xfa, 0xa5, 0xa4, 0x59, 0xb3, 0x84, 0x54, 0xd9, 0x14, 0x06, 0xe5, 0x22, 0xc6,
0x72, 0xea, 0x16, 0x49, 0x8f, 0x8c, 0xc2, 0x0b, 0x46, 0xbc, 0x0c, 0x62, 0xbf, 0x54, 0x22, 0x08,
0x92, 0xb8, 0xe3, 0xac, 0x07, 0x62, 0xbd, 0x1c, 0x38, 0xa3, 0x75, 0x9c, 0x15, 0x45, 0xe4, 0x07,
0x80, 0x02, 0x01, 0x0d, 0x3c, 0x7c, 0x30, 0xc3, 0x23, 0x7c, 0xa0, 0x58, 0xc7, 0xb2, 0x37, 0xeb,
0x32, 0xb0, 0x34, 0xb1, 0x39, 0x03, 0xd8, 0x79, 0xca, 0xd8, 0x9b, 0xf7, 0x1c, 0x39, 0x12, 0x3b,
0x72, 0x24, 0x0f, 0x33, 0x29, 0x04, 0x15, 0x34, 0x8d, 0x86, 0x9e, 0x43, 0x92, 0x96, 0x4d, 0xfa,
0xb1, 0x2f, 0x8c, 0x20, 0x21, 0xd4, 0x01, 0x49, 0x05, 0xa6, 0x12, 0x31, 0x83, 0x1f, 0xbe, 0x90,
0x54, 0x02, 0xfe, 0x93, 0x42, 0x3d, 0x91, 0xf5, 0x41, 0x76, 0x88, 0x0f, 0xe2, 0xf5, 0x7c, 0x48,
0x65, 0xa3, 0xde, 0x34, 0x13, 0x63, 0x42, 0x73, 0x75, 0x1e, 0x4c, 0x71, 0x80, 0x08, 0xe9, 0xb0,
0xa4, 0x90, 0x95, 0x86, 0x88, 0x48, 0xbd, 0x30, 0xac, 0xb4, 0xd6, 0x4b, 0x96, 0x9b, 0x66, 0xc3,
0x00, 0x7c, 0xc1, 0x30, 0xb4, 0xd1, 0xb8, 0xb4, 0x10, 0x61, 0x15, 0x1c, 0x5f, 0x72, 0xc4, 0x6c,
0xc4, 0x35, 0x4d, 0xb9, 0xec, 0xd4, 0x20, 0x04, 0x46, 0x28, 0xcf, 0x89, 0x10, 0x14, 0xe6, 0x16,
0x14, 0x1d, 0x02, 0x5a, 0xa3, 0x73, 0xa8, 0x0a, 0x4d, 0x50, 0x17, 0x82, 0xa2, 0xd1, 0x94, 0x6b,
0x83, 0x91, 0x18, 0x43, 0x54, 0x4d, 0x32, 0x42, 0x51, 0x00, 0x42, 0x30, 0x29, 0x09, 0xe6, 0x72,
0x2f, 0x04, 0xce, 0x3e, 0xec, 0x67, 0x40, 0x82, 0xb8, 0xe2, 0x25, 0xe5, 0xe0, 0x0d, 0x08, 0x99,
0xe7, 0xb5, 0x0c, 0xfc, 0x8c, 0x4c, 0x1c, 0x80, 0x30, 0x85, 0x21, 0x97, 0x58, 0x58, 0xa9, 0x45,
0x5d, 0x0c, 0x83, 0x4c, 0xc8, 0xde, 0x40, 0x8b, 0x38, 0x6d, 0x88, 0x64, 0x3c, 0x82, 0x2d, 0x0c,
0xb4, 0xd0, 0x08, 0x0b, 0x01, 0x18, 0x03, 0xaf, 0xe7, 0x0a, 0x06, 0x4e, 0xf2, 0xf2, 0x2c, 0xcb,
0xe3, 0xdc, 0xc1, 0xab, 0x07, 0x24, 0x84, 0x64, 0x62, 0x69, 0xc2, 0x01, 0x4d, 0x49, 0x70, 0x4a,
0x1b, 0x10, 0x75, 0x29, 0xb6, 0x4d, 0x3d, 0xf4, 0x0e, 0xe1, 0x77, 0x8a, 0x10, 0x1f, 0x85, 0x1c,
0x19, 0x83, 0xea, 0x5a, 0x00, 0xa2, 0x92, 0x99, 0x9c, 0x21, 0x12, 0xcf, 0x91, 0x9a, 0x64, 0xfb,
0xc1, 0xd8, 0x49, 0x86, 0x24, 0x4f, 0x2e, 0xa9, 0x49, 0xc3, 0x15, 0x50, 0xda, 0x89, 0x46, 0x92,
0x22, 0x32, 0x26, 0xe6, 0x39, 0x1c, 0xcf, 0x78, 0xbd, 0x2b, 0x9a, 0x38, 0xa0, 0x79, 0x5a, 0xa4,
0x9c, 0xd2, 0xa2, 0xa1, 0xd1, 0x5a, 0x07, 0x77, 0x50, 0x62, 0x6b, 0x38, 0x85, 0x18, 0xbe, 0x50,
0x17, 0x46, 0x01, 0x2d, 0x30, 0x42, 0x3b, 0x34, 0x31, 0x72, 0x05, 0x97, 0x49, 0x67, 0x5c, 0x3c,
0x85, 0x04, 0x29, 0x76, 0x0d, 0xa4, 0xbf, 0xb9, 0xac, 0x57, 0xba, 0x35, 0x61, 0x14, 0x9a, 0x53,
0x22, 0x52, 0x52, 0x91, 0x93, 0x09, 0xa1, 0xd1, 0xfb, 0xc2, 0x63, 0x2e, 0x9c, 0x05, 0xc8, 0x91,
0x05, 0xc0, 0xc4, 0x2c, 0xd2, 0xe2, 0x05, 0x29, 0x08, 0x7f, 0x62, 0x71, 0xa0, 0x38, 0x0b, 0x38,
0x09, 0x0a, 0xe2, 0x68, 0xc8, 0x21, 0x85, 0x93, 0x62, 0x87, 0x71, 0xc8, 0x98, 0x16, 0x9c, 0x30,
0xcd, 0x73, 0xce, 0x3c, 0x5a, 0x2e, 0xa7, 0xca, 0xd1, 0xad, 0x1c, 0x09, 0x84, 0x99, 0x2a, 0x51,
0x10, 0x52, 0xa4, 0x9c, 0x77, 0xa5, 0x8c, 0xab, 0x14, 0xf2, 0x94, 0x51, 0x4c, 0x64, 0xd3, 0x68,
0x88, 0xa5, 0x2a, 0x18, 0xec, 0xc9, 0x00, 0x49, 0x6f, 0x45, 0x3e, 0x2c, 0x81, 0xcc, 0x33, 0x42,
0x6d, 0x73, 0xf6, 0x77, 0x48, 0x4f, 0x3d, 0xac, 0xa5, 0x93, 0x6c, 0x47, 0x58, 0x4c, 0xb2, 0xb8,
0x38, 0xff, 0x47, 0x0b, 0x3d, 0x73, 0x40, 0xc9, 0x01, 0xb3, 0xa0, 0xf2, 0xa3, 0x4c, 0xb1, 0x34,
0x87, 0x15, 0x2a, 0x0b, 0xd6, 0x4a, 0xc4, 0x0e, 0x34, 0x0a, 0x55, 0xae, 0x24, 0x5f, 0xe4, 0xce,
0x29, 0x36, 0x03, 0xc4, 0x22, 0x6b, 0xde, 0x48, 0x5e, 0xd4, 0x67, 0x6c, 0x1e, 0x5c, 0xce, 0xd2,
0x02, 0x8e, 0x8b, 0xc5, 0x01, 0x3c, 0x00, 0x60, 0x8a, 0xa8, 0x92, 0xdd, 0xaa, 0x16, 0x29, 0x0a,
0xc6, 0x0a, 0x43, 0xe4, 0x61, 0xab, 0x3c, 0xd7, 0xbb, 0x1b, 0x50, 0x26, 0xf9, 0x84, 0xc3, 0xa0,
0xb3, 0x9f, 0xa5, 0xd8, 0x56, 0x4a, 0x0c, 0xdf, 0x6a, 0x57, 0x72, 0x30, 0x4b, 0xb6, 0x1e, 0x96,
0xb5, 0xa1, 0xc8, 0x1f, 0x75, 0x62, 0xb0, 0x40, 0x62, 0xb2, 0x8a, 0x8c, 0x41, 0x54, 0xf6, 0x01,
0xd3, 0x5c, 0x50, 0x09, 0x98, 0x44, 0xcb, 0x89, 0x46, 0x43, 0x93, 0x77, 0x92, 0xcb, 0x01, 0xa6,
0x0e, 0xfc, 0x43, 0x6b, 0x06, 0xf7, 0x9c, 0x67, 0x5b, 0xae, 0x89, 0x29, 0x8e, 0x96, 0x8d, 0x2b,
0xb9, 0xee, 0xcc, 0x0a, 0x72, 0x03, 0xf6, 0x79, 0x06, 0xb2, 0x0a, 0x9a, 0x3f, 0x6a, 0xc5, 0x2d,
0x43, 0xed, 0x3e, 0x47, 0xb6, 0xd8, 0xa0, 0xf8, 0x4a, 0x15, 0xc4, 0x02, 0xda, 0x6e, 0x1a, 0x9a,
0x44, 0x4e, 0x2e, 0x1f, 0x84, 0xe4, 0xc0, 0x24, 0x76, 0x44, 0x34, 0xef, 0x8b, 0xb8, 0x42, 0x0b,
0x01, 0xd4, 0x0e, 0xd0, 0x78, 0x70, 0x4b, 0x00, 0x97, 0xc6, 0x43, 0x33, 0x38, 0x05, 0x6e, 0x45,
0x15, 0x30, 0x0c, 0xec, 0xd4, 0xc4, 0xe4, 0x92, 0x2b, 0x8a, 0x00, 0x29, 0x17, 0x5c, 0x08, 0x84,
0x4a, 0x46, 0x0b, 0x26, 0xa3, 0x58, 0xb8, 0xe4, 0xd9, 0x01, 0xd0, 0xc7, 0x6c, 0xad, 0x76, 0x5f,
0xdf, 0xe6, 0x39, 0x17, 0x57, 0x29, 0x87, 0x19, 0x02, 0x18, 0x02, 0xfc, 0x4a, 0x46, 0x91, 0xfc,
0xf1, 0x12, 0x3a, 0x00, 0x4c, 0x91, 0xd7, 0x84, 0xe3, 0xed, 0x0f, 0x98, 0x32, 0x14, 0x6d, 0x38,
0x4e, 0x99, 0xbf, 0xc8, 0x1a, 0x91, 0x7c, 0x35, 0xf4, 0xdb, 0xc2, 0x5e, 0xa1, 0xb0, 0x80, 0x0b,
0x01, 0x19, 0xc2, 0xce, 0xe1, 0x3a, 0x58, 0x96, 0x29, 0xe9, 0x32, 0xac, 0x39, 0x4d, 0xc6, 0xeb,
0x02, 0x88, 0x2f, 0xd2, 0xcc, 0x8e, 0xb3, 0x34, 0xc4, 0x18, 0x74, 0xc0, 0x9f, 0xa8, 0x54, 0x42,
0x6b, 0xd2, 0x0a, 0xd9, 0x55, 0xd0, 0x80, 0x5b, 0xf3, 0x4e, 0x50, 0x02, 0xdd, 0x47, 0xb1, 0xbf,
0x1b, 0x7a, 0xa1, 0x3e, 0x73, 0x22, 0xb5, 0x3b, 0x42, 0xed, 0x56, 0x2a, 0xb0, 0x27, 0xa2, 0xf4,
0xb0, 0xc0, 0x3c, 0xe7, 0x8b, 0x65, 0xf0, 0xf9, 0x68, 0x32, 0x86, 0xf3, 0xe9, 0x1a, 0xbb, 0x19,
0xf4, 0x93, 0xac, 0x81, 0x43, 0x41, 0x15, 0x90, 0x7c, 0xd4, 0x17, 0xd1, 0x8e, 0x51, 0xc0, 0x51,
0x4f, 0x0d, 0xeb, 0xb7, 0x63, 0x5c, 0x93, 0x37, 0x56, 0xf2, 0x51, 0x32, 0x6c, 0xd2, 0xb4, 0xbb,
0x0b, 0x0c, 0x27, 0x6d, 0xb6, 0xd7, 0x1c, 0x82, 0x56, 0x3f, 0x2e, 0x9b, 0x6f, 0x78, 0x75, 0xd4,
0x92, 0x1f, 0xbe, 0x24, 0xe1, 0x2d, 0x85, 0x12, 0x6c, 0x92, 0x33, 0x30, 0xb3, 0x0c, 0x2e, 0xf7,
0x37, 0x10, 0x29, 0x92, 0x7e, 0x61, 0x79, 0x35, 0xd3, 0x17, 0xca, 0xeb, 0x90, 0xbc, 0x4a, 0x81,
0x9c, 0xf2, 0x46, 0x54, 0xe2, 0x36, 0x83, 0x14, 0x45, 0x5d, 0x9c, 0x1b, 0xa0, 0x05, 0x4b, 0xab,
0x88, 0x74, 0x87, 0x73, 0xf0, 0xb4, 0x15, 0xc9, 0x17, 0xf9, 0x4d, 0x45, 0x2a, 0x39, 0x13, 0x4e,
0x35, 0x31, 0xf2, 0x02, 0x42, 0x72, 0x04, 0xbd, 0x35, 0xbc, 0x9d, 0xc2, 0x0c, 0x13, 0x4f, 0x60,
0x56, 0x90, 0x31, 0xf7, 0x70, 0x11, 0x81, 0x3d, 0x16, 0x5c, 0xc1, 0xe9, 0x25, 0x13, 0x15, 0xc2,
0xd1, 0x63, 0xb9, 0xeb, 0x04, 0x95, 0x6b, 0x30, 0x72, 0x9e, 0x6b, 0x49, 0x5c, 0x99, 0x4b, 0x83,
0x42, 0x49, 0xeb, 0xa1, 0x05, 0x5a, 0xcb, 0xf9, 0x24, 0xb4, 0x06, 0x71, 0x0c, 0x21, 0x07, 0x2c,
0xcc, 0x77, 0x9e, 0xb0, 0x67, 0x80, 0x21, 0x78, 0xdc, 0xe4, 0xc2, 0xc9, 0x98, 0xed, 0xb1, 0x2e,
0x25, 0xa2, 0x43, 0x01, 0xa6, 0x91, 0xb2, 0x32, 0x6c, 0x8e, 0xec, 0xb3, 0x49, 0x4d, 0xaa, 0x85,
0x2d, 0xd7, 0x25, 0xb3, 0xcc, 0xd1, 0x54, 0xe5, 0xeb, 0x00, 0x26, 0x4f, 0x40, 0xc3, 0x7d, 0xc1,
0x84, 0xeb, 0xc6, 0x59, 0xd3, 0xbf, 0x09, 0x90, 0x75, 0xa3, 0x2c, 0x6a, 0x0c, 0x2c, 0x73, 0x5b,
0xb0, 0x6c, 0xee, 0x07, 0x85, 0x05, 0x65, 0x24, 0x06, 0xf7, 0x5c, 0xbc, 0x4c, 0x96, 0x94, 0xd3,
0xa5, 0x5c, 0xe3, 0x07, 0xef, 0x18, 0x9c, 0xb7, 0xb0, 0x26, 0x41, 0x4c, 0x88, 0x4c, 0x45, 0x32,
0xcc, 0x79, 0x38, 0x1b, 0x16, 0x01, 0x01, 0xb3, 0x1e, 0x6b, 0x83, 0x59, 0xaf, 0x84, 0xd5, 0x28,
0xd4, 0x95, 0x3c, 0xd8, 0x70, 0x2c, 0xac, 0x67, 0x00, 0xcf, 0x49, 0x07, 0x81, 0xf5, 0x49, 0x60,
0x7d, 0xc2, 0xac, 0x37, 0xba, 0x13, 0x96, 0xab, 0xc0, 0x79, 0xf2, 0x1f, 0x47, 0x00, 0x65, 0x39,
0x06, 0x28, 0xcb, 0x38, 0x40, 0xd9, 0x8c, 0x71, 0xcf, 0xb1, 0xd4, 0x46, 0x00, 0xe2, 0x98, 0x85,
0xdf, 0x86, 0x64, 0xbb, 0x71, 0x36, 0xfc, 0x00, 0x74, 0xb5, 0x0f, 0xc7, 0xe5, 0xe0, 0x3d, 0x0a,
0x15, 0x56, 0xc1, 0xa0, 0x8f, 0x0d, 0xf4, 0x50, 0x89, 0x1e, 0xf2, 0x3e, 0x57, 0xa2, 0x94, 0x80,
0xf5, 0xd0, 0xa8, 0xa0, 0x88, 0x4a, 0x14, 0x91, 0xef, 0x2b, 0xe2, 0x4a, 0xf3, 0x5c, 0xb6, 0x5a,
0xd6, 0xc4, 0x4c, 0x89, 0xee, 0x29, 0xd1, 0x44, 0x33, 0x1c, 0x0e, 0xaa, 0xa8, 0x44, 0x15, 0x13,
0xd1, 0x44, 0x15, 0x34, 0x31, 0x09, 0x9a, 0xc8, 0xba, 0x55, 0x88, 0xb8, 0x35, 0x74, 0x0b, 0xf7,
0x46, 0x89, 0xb8, 0x45, 0xc2, 0x2c, 0xef, 0x43, 0x62, 0x53, 0x7e, 0x7e, 0xcb, 0x5d, 0xbd, 0xfe,
0x74, 0x38, 0xf2, 0x23, 0xe0, 0x46, 0x28, 0xdb, 0x3f, 0xac, 0x80, 0x39, 0xeb, 0x67, 0x45, 0xcb,
0xd9, 0x0c, 0x18, 0xf2, 0x28, 0xdd, 0x67, 0x5a, 0x76, 0x40, 0x84, 0xa6, 0xe0, 0xe5, 0xb6, 0x61,
0x2f, 0x04, 0xec, 0x1c, 0x11, 0x1a, 0x8e, 0x9b, 0xed, 0x90, 0xd4, 0xf3, 0x10, 0x6e, 0x7f, 0x10,
0x84, 0x9b, 0xa9, 0x29, 0xc8, 0xb3, 0x3d, 0x3d, 0x00, 0x5e, 0x4a, 0x6a, 0xb2, 0x67, 0x44, 0xe6,
0x78, 0xd6, 0xd7, 0x1d, 0xd7, 0xb0, 0x04, 0xbf, 0x1f, 0x36, 0x8d, 0xa2, 0xb4, 0x51, 0x90, 0x76,
0x5f, 0x24, 0x05, 0xdf, 0x23, 0xcf, 0x78, 0x28, 0x9e, 0xe3, 0x9c, 0xf4, 0x09, 0xd4, 0xb8, 0x4b,
0x18, 0x8c, 0x8b, 0x58, 0xa6, 0xd6, 0x74, 0xaf, 0x76, 0xee, 0x49, 0x7d, 0x24, 0x33, 0x4d, 0x42,
0x61, 0x0d, 0x1b, 0xcc, 0xf0, 0x6d, 0x43, 0x39, 0x83, 0x7c, 0x17, 0xcd, 0x53, 0xdd, 0x1c, 0x53,
0x98, 0x62, 0xbd, 0xc5, 0x5e, 0x84, 0xd5, 0x75, 0x6c, 0x9b, 0xb2, 0xe1, 0x56, 0xab, 0x90, 0x2f,
0x49, 0x46, 0xf9, 0x92, 0x64, 0x9c, 0x2f, 0x39, 0x18, 0x25, 0xbd, 0x5e, 0x7d, 0x59, 0xfe, 0x55,
0x90, 0xd4, 0x53, 0xc8, 0x89, 0x1b, 0x01, 0x8b, 0x59, 0x0a, 0x62, 0x56, 0xc7, 0x3f, 0x5b, 0x3b,
0x45, 0x9f, 0x4c, 0x35, 0x61, 0xae, 0x32, 0x31, 0x55, 0xda, 0xa9, 0x5a, 0x00, 0x47, 0x27, 0x10,
0x6b, 0x31, 0x83, 0x58, 0xcd, 0x0c, 0x62, 0x35, 0x53, 0x88, 0x35, 0x9b, 0x41, 0xac, 0x66, 0x06,
0xb1, 0xfa, 0x27, 0xbf, 0x4f, 0x21, 0xd6, 0x7c, 0x0a, 0xb1, 0x6e, 0x53, 0x15, 0x48, 0xce, 0xd2,
0x6a, 0xe6, 0x54, 0x1e, 0xe9, 0xfe, 0xe4, 0x76, 0x96, 0xa7, 0x40, 0x68, 0x1f, 0xc3, 0x3f, 0x9b,
0xe1, 0x4e, 0x98, 0x28, 0x0e, 0xda, 0x47, 0xd5, 0x63, 0x6f, 0x41, 0x57, 0x9a, 0xf1, 0x86, 0x54,
0x58, 0x2e, 0x12, 0x7b, 0x92, 0x81, 0x90, 0x75, 0xed, 0xf6, 0x27, 0xb6, 0x46, 0x5a, 0x11, 0x41,
0x50, 0x9f, 0x00, 0x99, 0x9f, 0xb7, 0x00, 0xe9, 0x70, 0x1f, 0xf4, 0x93, 0x3b, 0x25, 0xe5, 0xd6,
0xc4, 0x11, 0x6c, 0x3a, 0xba, 0x41, 0xf1, 0xf3, 0x04, 0x20, 0xd5, 0x5c, 0xb2, 0xc4, 0xf4, 0x0f,
0x41, 0x73, 0xc7, 0x39, 0x88, 0x80, 0x8c, 0x32, 0x4c, 0x9a, 0x8c, 0x61, 0xd2, 0x3e, 0x6c, 0xd0,
0x36, 0xe8, 0x0a, 0xef, 0x37, 0x7c, 0x46, 0x78, 0xa3, 0x76, 0xbc, 0x81, 0x7b, 0xa1, 0x02, 0x3a,
0xaa, 0x59, 0x0f, 0x35, 0xee, 0xa2, 0xdd, 0xab, 0x5a, 0x2f, 0xe4, 0x26, 0xf4, 0x97, 0x7c, 0x4b,
0xfa, 0xcb, 0xeb, 0xc5, 0x67, 0x68, 0xd9, 0xe6, 0xea, 0x24, 0xdc, 0x26, 0x8f, 0x47, 0x03, 0xe1,
0x61, 0x5a, 0xe1, 0xce, 0x78, 0x9c, 0xbb, 0xbd, 0x52, 0xab, 0x65, 0xd3, 0x2d, 0x9a, 0x4f, 0x3f,
0xf3, 0xb3, 0x3d, 0xab, 0x97, 0x0f, 0x7d, 0xab, 0xfe, 0xc4, 0x5b, 0x98, 0x7e, 0xe2, 0xf7, 0x2b,
0xfd, 0xd7, 0xfc, 0x7e, 0x75, 0xea, 0x8f, 0x9a, 0x54, 0x2f, 0xc3, 0x6d, 0xef, 0x81, 0xd0, 0xdd,
0xf5, 0x98, 0x10, 0x3f, 0x2d, 0x73, 0x3f, 0x29, 0xdc, 0x8c, 0xbb, 0x9f, 0xc4, 0x7d, 0xbf, 0x23,
0x71, 0xfa, 0xef, 0x23, 0x13, 0x21, 0xc0, 0xf5, 0x6f, 0xfb, 0x29, 0xdc, 0xfc, 0x39, 0xa1, 0x60,
0x23, 0x14, 0x50, 0x0e, 0xb3, 0x9f, 0xc0, 0xb2, 0x9b, 0x10, 0xc8, 0x22, 0x04, 0x70, 0x23, 0x5e,
0x84, 0x40, 0x3f, 0x66, 0xc3, 0xbb, 0x18, 0x13, 0x10, 0xe2, 0xec, 0x27, 0xd0, 0x6f, 0xc6, 0x04,
0x2e, 0x62, 0x04, 0x9a, 0xd5, 0xed, 0xe4, 0x7a, 0x92, 0xfc, 0xe2, 0xf3, 0x56, 0xe2, 0xcd, 0xf5,
0x20, 0xf1, 0xe1, 0xf1, 0x69, 0xd5, 0x4b, 0x3c, 0xf2, 0x87, 0x79, 0x44, 0xdf, 0xff, 0xd7, 0xc8,
0x5b, 0xb8, 0x4e, 0x6f, 0xa8, 0xe3, 0xab, 0xf5, 0xf6, 0xba, 0xbe, 0x7b, 0x15, 0xae, 0xeb, 0xb1,
0x9a, 0xf8, 0x69, 0x11, 0x6a, 0xf7, 0xb4, 0x08, 0xc5, 0x2f, 0xd2, 0xfa, 0xf9, 0x84, 0x53, 0x5a,
0xcb, 0xb6, 0xef, 0x87, 0xc6, 0xd7, 0xbd, 0xd0, 0xbb, 0x78, 0xa5, 0xc2, 0x53, 0x5f, 0xd5, 0xdd,
0x62, 0x89, 0xcf, 0xfa, 0x4f, 0x3c, 0x4d, 0x5d, 0xf5, 0x9b, 0xf6, 0xfe, 0x67, 0x8d, 0x89, 0xc9,
0x0b, 0x8d, 0xf0, 0x44, 0xf1, 0xd9, 0x98, 0xf1, 0xe8, 0xea, 0xd1, 0x40, 0xce, 0x0f, 0x18, 0xc8,
0x3f, 0xce, 0xdb, 0x6b, 0xc5, 0xaf, 0x1d, 0xf8, 0xc7, 0x93, 0x91, 0x9c, 0xff, 0x8d, 0x91, 0x74,
0xbf, 0x1e, 0xd2, 0xf9, 0xaf, 0x78, 0xc0, 0x57, 0xac, 0xfb, 0x5f, 0xff, 0x4e, 0xf7, 0x27, 0x87,
0x74, 0x7f, 0x42, 0xe4, 0x63, 0xbd, 0x9f, 0x7c, 0x47, 0xef, 0xd3, 0x31, 0x7c, 0xec, 0x47, 0xe3,
0xf9, 0xed, 0x80, 0xf1, 0xfc, 0x46, 0xa3, 0x99, 0x0f, 0xe3, 0xb7, 0x3d, 0xc3, 0xd0, 0x32, 0x88,
0xf4, 0x38, 0x35, 0x6e, 0x34, 0x90, 0xf0, 0xd0, 0xfe, 0x27, 0xac, 0xb8, 0x38, 0xa0, 0xeb, 0x8b,
0x7a, 0xf3, 0xb0, 0xae, 0xf9, 0x19, 0x86, 0xf3, 0x11, 0x5c, 0x7c, 0xf7, 0x08, 0x66, 0xe3, 0xf8,
0xe3, 0x10, 0x91, 0xfc, 0x81, 0xe7, 0xa0, 0xc5, 0x64, 0xf2, 0xc7, 0xf7, 0xaf, 0x88, 0x4d, 0xf7,
0x61, 0x64, 0xc0, 0xc9, 0xbe, 0xff, 0xcf, 0xcd, 0x4d, 0xdb, 0x6c, 0xd4, 0x59, 0xbd, 0x6c, 0x3b,
0xd6, 0x5f, 0xfa, 0xdb, 0x19, 0xc1, 0xbb, 0xf5, 0xd6, 0x82, 0xf0, 0x53, 0x95, 0x93, 0xb2, 0x8c,
0x59, 0x11, 0x54, 0x08, 0x45, 0x4c, 0xe9, 0x6a, 0x4e, 0x45, 0x47, 0x88, 0x3c, 0xa9, 0x21, 0x89,
0x10, 0xbc, 0xbb, 0x99, 0x12, 0x7c, 0x9e, 0xde, 0x38, 0x6d, 0x1e, 0x1b, 0xe1, 0x8c, 0x60, 0x74,
0x9a, 0x92, 0x28, 0x9c, 0xda, 0xcb, 0x1d, 0xd7, 0xf8, 0x71, 0x5c, 0x22, 0xa4, 0xcb, 0x0f, 0x23,
0x39, 0x7c, 0x80, 0x1c, 0x56, 0xf7, 0xbc, 0xae, 0x06, 0xd1, 0x0d, 0x8f, 0x89, 0xae, 0x2e, 0xf8,
0xf1, 0x56, 0x47, 0xe9, 0x0f, 0x2f, 0x5f, 0x48, 0x93, 0x59, 0x53, 0x5d, 0x9d, 0xf0, 0x8b, 0x0c,
0x69, 0x96, 0x91, 0x16, 0xa6, 0x3a, 0x59, 0xb7, 0xf0, 0x8f, 0x68, 0xa3, 0x8b, 0x34, 0xb1, 0xd5,
0x1f, 0x8b, 0xfb, 0x56, 0xd1, 0x3e, 0x16, 0x69, 0x90, 0x49, 0x83, 0x73, 0x7e, 0xbf, 0x9c, 0xa2,
0xed, 0x2a, 0xd2, 0xce, 0x55, 0xa7, 0xfc, 0xa6, 0xc5, 0x73, 0x75, 0xe4, 0x62, 0x6d, 0x7c, 0x75,
0xf1, 0xa5, 0x6d, 0xef, 0xd5, 0x91, 0x8f, 0xb5, 0xc8, 0xab, 0x5f, 0x1e, 0x89, 0x4d, 0x8b, 0x46,
0x1d, 0xe5, 0xb1, 0x36, 0x85, 0xf4, 0xd4, 0xad, 0x56, 0x44, 0xa9, 0x88, 0xb5, 0x2a, 0xab, 0xf3,
0x7a, 0xb1, 0xbc, 0x5a, 0x7d, 0x51, 0x47, 0x65, 0x94, 0x83, 0x69, 0x75, 0xd1, 0xd4, 0x4b, 0xe2,
0x60, 0x9c, 0xc9, 0x5a, 0x9a, 0xfc, 0x69, 0xa8, 0x55, 0x94, 0xd1, 0xda, 0x54, 0xaf, 0xeb, 0x6b,
0x62, 0xa3, 0x8e, 0x32, 0x5a, 0xdb, 0xea, 0xf4, 0x23, 0x1e, 0xd8, 0x7a, 0xa4, 0xa3, 0xbc, 0xd6,
0x59, 0x68, 0x73, 0xda, 0x51, 0xb3, 0x28, 0xab, 0xb5, 0xab, 0xce, 0x1f, 0x96, 0xcb, 0xc5, 0xf2,
0x96, 0x5a, 0x45, 0x99, 0xad, 0x7d, 0x75, 0xf9, 0x85, 0xd6, 0x47, 0x87, 0x2e, 0xa3, 0x0c, 0xd7,
0xf9, 0xb6, 0x15, 0x49, 0x4e, 0x47, 0x99, 0xae, 0x8b, 0x6d, 0xbb, 0x30, 0xd3, 0x28, 0xe7, 0x75,
0xb9, 0x23, 0xf9, 0x9a, 0x1a, 0x46, 0xd9, 0x6f, 0x88, 0xfd, 0xf7, 0xf5, 0x9a, 0x47, 0x68, 0xa2,
0x12, 0x30, 0x7a, 0xdb, 0xea, 0xcd, 0xf2, 0x33, 0xb5, 0x8c, 0x2f, 0x77, 0x33, 0x6e, 0xf9, 0xdf,
0xd4, 0x34, 0x2a, 0x0d, 0x63, 0xab, 0x8b, 0xcd, 0x7a, 0x75, 0x85, 0x9e, 0xa3, 0xe2, 0x30, 0xd9,
0xd0, 0x08, 0xf2, 0x30, 0x51, 0x79, 0x18, 0x37, 0xb4, 0x43, 0x9f, 0x51, 0x81, 0x18, 0x1f, 0x14,
0x96, 0xa9, 0x45, 0x25, 0x62, 0xf2, 0xea, 0xd5, 0xf2, 0x7a, 0xbd, 0x82, 0xee, 0x9b, 0xa8, 0x3c,
0x4c, 0x31, 0x2c, 0x27, 0x13, 0x15, 0x84, 0x29, 0x43, 0x1b, 0x92, 0xac, 0x89, 0x4a, 0xc1, 0xa6,
0x43, 0xab, 0x41, 0x5f, 0x6c, 0x54, 0x16, 0x56, 0x87, 0xb6, 0xaf, 0x69, 0xab, 0x20, 0xb3, 0x62,
0xa3, 0xb2, 0xb0, 0x66, 0xa0, 0x4a, 0x4b, 0xc0, 0xc6, 0xcd, 0x8f, 0xdd, 0xad, 0x78, 0x96, 0xae,
0x8d, 0x4a, 0xc3, 0x66, 0xa2, 0xf7, 0x37, 0x0f, 0xc4, 0x3e, 0x1b, 0x15, 0x86, 0x75, 0xd5, 0xe5,
0xba, 0xbe, 0xb9, 0x21, 0x13, 0xf2, 0x3b, 0xec, 0x3b, 0xb5, 0x8d, 0x4a, 0xc4, 0x0e, 0x06, 0xe9,
0xfc, 0xc8, 0x46, 0xe5, 0x61, 0xf3, 0xad, 0xb6, 0x91, 0x0d, 0xb0, 0x51, 0x89, 0xd8, 0xa2, 0x22,
0x5f, 0xf1, 0x05, 0xfb, 0x4c, 0x47, 0x36, 0x2a, 0x14, 0x5b, 0x6e, 0xc9, 0x91, 0x58, 0x6c, 0x54,
0x2c, 0x99, 0xd8, 0xa6, 0x65, 0xbb, 0x26, 0xa3, 0x1b, 0x15, 0x48, 0xa6, 0x2b, 0x9e, 0xe5, 0xc7,
0x15, 0x36, 0xa5, 0xa3, 0x2c, 0x2a, 0x8f, 0x8c, 0x2c, 0xd4, 0x62, 0xdd, 0x7e, 0x59, 0xad, 0x3f,
0xf5, 0xd4, 0x2e, 0x2a, 0x90, 0xcc, 0x8e, 0xda, 0xd1, 0x00, 0xb3, 0xf8, 0xc6, 0x00, 0x63, 0xb5,
0xa6, 0x48, 0xeb, 0xae, 0x06, 0xc5, 0xa8, 0x40, 0x32, 0xc7, 0x14, 0x69, 0xc9, 0xd0, 0x2e, 0xca,
0xb3, 0x89, 0xca, 0x23, 0xf3, 0xd5, 0xaf, 0xe1, 0xb9, 0xaa, 0xd4, 0x2c, 0x2a, 0x91, 0x2c, 0x1f,
0x9e, 0xc3, 0x46, 0xad, 0xa2, 0xf2, 0xc8, 0x0a, 0xd9, 0xb9, 0xde, 0xbc, 0xa7, 0x56, 0x51, 0x71,
0x64, 0x65, 0x68, 0xf5, 0x86, 0x5a, 0x45, 0x85, 0xe1, 0x52, 0x69, 0xf5, 0x9e, 0x68, 0xb9, 0xa8,
0x30, 0x9c, 0x0e, 0xad, 0x88, 0x96, 0x8b, 0x4a, 0xc2, 0x91, 0x66, 0x90, 0xcb, 0xf1, 0x40, 0x4c,
0x73, 0x51, 0x31, 0x38, 0x5b, 0xfd, 0x56, 0xe3, 0x61, 0xbb, 0xf0, 0xff, 0x8f, 0x5c, 0x54, 0x08,
0x2e, 0xa3, 0xd5, 0xbe, 0x90, 0x77, 0x1c, 0x07, 0x7b, 0xe0, 0xe2, 0x5b, 0xb4, 0xdb, 0x35, 0x96,
0x5d, 0xdf, 0x45, 0x45, 0xe1, 0xfc, 0xae, 0xad, 0x18, 0x7c, 0x17, 0x95, 0x87, 0xcb, 0x65, 0x11,
0xb2, 0x40, 0x5c, 0x54, 0x20, 0xae, 0xa8, 0xde, 0x9c, 0xfe, 0x2f, 0xb5, 0x88, 0x0a, 0xc3, 0x95,
0xd5, 0xdb, 0x87, 0x6e, 0xb3, 0x50, 0xa7, 0xab, 0xbb, 0x96, 0x96, 0x80, 0x8b, 0x0a, 0xc4, 0xef,
0xb4, 0x03, 0x3b, 0xb3, 0x8f, 0xca, 0xc4, 0xeb, 0x2a, 0xb8, 0x2e, 0x81, 0x3d, 0x3e, 0x2a, 0x18,
0x6f, 0xaa, 0xf7, 0x7d, 0xb3, 0xe8, 0xf0, 0xb8, 0x5a, 0x6a, 0x17, 0x95, 0x8d, 0xb7, 0xd5, 0xd9,
0x7a, 0x41, 0x2c, 0x31, 0xa9, 0x76, 0xd4, 0x30, 0x2a, 0x1c, 0x9f, 0x55, 0xff, 0x7a, 0xb8, 0xbd,
0xc5, 0xf6, 0xe6, 0xa3, 0x42, 0xf1, 0xae, 0x3a, 0xab, 0xbb, 0x76, 0xc3, 0x7d, 0xc6, 0x3d, 0x27,
0x2f, 0x4a, 0x44, 0x5d, 0x62, 0xba, 0x51, 0x51, 0xf8, 0x5c, 0x8c, 0xe4, 0x97, 0xfa, 0x73, 0x4b,
0x0b, 0xcc, 0x47, 0x85, 0xe1, 0x8b, 0xea, 0xe4, 0xec, 0x2d, 0xb5, 0x88, 0x0a, 0xc3, 0x97, 0xd4,
0x65, 0xd7, 0xa9, 0x77, 0x78, 0x54, 0xb4, 0x2a, 0xa8, 0x69, 0x54, 0x1a, 0x79, 0x5a, 0x49, 0x2b,
0x60, 0x8a, 0xe4, 0xb9, 0x45, 0xa5, 0x91, 0xeb, 0x5d, 0x43, 0x9a, 0x47, 0x1e, 0x95, 0x45, 0x6e,
0x76, 0x0d, 0x2d, 0x35, 0x8c, 0x0a, 0x23, 0xb7, 0xbb, 0x86, 0x19, 0x35, 0x8c, 0x0a, 0x23, 0x0f,
0xdb, 0xc7, 0x66, 0xf0, 0x89, 0xf2, 0xa8, 0x48, 0x72, 0x57, 0xfd, 0x5e, 0x7f, 0x42, 0x93, 0xa8,
0x3c, 0x72, 0x5f, 0xbd, 0x6d, 0x37, 0x2d, 0x29, 0xc7, 0x51, 0x1e, 0x77, 0x66, 0xf3, 0xea, 0xe2,
0x6e, 0xb5, 0x22, 0xff, 0x7b, 0xdb, 0x36, 0x2a, 0x8f, 0xbc, 0x80, 0xbf, 0xda, 0x7d, 0xa9, 0x1f,
0xa9, 0xd5, 0x48, 0x26, 0xc0, 0xd9, 0x38, 0x20, 0xd8, 0x85, 0x13, 0x17, 0xa4, 0x1a, 0x7d, 0xdb,
0xac, 0x96, 0xd7, 0xf5, 0xfa, 0x51, 0x89, 0x8a, 0x6e, 0x56, 0xea, 0xa5, 0x3c, 0xa4, 0x58, 0x62,
0xc6, 0xf0, 0xff, 0x16, 0x67, 0xba, 0xa0, 0x30, 0xa2, 0xe2, 0xd8, 0xf1, 0xe5, 0x0b, 0x39, 0x57,
0x7d, 0xeb, 0x02, 0x8a, 0x9d, 0x4e, 0xf0, 0x3c, 0xeb, 0x83, 0x2f, 0x30, 0x3f, 0x04, 0x55, 0x3b,
0xf8, 0x0a, 0xfb, 0x03, 0x34, 0xe9, 0x8e, 0xa6, 0x71, 0xf0, 0x25, 0xd9, 0x0f, 0xb4, 0x65, 0xd7,
0xf7, 0xea, 0xec, 0xc5, 0xc5, 0xee, 0x1a, 0x62, 0xc0, 0x37, 0x2e, 0x73, 0x74, 0x19, 0xb1, 0x2d,
0xf4, 0x06, 0x76, 0xcd, 0x86, 0x3a, 0xc4, 0xc1, 0x37, 0xf7, 0x1f, 0x04, 0xaf, 0x7a, 0x5d, 0xf7,
0x1b, 0xbc, 0xdf, 0x3d, 0x28, 0xe7, 0xfe, 0x60, 0xee, 0xf5, 0xd9, 0x28, 0x98, 0x3b, 0x7b, 0x2e,
0x98, 0xfb, 0xa5, 0xbd, 0xa9, 0xc9, 0xb4, 0x45, 0x43, 0xb9, 0xc1, 0x48, 0x3d, 0x52, 0x2c, 0x1a,
0x8d, 0xe6, 0x86, 0xe1, 0x4b, 0xa4, 0xf5, 0x7e, 0xd9, 0x3d, 0x46, 0xa3, 0xba, 0x13, 0x32, 0x76,
0xd7, 0x0a, 0xef, 0xc3, 0x19, 0x18, 0xbc, 0x7f, 0xfb, 0x63, 0xb6, 0x30, 0xbd, 0xfe, 0x39, 0x82,
0x6e, 0x47, 0x70, 0x77, 0x41, 0x34, 0xca, 0x3b, 0xab, 0xd7, 0x9b, 0x18, 0x25, 0x32, 0x50, 0xdd,
0xea, 0xe1, 0x3a, 0x1a, 0xdb, 0xfd, 0x5e, 0x7f, 0xae, 0xa3, 0x21, 0xdd, 0xfb, 0xa6, 0xad, 0x97,
0xf1, 0x60, 0xee, 0xf5, 0x6a, 0xdd, 0xf6, 0x51, 0x1e, 0xeb, 0x21, 0x22, 0x8c, 0x07, 0x71, 0x83,
0x0b, 0x8c, 0x77, 0xb3, 0xdd, 0xc7, 0xe4, 0x40, 0x91, 0xdc, 0xc5, 0xc3, 0xb2, 0x6f, 0xa3, 0x1d,
0x65, 0xd5, 0xf9, 0xe2, 0x73, 0xbb, 0xbc, 0x6e, 0xbb, 0x2e, 0x1e, 0xc1, 0x51, 0x64, 0xde, 0x7e,
0x8d, 0x76, 0xe1, 0xe1, 0x4b, 0xaa, 0x7f, 0x2a, 0x78, 0x93, 0xf1, 0xc8, 0xed, 0x3f, 0xfc, 0x3c,
0xfe, 0xd5, 0x43, 0x74, 0x24, 0x05, 0x85, 0x12, 0x75, 0xb7, 0xba, 0x25, 0xef, 0x30, 0x1e, 0xac,
0x5d, 0xdc, 0xc3, 0x99, 0x8f, 0xc7, 0x68, 0x67, 0xa4, 0x09, 0x6d, 0x6c, 0x26, 0x88, 0xce, 0x98,
0x19, 0x26, 0x1e, 0x94, 0x9d, 0xb4, 0x6d, 0x13, 0xa5, 0x6f, 0xab, 0x7f, 0x2f, 0x96, 0x9b, 0xfa,
0x36, 0xba, 0xea, 0x33, 0xd2, 0x9d, 0x7b, 0x3c, 0xac, 0x77, 0x1d, 0x6d, 0x02, 0x7b, 0xbd, 0xbc,
0xee, 0x9b, 0x3a, 0x2a, 0x33, 0xc4, 0x5e, 0x6d, 0x1d, 0x1f, 0x05, 0x59, 0xea, 0x8f, 0xed, 0xfa,
0x2a, 0x2a, 0x53, 0x0a, 0xb7, 0x7e, 0x8b, 0xab, 0x2f, 0x05, 0x5a, 0x38, 0xeb, 0xb3, 0x78, 0x88,
0xf5, 0xcb, 0xfa, 0xf1, 0x4b, 0x94, 0x3a, 0x85, 0x55, 0xff, 0x7a, 0x88, 0x71, 0x98, 0x22, 0xa9,
0x5f, 0xd7, 0xcf, 0xb2, 0x88, 0x82, 0xa8, 0xf3, 0xf6, 0xcb, 0xc7, 0x45, 0x3c, 0x70, 0xba, 0x6c,
0xd7, 0x9b, 0x45, 0xdc, 0x0a, 0x58, 0x71, 0xd1, 0xe3, 0x51, 0xd2, 0x9b, 0xa6, 0xbd, 0x79, 0xa6,
0x01, 0xe9, 0xf4, 0x63, 0xbd, 0x8c, 0x9e, 0x2e, 0xc4, 0x3d, 0x54, 0x67, 0xb4, 0xf3, 0xc6, 0xa3,
0xa2, 0x57, 0x0f, 0x9b, 0x87, 0xbb, 0x98, 0x72, 0x53, 0x34, 0xf4, 0x96, 0x18, 0x40, 0x5c, 0x88,
0x07, 0x42, 0xd4, 0x60, 0xdd, 0xc6, 0x0c, 0x0b, 0xc5, 0x3f, 0xa4, 0x2d, 0x77, 0xf5, 0x6d, 0x3c,
0xee, 0xa1, 0xf3, 0x57, 0xdd, 0x43, 0x3c, 0xda, 0x79, 0x2f, 0x40, 0xec, 0x3f, 0xd5, 0x65, 0x5b,
0xc7, 0x84, 0x45, 0x64, 0x2e, 0x17, 0xf5, 0x5d, 0x1d, 0x93, 0x34, 0x91, 0x79, 0x75, 0xbf, 0x5e,
0x90, 0x57, 0x05, 0x8e, 0xec, 0xdb, 0xe5, 0x67, 0xa0, 0xf0, 0x87, 0x43, 0x40, 0xe1, 0x80, 0xd5,
0x5e, 0xdc, 0xb7, 0xed, 0xf5, 0x53, 0x54, 0xf8, 0xc3, 0xa1, 0xa8, 0xf0, 0x87, 0x3d, 0xe0, 0xf8,
0x9b, 0xbf, 0xd0, 0xff, 0x9b, 0xe5, 0xa6, 0x5d, 0xf6, 0x8b, 0xcd, 0xe3, 0x93, 0x31, 0xbc, 0xf9,
0x9e, 0x31, 0xcc, 0xf0, 0xe9, 0xb3, 0x29, 0x3e, 0xfd, 0xba, 0xfe, 0xbc, 0x5a, 0x03, 0x0b, 0x3f,
0x23, 0x9b, 0xdf, 0x6e, 0xfa, 0x27, 0x18, 0xf5, 0x7d, 0xdf, 0x6d, 0x3d, 0x00, 0x7e, 0x29, 0x1c,
0xbf, 0x0e, 0x2d, 0x86, 0x52, 0xcf, 0x6a, 0x69, 0x62, 0xd9, 0xc3, 0xfb, 0x11, 0x1c, 0x1c, 0x5e,
0xbb, 0xf7, 0x77, 0x41, 0xeb, 0xfb, 0x7e, 0xf9, 0x94, 0xe8, 0xdf, 0x05, 0xae, 0xef, 0xfb, 0x7e,
0x3a, 0x79, 0x7e, 0xb7, 0xf3, 0x73, 0x34, 0x47, 0xd5, 0x11, 0x33, 0x92, 0x60, 0xac, 0x48, 0x9e,
0x09, 0xbf, 0x09, 0xae, 0xcf, 0x6b, 0x35, 0x7a, 0x83, 0xc3, 0xb0, 0x9b, 0xb0, 0x78, 0x75, 0x10,
0x6f, 0x90, 0xee, 0xce, 0x73, 0x3d, 0xc5, 0x78, 0xd4, 0x96, 0xd7, 0x70, 0xc3, 0xea, 0xfb, 0xfb,
0xee, 0x71, 0xb2, 0xb2, 0xf8, 0xf4, 0xd5, 0xea, 0x4f, 0x15, 0xde, 0x4f, 0x15, 0xd2, 0x88, 0xa7,
0x95, 0xb8, 0xba, 0x3f, 0x1e, 0xd0, 0xf8, 0x35, 0x35, 0x6e, 0x79, 0x4d, 0xf6, 0x8a, 0x36, 0x86,
0xfd, 0x57, 0x70, 0xcb, 0x13, 0x6a, 0x79, 0xb5, 0xcd, 0x35, 0x1e, 0xef, 0x46, 0x0a, 0x3f, 0x4c,
0x6d, 0x3e, 0xae, 0x57, 0x0f, 0xb7, 0x1f, 0xd5, 0xbd, 0x2c, 0x31, 0xb5, 0x63, 0x43, 0xf3, 0xa8,
0x85, 0xc0, 0x99, 0x3e, 0x98, 0x0d, 0xec, 0xa4, 0x8f, 0x28, 0x98, 0x40, 0xc1, 0xec, 0xa1, 0xe0,
0xf6, 0x52, 0xf8, 0x51, 0x7d, 0x02, 0x5a, 0x85, 0x8d, 0x4c, 0x21, 0x79, 0x32, 0xa6, 0x76, 0x19,
0xa8, 0x5d, 0xee, 0x1b, 0x8f, 0x71, 0xa2, 0x78, 0x4e, 0x34, 0xcf, 0x3b, 0xd4, 0x02, 0x04, 0xa2,
0x77, 0xfd, 0x4f, 0xcf, 0x30, 0xe8, 0xf4, 0x3f, 0xdb, 0x75, 0xc4, 0xaf, 0x12, 0x1b, 0x65, 0x35,
0x9e, 0xf1, 0xb8, 0xcf, 0xa9, 0xe1, 0x2b, 0x96, 0xec, 0x15, 0xc5, 0x3f, 0x24, 0xb8, 0xe5, 0xcd,
0xe2, 0x76, 0xe7, 0x6b, 0x83, 0xc4, 0x4c, 0xbb, 0xdf, 0x4d, 0xb5, 0xfb, 0x72, 0x71, 0x47, 0xc2,
0xe4, 0x6d, 0xe3, 0x89, 0x62, 0x2f, 0x27, 0xe9, 0x6b, 0xbc, 0x51, 0xf0, 0x2f, 0x16, 0x14, 0x6c,
0x29, 0x02, 0x6c, 0x7a, 0xa8, 0x3b, 0x1a, 0xe6, 0xf5, 0xe2, 0x4e, 0x41, 0x03, 0x0e, 0x58, 0x5c,
0xef, 0x4e, 0xe5, 0x62, 0xdd, 0x53, 0x18, 0xc6, 0xaf, 0xe0, 0x50, 0xbc, 0x38, 0xae, 0x87, 0x04,
0xe1, 0x11, 0x2a, 0x40, 0x1c, 0xb1, 0xfb, 0x87, 0x1f, 0x95, 0xa1, 0xd5, 0xc7, 0x67, 0x37, 0xf5,
0xfa, 0x96, 0x1c, 0xe7, 0xf1, 0x5a, 0xdb, 0xda, 0xd8, 0x77, 0x07, 0xd8, 0xd8, 0x5f, 0x62, 0xe9,
0xc7, 0x77, 0x5b, 0x44, 0x64, 0xb2, 0x64, 0x76, 0xca, 0x37, 0x33, 0xe8, 0x97, 0x87, 0x18, 0xf4,
0x4b, 0x19, 0xed, 0x2e, 0x0b, 0xff, 0x74, 0x57, 0xb9, 0x7c, 0xd6, 0xa2, 0x4f, 0x6c, 0xb8, 0x98,
0x90, 0x17, 0xa8, 0x0a, 0xd8, 0xfe, 0x22, 0xef, 0x2c, 0x92, 0x6a, 0x84, 0x1b, 0x85, 0x17, 0x5c,
0xd6, 0x57, 0xe4, 0xc8, 0xfe, 0x74, 0xd5, 0xd5, 0xcb, 0x4f, 0xd4, 0x4a, 0xce, 0xd3, 0x3f, 0x78,
0x69, 0x03, 0xde, 0xe0, 0xb0, 0xb9, 0xeb, 0xaa, 0xff, 0x07, 0x89, 0xc6, 0x88, 0x77, 0x0c, 0x8c,
};

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,23 +1,15 @@
/*
* Settings html
*/
//common CSS of settings pages
const char PAGE_settingsCss[] PROGMEM = R"=====(
body{font-family:var(--cFn),sans-serif;text-align:center;background:var(--cCol);color:var(--tCol);line-height:200%;margin:0;background-attachment:fixed}hr{border-color:var(--dCol);filter:drop-shadow(-5px -5px 5px var(--sCol))}button{background:var(--bCol);color:var(--tCol);font-family:var(--cFn),sans-serif;border:.3ch solid var(--bCol);display:inline-block;filter:drop-shadow(-5px -5px 5px var(--sCol));font-size:20px;margin:8px;margin-top:12px}.helpB{text-align:left;position:absolute;width:60px}input{background:var(--bCol);color:var(--tCol);font-family:var(--cFn),sans-serif;border:.5ch solid var(--bCol);filter:drop-shadow(-5px -5px 5px var(--sCol))}input[type=number]{width:4em}select{background:var(--bCol);color:var(--tCol);font-family:var(--cFn),sans-serif;border:0.5ch solid var(--bCol);filter:drop-shadow( -5px -5px 5px var(--sCol) );}</style>
)=====";
const char PAGE_settingsCss[] PROGMEM = R"=====(<style>body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%%;margin:0}hr{border-color:#666}button{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}.helpB{text-align:left;position:absolute;width:60px}input{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.5ch solid #333}input[type=number]{width:4em}select{background:#333;color:#fff;font-family:Verdana,sans-serif;border:0.5ch solid #333}td{padding:2px;}</style>)=====";
//settings menu
const char PAGE_settings0[] PROGMEM = R"=====(
<!DOCTYPE html>
<html><head><title>WLED Settings</title>
)=====";
const char PAGE_settings1[] PROGMEM = R"=====(
body{text-align:center;background:var(--cCol);height:100%;margin:0;background-attachment:fixed}html{--h:11.55vh}button{background:var(--bCol);color:var(--tCol);font-family:var(--cFn),Helvetica,sans-serif;border:.3ch solid var(--bCol);display:inline-block;filter:drop-shadow(-5px -5px 5px var(--sCol));font-size:8vmin;height:var(--h);width:95%;margin-top:2.4vh}</style>
<script>function BB(){if(window.frameElement){document.getElementById("b").style.display="none";document.documentElement.style.setProperty("--h","13.86vh")}};</script>
</head>
const char PAGE_settings[] PROGMEM = R"=====(<!DOCTYPE html>
<html><head><title>WLED Settings</title><style>body{text-align:center;background:#222;height:100%;margin:0}html{--h:11.55vh}button{background:#333;color:#fff;font-family:Verdana,Helvetica,sans-serif;border:.3ch solid #333;display:inline-block;font-size:8vmin;height:var(--h);width:95%;margin-top:2.4vh}</style>
<script>function BB(){if(window.frameElement){document.getElementById("b").style.display="none";document.documentElement.style.setProperty("--h","13.86vh")}};</script></head>
<body onload=BB()>
<form action=/><button type=submit id=b>Back</button></form>
<form action=/settings/wifi><button type=submit>WiFi Setup</button></form>
@@ -26,24 +18,17 @@ body{text-align:center;background:var(--cCol);height:100%;margin:0;background-at
<form action=/settings/sync><button type=submit>Sync Interfaces</button></form>
<form action=/settings/time><button type=submit>Time & Macros</button></form>
<form action=/settings/sec><button type=submit>Security & Updates</button></form>
</body>
</html>
)=====";
</body></html>)=====";
//wifi settings
const char PAGE_settings_wifi0[] PROGMEM = R"=====(
<!DOCTYPE html>
<html><head><meta name="viewport" content="width=500">
<title>WiFi Settings</title><script>function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#wifi-settings");}function B(){window.history.back();}function GetV(){var d = document;
)=====";
const char PAGE_settings_wifi1[] PROGMEM = R"=====(
</head>
<body onload="GetV()">
const char PAGE_settings_wifi[] PROGMEM = R"=====(<!DOCTYPE html>
<html><head><meta name="viewport" content="width=500"><meta charset="utf-8">
<title>WiFi Settings</title><script>function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#wifi-settings");}function B(){window.history.back();}function GetV(){var d=document;
%CSS%%SCSS%</head><body onload="GetV()">
<form id="form_s" name="Sf" method="post">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save & Reboot</button><hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save & Connect</button><hr>
<h2>WiFi setup</h2>
<h3>Connect to existing network</h3>
Network name (SSID, empty to not connect): <br><input name="CS" maxlength="32"><br>
@@ -65,178 +50,136 @@ Static subnet mask:<br>
<input name="S3" type="number" min="0" max="255" required><br>
mDNS address (leave empty for no mDNS):<br/>
http:// <input name="CM" maxlength="32"> .local<br>
Try connecting before opening AP for: <input name="AT" type="number" min="0" max="255" required> s <br>
Client IP: <span class="sip"> Not connected </span><br>
<h3>Configure Access Point</h3>
AP SSID (leave empty for no AP):<br> <input name="AS" maxlength="32"><br>
AP name (SSID):<br><input name="AS" maxlength="32"><br>
Hide AP name: <input type="checkbox" name="AH"><br>
AP password (leave empty for open):<br> <input type="password" name="AP" maxlength="63"> <br>
AP password (leave empty for open):<br> <input type="password" name="AP" maxlength="63"><br>
Access Point WiFi channel: <input name="AC" type="number" min="1" max="13" required><br>
AP opens:
<select name="AB">
<option value="0">No connection after boot</option>
<option value="1">Disconnected</option>
<option value="2">Always</option>
<option value="3">Never (not recommended)</option></select><br>
AP IP: <span class="sip"> Not active </span><hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save & Reboot</button>
<button type="button" onclick="B()">Back</button><button type="submit">Save & Connect</button>
</form>
</body>
</html>
)=====";
</html>)=====";
//LED settings
const char PAGE_settings_leds0[] PROGMEM = R"=====(
<!DOCTYPE html>
<html><head><meta name="viewport" content="width=500">
<title>LED Settings</title><script>function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#led-settings");}function B(){window.history.back();}function S(){GetV();UI();}function UI(){
var myC=document.querySelectorAll('.wc'),l=myC.length;
for (i = 0; i < l; i++){myC[i].style.display=(document.getElementById('rgbw').checked)?'inline':'none';}
var val=Math.ceil((100+document.Sf.LC.value*55)/500)/2;
val=(val>5)?Math.ceil(val):val;var s="";
if (val<1.1){s="ESP 5V pin with 1A USB supply";}else{s="External 5V ";s+=val;s+="A supply connected to LEDs";}
document.getElementById('psu').innerHTML=s;document.getElementById('ps2').innerHTML=val+"A = "+val*1000;
}function GetV(){var d = document;
)=====";
const char PAGE_settings_leds1[] PROGMEM = R"=====(
</head>
<body onload="S()">
<form id="form_s" name="Sf" method="post">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><hr>
const char PAGE_settings_leds[] PROGMEM = R"=====(<!DOCTYPE html>
<html><head>
<meta charset=utf-8>
<meta name=viewport content="width=500">
<title>LED Settings</title>
<script>var d=document,laprev=55;function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#led-settings")}function B(){window.open("/settings","_self")}function S(){GetV();setABL()}function enABL(){var a=d.getElementById("able").checked;d.Sf.LA.value=(a)?laprev:0;d.getElementById("abl").style.display=(a)?"inline":"none";d.getElementById("psu2").style.display=(a)?"inline":"none";if(d.Sf.LA.value>0){setABL()}}function enLA(){var a=d.Sf.LAsel.value;d.Sf.LA.value=a;d.getElementById("LAdis").style.display=(a==50)?"inline":"none";UI()}function setABL(){d.getElementById("able").checked=true;d.Sf.LAsel.value=50;switch(parseInt(d.Sf.LA.value)){case 0:d.getElementById("able").checked=false;enABL();break;case 30:d.Sf.LAsel.value=30;break;case 35:d.Sf.LAsel.value=35;break;case 55:d.Sf.LAsel.value=55;break;default:d.getElementById("LAdis").style.display="inline"}UI()}function UI(){var b=d.querySelectorAll(".wc"),a=b.length;for(i=0;i<a;i++){b[i].style.display=(d.getElementById("rgbw").checked)?"inline":"none"}d.getElementById("ledwarning").style.display=(d.Sf.LC.value>1000)?"inline":"none";d.getElementById("ampwarning").style.display=(d.Sf.MA.value>7200)?"inline":"none";if(d.Sf.LA.value>0){laprev=d.Sf.LA.value}var j=Math.ceil((100+d.Sf.LC.value*laprev)/500)/2;j=(j>5)?Math.ceil(j):j;var g="";var e=(d.Sf.LAsel.value==30);if(j<1.02&&!e){g="ESP 5V pin with 1A USB supply"}else{g+=e?"12V ":"5V ";g+=j;g+="A supply connected to LEDs"}var h=Math.ceil((100+d.Sf.LC.value*laprev)/1500)/2;h=(h>5)?Math.ceil(h):h;var c="(for most effects, ~";c+=h;c+="A is enough)<br>";d.getElementById("psu").innerHTML=g;d.getElementById("psu2").innerHTML=c}function GetV(){var d=document;
%CSS%%SCSS%</head><body onload=S()>
<form id=form_s name=Sf method=post>
<div class=helpB><button type=button onclick=H()>?</button></div>
<button type=button onclick=B()>Back</button><button type=submit>Save</button><hr>
<h2>LED setup</h2>
LED count: <input name="LC" type="number" min="1" max="1200" oninput=UI() required><br>
LED count: <input name=LC type=number min=1 max=1500 oninput=UI() required><br>
<div id=ledwarning style=color:orange;display:none>
&#9888; You might run into stability or lag issues.<br>
Use less than 1000 LEDs per ESP for the best experience!<br>
</div>
<i>Recommended power supply for brightest white:</i><br>
<b><span id="psu">?</span></b><br><br>
Maximum Current: <input name="MA" type="number" min="250" max="65000" required> mA<br>
<b><span id=psu>?</span></b><br>
<span id=psu2><br></span>
<br>
Enable automatic brightness limiter: <input type=checkbox name=ABen onchange=enABL() id=able><br>
<div id=abl>
Maximum Current: <input name=MA type=number min=250 max=65000 oninput=UI() required> mA<br>
<div id=ampwarning style=color:orange;display:none>
&#9888; Your power supply provides high current.<br>
To improve the safety of your setup,<br>
please use thick cables,<br>
multiple power injection points and a fuse!<br>
</div>
<i>Automatically limits brightness to stay close to the limit.<br>
Keep at &lt;1A if powering LEDs directly from the ESP 5V pin!<br>
If you are using an external 5V supply, enter its rating.<br>
"65000" completely diasbles the power calculation.<br>
(Current estimated usage: <span class="pow">unknown</span>)</i><br><br>
LEDs are 4-channel type (RGBW): <input type="checkbox" name="EW" onchange=UI() id="rgbw"><br>
If you are using an external power supply, enter its rating.<br>
(Current estimated usage: <span class=pow>unknown</span>)</i><br><br>
LED voltage (Max. current for a single LED):<br>
<select name=LAsel onchange=enLA()>
<option value=55 selected>5V default (55mA)</option>
<option value=35>5V efficient (35mA)</option>
<option value=30>12V (30mA)</option>
<option value=50>Custom</option>
</select><br>
<span id=LAdis style=display:none>Custom max. current per LED: <input name=LA type=number min=0 max=255 id=la oninput=UI() required> mA<br></span>
<i>Keep at default if you are unsure about your type of LEDs.</i><br>
</div>
<br>
LEDs are 4-channel type (RGBW): <input type=checkbox name=EW onchange=UI() id=rgbw><br>
<span class=wc>
Auto-calculate white channel from RGB: <input type=checkbox name=AW><br></span>
Color order:
<select name="CO">
<option value="0">GRB</option>
<option value="1">RGB</option>
<option value="2">BRG</option>
<option value="3">RBG</option></select>
<select name=CO>
<option value=0>GRB</option>
<option value=1>RGB</option>
<option value=2>BRG</option>
<option value=3>RBG</option>
</select>
<h3>Defaults</h3>
Turn LEDs on after power up/reset: <input type="checkbox" name="BO"><br><br>
Default RGB color:
<input name="CR" type="number" min="0" max="255" required>
<input name="CG" type="number" min="0" max="255" required>
<input name="CB" type="number" min="0" max="255" required><br>
<span class="wc">Default white value: <input name="CW" type="number" min="0" max="255" required><br>
Auto-calculate white from RGB instead: <input type="checkbox" name="AW"><br></span>
Default brightness: <input name="CA" type="number" min="0" max="255" required> (0-255)<br>
Default effect ID: <input name="FX" type="number" min="0" max="57" required><br>
Default effect speed: <input name="SX" type="number" min="0" max="255" required><br>
Default effect intensity: <input name="IX" type="number" min="0" max="255" required><br>
Default effect palette: <input name="FP" type="number" min="0" max="255" required><br>
Default secondary RGB<span class="wc">W</span>:<br>
<input name="SR" type="number" min="0" max="255" required>
<input name="SG" type="number" min="0" max="255" required>
<input name="SB" type="number" min="0" max="255" required>
<span class="wc"><input name="SW" type="number" min="0" max="255" required></span><br>
Ignore and use current color, brightness and effects: <input type="checkbox" name="IS"><br><br>
Apply preset <input name="BP" type="number" min="0" max="25" required> at boot (0 uses defaults)<br>
Save current preset cycle configuration as boot default: <input type="checkbox" name="PC"><br><br>
Use Gamma correction for color: <input type="checkbox" name="GC"> (strongly recommended)<br>
Use Gamma correction for brightness: <input type="checkbox" name="GB"> (not recommended)<br><br>
Brightness factor: <input name="BF" type="number" min="0" max="255" required> %
Turn LEDs on after power up/reset: <input type=checkbox name=BO><br>
Default brightness: <input name=CA type=number min=0 max=255 required> (0-255)<br><br>
Apply preset <input name=BP type=number min=0 max=16 required> at boot (0 uses defaults)
<br>- <i>or</i> -<br>
Set current preset cycle setting as boot default: <input type=checkbox name=PC><br><br>
Use Gamma correction for color: <input type=checkbox name=GC> (strongly recommended)<br>
Use Gamma correction for brightness: <input type=checkbox name=GB> (not recommended)<br><br>
Brightness factor: <input name=BF type=number min=1 max=255 required> %
<h3>Transitions</h3>
Crossfade: <input type="checkbox" name="TF"><br>
Transition Time: <input name="TD" maxlength="5" size="2"> ms<br>
Enable transition for secondary color: <input type="checkbox" name="T2"><br>
Enable Palette transitions: <input type="checkbox" name="PF">
Crossfade: <input type=checkbox name=TF><br>
Transition Time: <input name=TD maxlength=5 size=2> ms<br>
Enable Palette transitions: <input type=checkbox name=PF>
<h3>Timed light</h3>
Default Duration: <input name="TL" type="number" min="1" max="255" required> min<br>
Default Target brightness: <input name="TB" type="number" min="0" max="255" required><br>
Fade down: <input type="checkbox" name="TW"><br>
Default Duration: <input name=TL type=number min=1 max=255 required> min<br>
Default Target brightness: <input name=TB type=number min=0 max=255 required><br>
Fade down: <input type=checkbox name=TW><br>
<h3>Advanced</h3>
Palette blending:
<select name="PB">
<option value="0">Linear (wrap if moving)</option>
<option value="1">Linear (always wrap)</option>
<option value="2">Linear (never wrap)</option>
<option value="3">None (not recommended)</option>
<select name=PB>
<option value=0>Linear (wrap if moving)</option>
<option value=1>Linear (always wrap)</option>
<option value=2>Linear (never wrap)</option>
<option value=3>None (not recommended)</option>
</select><br>
Reverse LED order (rotate 180): <input type="checkbox" name="RV"><br>
Init LEDs after WiFi: <input type="checkbox" name="EI"><br>
Skip first LED: <input type="checkbox" name="SL"><hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>
</html>
)=====";
Reverse LED order (rotate 180): <input type=checkbox name=RV><br>
Skip first LED: <input type=checkbox name=SL><br>
Disable repeating N LEDs: <input type=number min=0 max=255 name=DL><br>
(Turns off N LEDs between each lit one, spacing out effects)<hr>
<button type=button onclick=B()>Back</button><button type=submit>Save</button>
</form></body></html>)=====";
//User Interface settings
const char PAGE_settings_ui0[] PROGMEM = R"=====(
<!DOCTYPE html>
<html><head><meta name="viewport" content="width=500">
<title>UI Settings</title><script>
function gId(s){return document.getElementById(s);}function S(){GetV();Ct();}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#user-interface-settings");}function B(){window.history.back();}function Ct(){if (gId("co").selected){gId("cth").style.display="block";}else{gId("cth").style.display="none";}}function GetV(){var d = document;
)=====";
const char PAGE_settings_ui1[] PROGMEM = R"=====(
</head>
const char PAGE_settings_ui[] PROGMEM = R"=====(<!DOCTYPE html>
<html><head><meta name="viewport" content="width=500"><meta charset="utf-8"><title>UI Settings</title><script>
function gId(s){return document.getElementById(s);}function S(){GetV();Ct();}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#user-interface-settings");}function B(){window.history.back();}function Ct(){if (gId("co").selected){gId("cth").style.display="block";}else{gId("cth").style.display="none";}}function GetV(){var d=document;
%CSS%%SCSS%</head>
<body onload="S()">
<form id="form_s" name="Sf" method="post">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><hr>
<h2>Web Setup</h2>
User Interface Mode:
<select name="UI">
<option value="0" selected>Auto</option>
<option value="1">Classic</option>
<option value="2">Mobile</option>
</select><br>
Server description: <input name="DS" maxlength="32"><br><br>
<i>The following options are for the classic UI!</i><br>
Use HSB sliders instead of RGB by default: <input type="checkbox" name="MD"><br>
Color Theme:
<select name="TH" onchange="Ct()">
<option value="0" selected>Night</option>
<option value="1">Modern</option>
<option value="2">Bright</option>
<option value="3">Wine</option>
<option value="4">Electric</option>
<option value="5">Mint</option>
<option value="6">Amber</option>
<option value="7">Club</option>
<option value="8">Air</option>
<option value="9">Nixie</option>
<option value="10">Terminal</option>
<option value="11">C64</option>
<option value="12">Easter</option>
<option value="13">Christmas</option>
<option value="14">The End</option>
<option value="15" id="co">Custom</option>
</select><br>
<div id="cth">
Please specify your custom hex colors (e.g. FF0000 for red)<br>
Custom accent color: <input maxlength=9 name="C0"><br>
Custom background: <input maxlength=9 name="C1"><br>
Custom panel color: <input maxlength=9 name="C2"><br>
Custom icon color: <input maxlength=9 name="C3"><br>
Custom shadow: <input maxlength=9 name="C4"><br>
Custom text color: <input maxlength=9 name="C5"><br></div>
Use font: <input maxlength=32 name="CF"><br>
Make sure the font you use is installed on your system!<br>
Server description: <input name="DS" maxlength="32"><br>
Sync button toggles both send and receive: <input type="checkbox" name="ST"><br><br>
<hr><button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>
</html>
)=====";
</html>)=====";
//sync settings
const char PAGE_settings_sync0[] PROGMEM = R"=====(
<!DOCTYPE html>
<html><head><meta name="viewport" content="width=500"><title>Sync Settings</title>
<script>function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#sync-settings");}function B(){window.open("/settings","_self");}function GetV(){var d = document;
)=====";
const char PAGE_settings_sync1[] PROGMEM = R"=====(
</head>
const char PAGE_settings_sync[] PROGMEM = R"=====(<!DOCTYPE html>
<html><head><meta name="viewport" content="width=500"><meta charset="utf-8"><title>Sync Settings</title>
<script>function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#sync-settings");}function B(){window.open("/settings","_self");}function GetV(){var d=document;
%CSS%%SCSS%</head>
<body onload="GetV()">
<form id="form_s" name="Sf" method="post">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
@@ -247,13 +190,14 @@ On/Off button enabled: <input type="checkbox" name="BT"><br>
Infrared receiver enabled: <input type="checkbox" name="IR"><br>
<a href="https://github.com/Aircoookie/WLED/wiki/Infrared-Control" target="_blank">IR info</a>
<h3>WLED Broadcast</h3>
UDP Port: <input name="UP" maxlength="5" size="4"><br>
UDP Port: <input name="UP" type="number" min="1" max="65535" required><br>
Receive <input type="checkbox" name="RB">Brightness, <input type="checkbox" name="RC">Color, and <input type="checkbox" name="RX">Effects<br>
Send notifications on direct change: <input type="checkbox" name="SD"><br>
Send notifications on button press: <input type="checkbox" name="SB"><br>
Send Alexa notifications: <input type="checkbox" name="SA"><br>
Send Philips Hue change notifications: <input type="checkbox" name="SH"><br>
Send notifications twice: <input type="checkbox" name="S2"><br>
Send Macro notifications: <input type="checkbox" name="SM"><br>
Send notifications twice: <input type="checkbox" name="S2">
<h3>Realtime</h3>
Receive UDP realtime: <input type="checkbox" name="RD"><br><br>
<i>E1.31 (sACN)</i><br>
@@ -263,20 +207,26 @@ E1.31 start universe: <input name="EU" type="number" min="1" max="63999" require
Timeout: <input name="ET" type="number" min="1" max="65000" required> ms<br>
Force max brightness: <input type="checkbox" name="FB"><br>
Disable realtime gamma correction: <input type="checkbox" name="RG"><br>
Realtime LED offset: <input name="WO" type="number" min="-255" max="255" required><br>
Enable UI access during realtime: <input type="checkbox" name="RU"> (can cause issues)
Realtime LED offset: <input name="WO" type="number" min="-255" max="255" required>
<h3>Alexa Voice Assistant</h3>
Emulate Alexa device: <input type="checkbox" name="AL"><br>
Alexa invocation name: <input name="AI" maxlength="32">
<h3>Blynk</h3>
<b>Blynk, MQTT and Hue sync all connect to external hosts!<br>
This impacts the responsiveness of the ESP8266.</b><br>
This may impact the responsiveness of the ESP8266.</b><br>
For best results, only use one of these services at a time.<br>
(alternatively, connect a second ESP to them and use the UDP sync)<br><br>
Device Auth token: <input name="BK" maxlength="33"><br>
<i>Clear the token field to disable. </i><a href="https://github.com/Aircoookie/WLED/wiki/Blynk" target="_blank">Setup info</a>
<h3>MQTT</h3>
Broker: <input name="MS" maxlength="32"><br>
Enable MQTT: <input type="checkbox" name="MQ"><br>
Broker: <input name="MS" maxlength="32">
Port: <input name="MQPORT" type="number" min="1" max="65535" required><br>
<b>The MQTT credentials are sent over an unsecured connection.<br>
Never use the MQTT password for another service!</b><br>
Username: <input name="MQUSER" maxlength="40"><br>
Password: <input type="password" input name="MQPASS" maxlength="40"><br>
Client ID: <input name="MQCID" maxlength="40"><br>
Device Topic: <input name="MD" maxlength="32"><br>
Group Topic: <input name="MG" maxlength="32"><br>
<i>Reboot required to apply changes. </i><a href="https://github.com/Aircoookie/WLED/wiki/MQTT" target="_blank">MQTT info</a>
@@ -295,27 +245,26 @@ Hue status: <span class="hms"> Internal ESP Error! </span><hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>
</html>
)=====";
</html>)=====";
//time and macro settings
const char PAGE_settings_time0[] PROGMEM = R"=====(
<!DOCTYPE html>
<html><head><meta name="viewport" content="width=500"><title>Time Settings</title>
<script>function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#time-settings");}function B(){window.open("/settings","_self");}function S(){GetV();Cs();}function gId(s){return document.getElementById(s);}function Cs(){gId("cac").style.display="none";gId("coc").style.display="block";gId("ccc").style.display="none";if (gId("ca").selected){gId("cac").style.display="block";}if (gId("cc").selected){gId("coc").style.display="none";gId("ccc").style.display="block";}if (gId("cn").selected){gId("coc").style.display="none";}}function GetV(){var d = document;
)=====";
const char PAGE_settings_time1[] PROGMEM = R"=====(
</head>
const char PAGE_settings_time[] PROGMEM = R"=====(<!DOCTYPE html>
<html><head><meta name="viewport" content="width=500"><meta charset="utf-8"><title>Time Settings</title>
<script>var d=document;function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#time-settings");}function B(){window.open("/settings","_self");}function S(){BTa();GetV();Cs();FC();}function gId(s){return d.getElementById(s);}function Cs(){gId("cac").style.display="none";gId("coc").style.display="block";gId("ccc").style.display="none";if (gId("ca").selected){gId("cac").style.display="block";}if (gId("cc").selected){gId("coc").style.display="none";gId("ccc").style.display="block";}if (gId("cn").selected){gId("coc").style.display="none";}}
function BTa(){var ih="<tr><th>Active</th><th>Hour</th><th>Minute</th><th>Macro</th><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr>";for (i=0;i<8;i++){ih+="<tr><td><input name=\"W"+i+"\" id=\"W"+i+"\" type=\"number\" style=\"display:none\"><input id=\"W"+i+"0\" type=\"checkbox\"></td><td><input name=\"H"+i+"\" type=\"number\" min=\"0\" max=\"24\"></td><td><input name=\"N"+i+"\" type=\"number\" min=\"0\" max=\"59\"></td><td><input name=\"T"+i+"\" type=\"number\" min=\"0\" max=\"16\"></td>";for (j=1;j<8;j++) ih+="<td><input id=\"W"+i+j+"\" type=\"checkbox\"></td>";}gId("TMT").innerHTML=ih;}
function FC(){for(j=0;j<8;j++){for(i=0;i<8;i++)gId("W"+i+j).checked=gId("W"+i).value>>j&1;}}
function Wd(){a=[0,0,0,0,0,0,0,0];for(i=0;i<8;i++){m=1;for(j=0;j<8;j++){a[i]+=gId("W"+i+j).checked*m;m*=2;}gId("W"+i).value=a[i];}}function GetV(){
%CSS%%SCSS%</head>
<body onload="S()">
<form id="form_s" name="Sf" method="post">
<form id="form_s" name="Sf" method="post" onsubmit="Wd()">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><hr>
<h2>Time setup</h2>
Get time from NTP server: <input type="checkbox" name="NT"><br>
<input name="NS" maxlength="32"><br>
Use 24h format: <input type="checkbox" name="CF"><br>
Time zone:
Time zone:
<select name="TZ">
<option value="0" selected>GMT(UTC)</option>
<option value="1">GMT/BST</option>
@@ -331,6 +280,7 @@ Time zone:
<option value="11">AEST/AEDT</option>
<option value="12">NZST/NZDT</option>
<option value="13">North Korea</option>
<option value="14">IST (India)</option>
</select><br>
UTC offset: <input name="UO" type="number" min="-65500" max="65500" required> seconds (max. 18 hours)<br>
Current local time is <span class="times">unknown</span>.
@@ -339,7 +289,7 @@ Clock Overlay:
<select name="OL" onchange="Cs()">
<option value="0" id="cn" selected>None</option>
<option value="1" id="ca">Analog Clock</option>
<option value="2">Single Digit Clock</option>
<option value="2" disabled>-</option>
<option value="3" id="cc">Cronixie Clock</option>
</select><br>
<div id="coc">
@@ -376,45 +326,28 @@ Define API macros here:<br>
15: <input name="M15" maxlength="64"><br>
16: <input name="M16" maxlength="64"><br><br>
<i>Use 0 for the default action instead of a macro</i><br>
Boot Macro: <input name="MB" type="number" min="0" max="16" required><br>
Alexa On/Off Macros: <input name="A0" type="number" min="0" max="16" required> <input name="A1" type="number" min="0" max="16" required><br>
Button Macro: <input name="MP" type="number" min="0" max="16" required> Long Press: <input name="ML" type="number" min="0" max="16" required><br>
Countdown-Over Macro: <input name="MC" type="number" min="0" max="16" required><br>
Timed-Light-Over Macro: <input name="MN" type="number" min="0" max="16" required><br>
Time-Controlled Macros (Hours/Minutes &gt; Macro):<br>
<input name="H0" type="number" min="0" max="24"> <input name="N0" type="number" min="0" max="59">
&gt; <input name="T0" type="number" min="0" max="16"><br>
<input name="H1" type="number" min="0" max="24"> <input name="N1" type="number" min="0" max="59">
&gt; <input name="T1" type="number" min="0" max="16"><br>
<input name="H2" type="number" min="0" max="24"> <input name="N2" type="number" min="0" max="59">
&gt; <input name="T2" type="number" min="0" max="16"><br>
<input name="H3" type="number" min="0" max="24"> <input name="N3" type="number" min="0" max="59">
&gt; <input name="T3" type="number" min="0" max="16"><br>
<input name="H4" type="number" min="0" max="24"> <input name="N4" type="number" min="0" max="59">
&gt; <input name="T4" type="number" min="0" max="16"><br>
<input name="H5" type="number" min="0" max="24"> <input name="N5" type="number" min="0" max="59">
&gt; <input name="T5" type="number" min="0" max="16"><br>
<input name="H6" type="number" min="0" max="24"> <input name="N6" type="number" min="0" max="59">
&gt; <input name="T6" type="number" min="0" max="16"><br>
<input name="H7" type="number" min="0" max="24"> <input name="N7" type="number" min="0" max="59">
&gt; <input name="T7" type="number" min="0" max="16"><hr>
Boot macro: <input name="MB" type="number" min="0" max="16" required><br>
Alexa On/Off macros: <input name="A0" type="number" min="0" max="16" required> <input name="A1" type="number" min="0" max="16" required><br>
Button short press macro: <input name="MP" type="number" min="0" max="16" required><br>
Long press: <input name="ML" type="number" min="0" max="16" required> Double press: <input name="MD" type="number" min="0" max="16" required><br>
Countdown-Over macro: <input name="MC" type="number" min="0" max="16" required><br>
Timed-Light-Over macro: <input name="MN" type="number" min="0" max="16" required><br>
Time-Controlled macros:<br>
<div style="display: inline-block">
<table id="TMT">
</table></div><hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>
</html>
)=====";
</html>)=====";
//security settings and about
const char PAGE_settings_sec0[] PROGMEM = R"=====(
<!DOCTYPE html>
<html><head><meta name="viewport" content="width=500">
const char PAGE_settings_sec[] PROGMEM = R"=====(<!DOCTYPE html>
<html><head><meta name="viewport" content="width=500"><meta charset="utf-8">
<title>Misc Settings</title>
<script>function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#security-settings");}function B(){window.open("/settings","_self");}function U(){window.open("/update","_self");}function GetV(){var d = document;
)=====";
const char PAGE_settings_sec1[] PROGMEM = R"=====(
</head>
<script>function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#security-settings");}function B(){window.open("/settings","_self");}function U(){window.open("/update","_self");}function GetV(){var d=document;
%CSS%%SCSS%</head>
<body onload="GetV()">
<form id="form_s" name="Sf" method="post">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
@@ -427,9 +360,6 @@ The password should be changed when OTA is enabled.<br>
<b>Disable OTA when not in use, otherwise an attacker can reflash device software!</b><br>
<i>Settings on this page are only changable if OTA lock is disabled!</i><br>
Deny access to WiFi settings if locked: <input type="checkbox" name="OW"><br><br>
Disable recovery AP: <input type="checkbox" name="NA"><br>
In case of an error there will be no wireless recovery possible!<br>
Completely disables all Access Point functions.<br><br>
Factory reset: <input type="checkbox" name="RS"><br>
All EEPROM content (settings) will be erased.<br><br>
HTTP traffic is unencrypted. An attacker in the same network can intercept form data!
@@ -437,14 +367,13 @@ HTTP traffic is unencrypted. An attacker in the same network can intercept form
<button type="button" onclick="U()">Manual OTA Update</button><br>
Enable ArduinoOTA: <input type="checkbox" name="AO"><br>
<h3>About</h3>
<a href="https://github.com/Aircoookie/WLED" target="_blank">WLED</a> version 0.8.2<br><br>
<a href="https://github.com/Aircoookie/WLED" target="_blank">WLED</a> version 0.9.0-b1<br><br>
<a href="https://github.com/Aircoookie/WLED/wiki/Contributors-&-About" target="_blank">Contributors, dependencies and special thanks</a><br>
A huge thank you to everyone who helped me create WLED!<br><br>
(c) 2016-2018 Christian Schwinne <br>
(c) 2016-2019 Christian Schwinne <br>
<i>Licensed under the MIT license</i><br><br>
Server message: <span class="msg"> Response error! </span><hr>
<button type="button" onclick="B()">Back</button><button type="submit">Save & Reboot</button>
</form>
</body>
</html>
)=====";
</html>)=====";

1459
wled00/html_ui.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -529,12 +529,49 @@ DEFINE_GRADIENT_PALETTE( April_Night_gp ) {
127, 249,150, 5, //yellow
143, 1, 5, 45,
162, 1, 5, 45,
178, 255,92, 0, //pastel orange
178, 255, 92, 0, //pastel orange
193, 1, 5, 45,
214, 1, 5, 45,
229, 223, 45, 72, //pink
244, 1, 5, 45,
255, 1, 5, 45};
DEFINE_GRADIENT_PALETTE( Orangery_gp ) {
0, 255, 95, 23,
30, 255, 82, 0,
60, 223, 13, 8,
90, 144, 44, 2,
120, 255,110, 17,
150, 255, 69, 0,
180, 158, 13, 11,
210, 241, 82, 17,
255, 213, 37, 4};
//inspired by Mark Kriegsman https://gist.github.com/kriegsman/756ea6dcae8e30845b5a
DEFINE_GRADIENT_PALETTE( C9_gp ) {
0, 184, 4, 0, //red
60, 184, 4, 0,
65, 144, 44, 2, //amber
125, 144, 44, 2,
130, 4, 96, 2, //green
190, 4, 96, 2,
195, 7, 7, 88, //blue
255, 7, 7, 88};
DEFINE_GRADIENT_PALETTE( Sakura_gp ) {
0, 196, 19, 10,
65, 255, 69, 45,
130, 223, 45, 72,
195, 255, 82,103,
255, 223, 13, 17};
DEFINE_GRADIENT_PALETTE( Aurora_gp ) {
0, 1, 5, 45, //deep blue
64, 0,200, 23,
128, 0,255, 0, //green
170, 0,243, 45,
200, 0,135, 7,
255, 1, 5, 45};//deep blue
// Single array of defined cpt-city color palettes.
@@ -581,7 +618,11 @@ const TProgmemRGBGradientPalettePtr gGradientPalettes[] = {
Blue_Cyan_Yellow_gp, //43-30 Yelblu
Orange_Teal_gp, //44-31 Orange & Teal
Tiamat_gp, //45-32 Tiamat
April_Night_gp //46-33 April Night
April_Night_gp, //46-33 April Night
Orangery_gp, //47-34 Orangery
C9_gp, //48-35 C9
Sakura_gp, //49-36 Sakura
Aurora_gp, //50-37 Aurora
};

View File

@@ -0,0 +1,877 @@
#include "AsyncMqttClient.hpp"
AsyncMqttClient::AsyncMqttClient()
: _connected(false)
, _connectPacketNotEnoughSpace(false)
, _disconnectFlagged(false)
, _tlsBadFingerprint(false)
, _lastClientActivity(0)
, _lastServerActivity(0)
, _lastPingRequestTime(0)
, _host(nullptr)
, _useIp(false)
#if ASYNC_TCP_SSL_ENABLED
, _secure(false)
#endif
, _port(0)
, _keepAlive(15)
, _cleanSession(true)
, _clientId(nullptr)
, _username(nullptr)
, _password(nullptr)
, _willTopic(nullptr)
, _willPayload(nullptr)
, _willPayloadLength(0)
, _willQos(0)
, _willRetain(false)
, _parsingInformation { .bufferState = AsyncMqttClientInternals::BufferState::NONE }
, _currentParsedPacket(nullptr)
, _remainingLengthBufferPosition(0)
, _nextPacketId(1) {
_client.onConnect([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onConnect(c); }, this);
_client.onDisconnect([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onDisconnect(c); }, this);
_client.onError([](void* obj, AsyncClient* c, int8_t error) { (static_cast<AsyncMqttClient*>(obj))->_onError(c, error); }, this);
_client.onTimeout([](void* obj, AsyncClient* c, uint32_t time) { (static_cast<AsyncMqttClient*>(obj))->_onTimeout(c, time); }, this);
_client.onAck([](void* obj, AsyncClient* c, size_t len, uint32_t time) { (static_cast<AsyncMqttClient*>(obj))->_onAck(c, len, time); }, this);
_client.onData([](void* obj, AsyncClient* c, void* data, size_t len) { (static_cast<AsyncMqttClient*>(obj))->_onData(c, static_cast<char*>(data), len); }, this);
_client.onPoll([](void* obj, AsyncClient* c) { (static_cast<AsyncMqttClient*>(obj))->_onPoll(c); }, this);
#ifdef ESP32
sprintf(_generatedClientId, "esp32%06x", ESP.getEfuseMac());
_xSemaphore = xSemaphoreCreateMutex();
#elif defined(ESP8266)
sprintf(_generatedClientId, "esp8266%06x", ESP.getChipId());
#endif
_clientId = _generatedClientId;
setMaxTopicLength(128);
}
AsyncMqttClient::~AsyncMqttClient() {
delete _currentParsedPacket;
delete[] _parsingInformation.topicBuffer;
#ifdef ESP32
vSemaphoreDelete(_xSemaphore);
#endif
}
AsyncMqttClient& AsyncMqttClient::setKeepAlive(uint16_t keepAlive) {
_keepAlive = keepAlive;
return *this;
}
AsyncMqttClient& AsyncMqttClient::setClientId(const char* clientId) {
_clientId = clientId;
return *this;
}
AsyncMqttClient& AsyncMqttClient::setCleanSession(bool cleanSession) {
_cleanSession = cleanSession;
return *this;
}
AsyncMqttClient& AsyncMqttClient::setMaxTopicLength(uint16_t maxTopicLength) {
_parsingInformation.maxTopicLength = maxTopicLength;
delete[] _parsingInformation.topicBuffer;
_parsingInformation.topicBuffer = new char[maxTopicLength + 1];
return *this;
}
AsyncMqttClient& AsyncMqttClient::setCredentials(const char* username, const char* password) {
_username = username;
_password = password;
return *this;
}
AsyncMqttClient& AsyncMqttClient::setWill(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length) {
_willTopic = topic;
_willQos = qos;
_willRetain = retain;
_willPayload = payload;
_willPayloadLength = length;
return *this;
}
AsyncMqttClient& AsyncMqttClient::setServer(IPAddress ip, uint16_t port) {
_useIp = true;
_ip = ip;
_port = port;
return *this;
}
AsyncMqttClient& AsyncMqttClient::setServer(const char* host, uint16_t port) {
_useIp = false;
_host = host;
_port = port;
return *this;
}
#if ASYNC_TCP_SSL_ENABLED
AsyncMqttClient& AsyncMqttClient::setSecure(bool secure) {
_secure = secure;
return *this;
}
AsyncMqttClient& AsyncMqttClient::addServerFingerprint(const uint8_t* fingerprint) {
std::array<uint8_t, SHA1_SIZE> newFingerprint;
memcpy(newFingerprint.data(), fingerprint, SHA1_SIZE);
_secureServerFingerprints.push_back(newFingerprint);
return *this;
}
#endif
AsyncMqttClient& AsyncMqttClient::onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback) {
_onConnectUserCallbacks.push_back(callback);
return *this;
}
AsyncMqttClient& AsyncMqttClient::onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback) {
_onDisconnectUserCallbacks.push_back(callback);
return *this;
}
AsyncMqttClient& AsyncMqttClient::onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback) {
_onSubscribeUserCallbacks.push_back(callback);
return *this;
}
AsyncMqttClient& AsyncMqttClient::onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback) {
_onUnsubscribeUserCallbacks.push_back(callback);
return *this;
}
AsyncMqttClient& AsyncMqttClient::onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback) {
_onMessageUserCallbacks.push_back(callback);
return *this;
}
AsyncMqttClient& AsyncMqttClient::onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback) {
_onPublishUserCallbacks.push_back(callback);
return *this;
}
void AsyncMqttClient::_freeCurrentParsedPacket() {
delete _currentParsedPacket;
_currentParsedPacket = nullptr;
}
void AsyncMqttClient::_clear() {
_lastPingRequestTime = 0;
_connected = false;
_disconnectFlagged = false;
_connectPacketNotEnoughSpace = false;
_tlsBadFingerprint = false;
_freeCurrentParsedPacket();
_pendingPubRels.clear();
_pendingPubRels.shrink_to_fit();
_toSendAcks.clear();
_toSendAcks.shrink_to_fit();
_nextPacketId = 1;
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE;
}
/* TCP */
void AsyncMqttClient::_onConnect(AsyncClient* client) {
(void)client;
#if ASYNC_TCP_SSL_ENABLED
if (_secure && _secureServerFingerprints.size() > 0) {
SSL* clientSsl = _client.getSSL();
bool sslFoundFingerprint = false;
for (std::array<uint8_t, SHA1_SIZE> fingerprint : _secureServerFingerprints) {
if (ssl_match_fingerprint(clientSsl, fingerprint.data()) == SSL_OK) {
sslFoundFingerprint = true;
break;
}
}
if (!sslFoundFingerprint) {
_tlsBadFingerprint = true;
_client.close(true);
return;
}
}
#endif
char fixedHeader[5];
fixedHeader[0] = AsyncMqttClientInternals::PacketType.CONNECT;
fixedHeader[0] = fixedHeader[0] << 4;
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.CONNECT_RESERVED;
uint16_t protocolNameLength = 4;
char protocolNameLengthBytes[2];
protocolNameLengthBytes[0] = protocolNameLength >> 8;
protocolNameLengthBytes[1] = protocolNameLength & 0xFF;
char protocolLevel[1];
protocolLevel[0] = 0x04;
char connectFlags[1];
connectFlags[0] = 0;
if (_cleanSession) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.CLEAN_SESSION;
if (_username != nullptr) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.USERNAME;
if (_password != nullptr) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.PASSWORD;
if (_willTopic != nullptr) {
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL;
if (_willRetain) connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_RETAIN;
switch (_willQos) {
case 0:
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS0;
break;
case 1:
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS1;
break;
case 2:
connectFlags[0] |= AsyncMqttClientInternals::ConnectFlag.WILL_QOS2;
break;
}
}
char keepAliveBytes[2];
keepAliveBytes[0] = _keepAlive >> 8;
keepAliveBytes[1] = _keepAlive & 0xFF;
uint16_t clientIdLength = strlen(_clientId);
char clientIdLengthBytes[2];
clientIdLengthBytes[0] = clientIdLength >> 8;
clientIdLengthBytes[1] = clientIdLength & 0xFF;
// Optional fields
uint16_t willTopicLength = 0;
char willTopicLengthBytes[2];
uint16_t willPayloadLength = _willPayloadLength;
char willPayloadLengthBytes[2];
if (_willTopic != nullptr) {
willTopicLength = strlen(_willTopic);
willTopicLengthBytes[0] = willTopicLength >> 8;
willTopicLengthBytes[1] = willTopicLength & 0xFF;
if (_willPayload != nullptr && willPayloadLength == 0) willPayloadLength = strlen(_willPayload);
willPayloadLengthBytes[0] = willPayloadLength >> 8;
willPayloadLengthBytes[1] = willPayloadLength & 0xFF;
}
uint16_t usernameLength = 0;
char usernameLengthBytes[2];
if (_username != nullptr) {
usernameLength = strlen(_username);
usernameLengthBytes[0] = usernameLength >> 8;
usernameLengthBytes[1] = usernameLength & 0xFF;
}
uint16_t passwordLength = 0;
char passwordLengthBytes[2];
if (_password != nullptr) {
passwordLength = strlen(_password);
passwordLengthBytes[0] = passwordLength >> 8;
passwordLengthBytes[1] = passwordLength & 0xFF;
}
uint32_t remainingLength = 2 + protocolNameLength + 1 + 1 + 2 + 2 + clientIdLength; // always present
if (_willTopic != nullptr) remainingLength += 2 + willTopicLength + 2 + willPayloadLength;
if (_username != nullptr) remainingLength += 2 + usernameLength;
if (_password != nullptr) remainingLength += 2 + passwordLength;
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(remainingLength, fixedHeader + 1);
uint32_t neededSpace = 1 + remainingLengthLength;
neededSpace += 2;
neededSpace += protocolNameLength;
neededSpace += 1;
neededSpace += 1;
neededSpace += 2;
neededSpace += 2;
neededSpace += clientIdLength;
if (_willTopic != nullptr) {
neededSpace += 2;
neededSpace += willTopicLength;
neededSpace += 2;
if (_willPayload != nullptr) neededSpace += willPayloadLength;
}
if (_username != nullptr) {
neededSpace += 2;
neededSpace += usernameLength;
}
if (_password != nullptr) {
neededSpace += 2;
neededSpace += passwordLength;
}
SEMAPHORE_TAKE();
if (_client.space() < neededSpace) {
_connectPacketNotEnoughSpace = true;
_client.close(true);
SEMAPHORE_GIVE();
return;
}
_client.add(fixedHeader, 1 + remainingLengthLength);
_client.add(protocolNameLengthBytes, 2);
_client.add("MQTT", protocolNameLength);
_client.add(protocolLevel, 1);
_client.add(connectFlags, 1);
_client.add(keepAliveBytes, 2);
_client.add(clientIdLengthBytes, 2);
_client.add(_clientId, clientIdLength);
if (_willTopic != nullptr) {
_client.add(willTopicLengthBytes, 2);
_client.add(_willTopic, willTopicLength);
_client.add(willPayloadLengthBytes, 2);
if (_willPayload != nullptr) _client.add(_willPayload, willPayloadLength);
}
if (_username != nullptr) {
_client.add(usernameLengthBytes, 2);
_client.add(_username, usernameLength);
}
if (_password != nullptr) {
_client.add(passwordLengthBytes, 2);
_client.add(_password, passwordLength);
}
_client.send();
_lastClientActivity = millis();
SEMAPHORE_GIVE();
}
void AsyncMqttClient::_onDisconnect(AsyncClient* client) {
(void)client;
if (!_disconnectFlagged) {
AsyncMqttClientDisconnectReason reason;
if (_connectPacketNotEnoughSpace) {
reason = AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE;
} else if (_tlsBadFingerprint) {
reason = AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT;
} else {
reason = AsyncMqttClientDisconnectReason::TCP_DISCONNECTED;
}
for (auto callback : _onDisconnectUserCallbacks) callback(reason);
}
_clear();
}
void AsyncMqttClient::_onError(AsyncClient* client, int8_t error) {
(void)client;
(void)error;
// _onDisconnect called anyway
}
void AsyncMqttClient::_onTimeout(AsyncClient* client, uint32_t time) {
(void)client;
(void)time;
// disconnection will be handled by ping/pong management
}
void AsyncMqttClient::_onAck(AsyncClient* client, size_t len, uint32_t time) {
(void)client;
(void)len;
(void)time;
}
void AsyncMqttClient::_onData(AsyncClient* client, char* data, size_t len) {
(void)client;
size_t currentBytePosition = 0;
char currentByte;
do {
switch (_parsingInformation.bufferState) {
case AsyncMqttClientInternals::BufferState::NONE:
currentByte = data[currentBytePosition++];
_parsingInformation.packetType = currentByte >> 4;
_parsingInformation.packetFlags = (currentByte << 4) >> 4;
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::REMAINING_LENGTH;
_lastServerActivity = millis();
switch (_parsingInformation.packetType) {
case AsyncMqttClientInternals::PacketType.CONNACK:
_currentParsedPacket = new AsyncMqttClientInternals::ConnAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onConnAck, this, std::placeholders::_1, std::placeholders::_2));
break;
case AsyncMqttClientInternals::PacketType.PINGRESP:
_currentParsedPacket = new AsyncMqttClientInternals::PingRespPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPingResp, this));
break;
case AsyncMqttClientInternals::PacketType.SUBACK:
_currentParsedPacket = new AsyncMqttClientInternals::SubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onSubAck, this, std::placeholders::_1, std::placeholders::_2));
break;
case AsyncMqttClientInternals::PacketType.UNSUBACK:
_currentParsedPacket = new AsyncMqttClientInternals::UnsubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onUnsubAck, this, std::placeholders::_1));
break;
case AsyncMqttClientInternals::PacketType.PUBLISH:
_currentParsedPacket = new AsyncMqttClientInternals::PublishPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6, std::placeholders::_7, std::placeholders::_8, std::placeholders::_9), std::bind(&AsyncMqttClient::_onPublish, this, std::placeholders::_1, std::placeholders::_2));
break;
case AsyncMqttClientInternals::PacketType.PUBREL:
_currentParsedPacket = new AsyncMqttClientInternals::PubRelPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubRel, this, std::placeholders::_1));
break;
case AsyncMqttClientInternals::PacketType.PUBACK:
_currentParsedPacket = new AsyncMqttClientInternals::PubAckPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubAck, this, std::placeholders::_1));
break;
case AsyncMqttClientInternals::PacketType.PUBREC:
_currentParsedPacket = new AsyncMqttClientInternals::PubRecPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubRec, this, std::placeholders::_1));
break;
case AsyncMqttClientInternals::PacketType.PUBCOMP:
_currentParsedPacket = new AsyncMqttClientInternals::PubCompPacket(&_parsingInformation, std::bind(&AsyncMqttClient::_onPubComp, this, std::placeholders::_1));
break;
default:
break;
}
break;
case AsyncMqttClientInternals::BufferState::REMAINING_LENGTH:
currentByte = data[currentBytePosition++];
_remainingLengthBuffer[_remainingLengthBufferPosition++] = currentByte;
if (currentByte >> 7 == 0) {
_parsingInformation.remainingLength = AsyncMqttClientInternals::Helpers::decodeRemainingLength(_remainingLengthBuffer);
_remainingLengthBufferPosition = 0;
if (_parsingInformation.remainingLength > 0) {
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::VARIABLE_HEADER;
} else {
// PINGRESP is a special case where it has no variable header, so the packet ends right here
_parsingInformation.bufferState = AsyncMqttClientInternals::BufferState::NONE;
_onPingResp();
}
}
break;
case AsyncMqttClientInternals::BufferState::VARIABLE_HEADER:
_currentParsedPacket->parseVariableHeader(data, len, &currentBytePosition);
break;
case AsyncMqttClientInternals::BufferState::PAYLOAD:
_currentParsedPacket->parsePayload(data, len, &currentBytePosition);
break;
default:
currentBytePosition = len;
}
} while (currentBytePosition != len);
}
void AsyncMqttClient::_onPoll(AsyncClient* client) {
if (!_connected) return;
// if there is too much time the client has sent a ping request without a response, disconnect client to avoid half open connections
if (_lastPingRequestTime != 0 && (millis() - _lastPingRequestTime) >= (_keepAlive * 1000 * 2)) {
disconnect();
return;
// send ping to ensure the server will receive at least one message inside keepalive window
} else if (_lastPingRequestTime == 0 && (millis() - _lastClientActivity) >= (_keepAlive * 1000 * 0.7)) {
_sendPing();
// send ping to verify if the server is still there (ensure this is not a half connection)
} else if (_connected && _lastPingRequestTime == 0 && (millis() - _lastServerActivity) >= (_keepAlive * 1000 * 0.7)) {
_sendPing();
}
// handle to send ack packets
_sendAcks();
// handle disconnect
if (_disconnectFlagged) {
_sendDisconnect();
}
}
/* MQTT */
void AsyncMqttClient::_onPingResp() {
_freeCurrentParsedPacket();
_lastPingRequestTime = 0;
}
void AsyncMqttClient::_onConnAck(bool sessionPresent, uint8_t connectReturnCode) {
(void)sessionPresent;
_freeCurrentParsedPacket();
if (connectReturnCode == 0) {
_connected = true;
for (auto callback : _onConnectUserCallbacks) callback(sessionPresent);
} else {
for (auto callback : _onDisconnectUserCallbacks) callback(static_cast<AsyncMqttClientDisconnectReason>(connectReturnCode));
_disconnectFlagged = true;
}
}
void AsyncMqttClient::_onSubAck(uint16_t packetId, char status) {
_freeCurrentParsedPacket();
for (auto callback : _onSubscribeUserCallbacks) callback(packetId, status);
}
void AsyncMqttClient::_onUnsubAck(uint16_t packetId) {
_freeCurrentParsedPacket();
for (auto callback : _onUnsubscribeUserCallbacks) callback(packetId);
}
void AsyncMqttClient::_onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId) {
bool notifyPublish = true;
if (qos == 2) {
for (AsyncMqttClientInternals::PendingPubRel pendingPubRel : _pendingPubRels) {
if (pendingPubRel.packetId == packetId) {
notifyPublish = false;
break;
}
}
}
if (notifyPublish) {
AsyncMqttClientMessageProperties properties;
properties.qos = qos;
properties.dup = dup;
properties.retain = retain;
for (auto callback : _onMessageUserCallbacks) callback(topic, payload, properties, len, index, total);
}
}
void AsyncMqttClient::_onPublish(uint16_t packetId, uint8_t qos) {
AsyncMqttClientInternals::PendingAck pendingAck;
if (qos == 1) {
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBACK;
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBACK_RESERVED;
pendingAck.packetId = packetId;
_toSendAcks.push_back(pendingAck);
} else if (qos == 2) {
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBREC;
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBREC_RESERVED;
pendingAck.packetId = packetId;
_toSendAcks.push_back(pendingAck);
bool pubRelAwaiting = false;
for (AsyncMqttClientInternals::PendingPubRel pendingPubRel : _pendingPubRels) {
if (pendingPubRel.packetId == packetId) {
pubRelAwaiting = true;
break;
}
}
if (!pubRelAwaiting) {
AsyncMqttClientInternals::PendingPubRel pendingPubRel;
pendingPubRel.packetId = packetId;
_pendingPubRels.push_back(pendingPubRel);
}
_sendAcks();
}
_freeCurrentParsedPacket();
}
void AsyncMqttClient::_onPubRel(uint16_t packetId) {
_freeCurrentParsedPacket();
AsyncMqttClientInternals::PendingAck pendingAck;
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBCOMP;
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBCOMP_RESERVED;
pendingAck.packetId = packetId;
_toSendAcks.push_back(pendingAck);
for (size_t i = 0; i < _pendingPubRels.size(); i++) {
if (_pendingPubRels[i].packetId == packetId) {
_pendingPubRels.erase(_pendingPubRels.begin() + i);
_pendingPubRels.shrink_to_fit();
}
}
_sendAcks();
}
void AsyncMqttClient::_onPubAck(uint16_t packetId) {
_freeCurrentParsedPacket();
for (auto callback : _onPublishUserCallbacks) callback(packetId);
}
void AsyncMqttClient::_onPubRec(uint16_t packetId) {
_freeCurrentParsedPacket();
AsyncMqttClientInternals::PendingAck pendingAck;
pendingAck.packetType = AsyncMqttClientInternals::PacketType.PUBREL;
pendingAck.headerFlag = AsyncMqttClientInternals::HeaderFlag.PUBREL_RESERVED;
pendingAck.packetId = packetId;
_toSendAcks.push_back(pendingAck);
_sendAcks();
}
void AsyncMqttClient::_onPubComp(uint16_t packetId) {
_freeCurrentParsedPacket();
for (auto callback : _onPublishUserCallbacks) callback(packetId);
}
bool AsyncMqttClient::_sendPing() {
char fixedHeader[2];
fixedHeader[0] = AsyncMqttClientInternals::PacketType.PINGREQ;
fixedHeader[0] = fixedHeader[0] << 4;
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.PINGREQ_RESERVED;
fixedHeader[1] = 0;
size_t neededSpace = 2;
SEMAPHORE_TAKE(false);
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return false; }
_client.add(fixedHeader, 2);
_client.send();
_lastClientActivity = millis();
_lastPingRequestTime = millis();
SEMAPHORE_GIVE();
return true;
}
void AsyncMqttClient::_sendAcks() {
uint8_t neededAckSpace = 2 + 2;
SEMAPHORE_TAKE();
for (size_t i = 0; i < _toSendAcks.size(); i++) {
if (_client.space() < neededAckSpace) break;
AsyncMqttClientInternals::PendingAck pendingAck = _toSendAcks[i];
char fixedHeader[2];
fixedHeader[0] = pendingAck.packetType;
fixedHeader[0] = fixedHeader[0] << 4;
fixedHeader[0] = fixedHeader[0] | pendingAck.headerFlag;
fixedHeader[1] = 2;
char packetIdBytes[2];
packetIdBytes[0] = pendingAck.packetId >> 8;
packetIdBytes[1] = pendingAck.packetId & 0xFF;
_client.add(fixedHeader, 2);
_client.add(packetIdBytes, 2);
_client.send();
_toSendAcks.erase(_toSendAcks.begin() + i);
_toSendAcks.shrink_to_fit();
_lastClientActivity = millis();
}
SEMAPHORE_GIVE();
}
bool AsyncMqttClient::_sendDisconnect() {
if (!_connected) return true;
const uint8_t neededSpace = 2;
SEMAPHORE_TAKE(false);
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return false; }
char fixedHeader[2];
fixedHeader[0] = AsyncMqttClientInternals::PacketType.DISCONNECT;
fixedHeader[0] = fixedHeader[0] << 4;
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.DISCONNECT_RESERVED;
fixedHeader[1] = 0;
_client.add(fixedHeader, 2);
_client.send();
_client.close(true);
_disconnectFlagged = false;
SEMAPHORE_GIVE();
return true;
}
uint16_t AsyncMqttClient::_getNextPacketId() {
uint16_t nextPacketId = _nextPacketId;
if (_nextPacketId == 65535) _nextPacketId = 0; // 0 is forbidden
_nextPacketId++;
return nextPacketId;
}
bool AsyncMqttClient::connected() const {
return _connected;
}
void AsyncMqttClient::connect() {
if (_connected) return;
#if ASYNC_TCP_SSL_ENABLED
if (_useIp) {
_client.connect(_ip, _port, _secure);
} else {
_client.connect(_host, _port, _secure);
}
#else
if (_useIp) {
_client.connect(_ip, _port);
} else {
_client.connect(_host, _port);
}
#endif
}
void AsyncMqttClient::disconnect(bool force) {
if (!_connected) return;
if (force) {
_client.close(true);
} else {
_disconnectFlagged = true;
_sendDisconnect();
}
}
uint16_t AsyncMqttClient::subscribe(const char* topic, uint8_t qos) {
if (!_connected) return 0;
char fixedHeader[5];
fixedHeader[0] = AsyncMqttClientInternals::PacketType.SUBSCRIBE;
fixedHeader[0] = fixedHeader[0] << 4;
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.SUBSCRIBE_RESERVED;
uint16_t topicLength = strlen(topic);
char topicLengthBytes[2];
topicLengthBytes[0] = topicLength >> 8;
topicLengthBytes[1] = topicLength & 0xFF;
char qosByte[1];
qosByte[0] = qos;
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(2 + 2 + topicLength + 1, fixedHeader + 1);
size_t neededSpace = 0;
neededSpace += 1 + remainingLengthLength;
neededSpace += 2;
neededSpace += 2;
neededSpace += topicLength;
neededSpace += 1;
SEMAPHORE_TAKE(0);
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; }
uint16_t packetId = _getNextPacketId();
char packetIdBytes[2];
packetIdBytes[0] = packetId >> 8;
packetIdBytes[1] = packetId & 0xFF;
_client.add(fixedHeader, 1 + remainingLengthLength);
_client.add(packetIdBytes, 2);
_client.add(topicLengthBytes, 2);
_client.add(topic, topicLength);
_client.add(qosByte, 1);
_client.send();
_lastClientActivity = millis();
SEMAPHORE_GIVE();
return packetId;
}
uint16_t AsyncMqttClient::unsubscribe(const char* topic) {
if (!_connected) return 0;
char fixedHeader[5];
fixedHeader[0] = AsyncMqttClientInternals::PacketType.UNSUBSCRIBE;
fixedHeader[0] = fixedHeader[0] << 4;
fixedHeader[0] = fixedHeader[0] | AsyncMqttClientInternals::HeaderFlag.UNSUBSCRIBE_RESERVED;
uint16_t topicLength = strlen(topic);
char topicLengthBytes[2];
topicLengthBytes[0] = topicLength >> 8;
topicLengthBytes[1] = topicLength & 0xFF;
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(2 + 2 + topicLength, fixedHeader + 1);
size_t neededSpace = 0;
neededSpace += 1 + remainingLengthLength;
neededSpace += 2;
neededSpace += 2;
neededSpace += topicLength;
SEMAPHORE_TAKE(0);
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; }
uint16_t packetId = _getNextPacketId();
char packetIdBytes[2];
packetIdBytes[0] = packetId >> 8;
packetIdBytes[1] = packetId & 0xFF;
_client.add(fixedHeader, 1 + remainingLengthLength);
_client.add(packetIdBytes, 2);
_client.add(topicLengthBytes, 2);
_client.add(topic, topicLength);
_client.send();
_lastClientActivity = millis();
SEMAPHORE_GIVE();
return packetId;
}
uint16_t AsyncMqttClient::publish(const char* topic, uint8_t qos, bool retain, const char* payload, size_t length, bool dup, uint16_t message_id) {
if (!_connected) return 0;
char fixedHeader[5];
fixedHeader[0] = AsyncMqttClientInternals::PacketType.PUBLISH;
fixedHeader[0] = fixedHeader[0] << 4;
if (dup) fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_DUP;
if (retain) fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_RETAIN;
switch (qos) {
case 0:
fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS0;
break;
case 1:
fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS1;
break;
case 2:
fixedHeader[0] |= AsyncMqttClientInternals::HeaderFlag.PUBLISH_QOS2;
break;
}
uint16_t topicLength = strlen(topic);
char topicLengthBytes[2];
topicLengthBytes[0] = topicLength >> 8;
topicLengthBytes[1] = topicLength & 0xFF;
uint32_t payloadLength = length;
if (payload != nullptr && payloadLength == 0) payloadLength = strlen(payload);
uint32_t remainingLength = 2 + topicLength + payloadLength;
if (qos != 0) remainingLength += 2;
uint8_t remainingLengthLength = AsyncMqttClientInternals::Helpers::encodeRemainingLength(remainingLength, fixedHeader + 1);
size_t neededSpace = 0;
neededSpace += 1 + remainingLengthLength;
neededSpace += 2;
neededSpace += topicLength;
if (qos != 0) neededSpace += 2;
if (payload != nullptr) neededSpace += payloadLength;
SEMAPHORE_TAKE(0);
if (_client.space() < neededSpace) { SEMAPHORE_GIVE(); return 0; }
uint16_t packetId = 0;
char packetIdBytes[2];
if (qos != 0) {
if (dup && message_id > 0) {
packetId = message_id;
} else {
packetId = _getNextPacketId();
}
packetIdBytes[0] = packetId >> 8;
packetIdBytes[1] = packetId & 0xFF;
}
_client.add(fixedHeader, 1 + remainingLengthLength);
_client.add(topicLengthBytes, 2);
_client.add(topic, topicLength);
if (qos != 0) _client.add(packetIdBytes, 2);
if (payload != nullptr) _client.add(payload, payloadLength);
_client.send();
_lastClientActivity = millis();
SEMAPHORE_GIVE();
if (qos != 0) {
return packetId;
} else {
return 1;
}
}

View File

@@ -0,0 +1,6 @@
#ifndef SRC_ASYNCMQTTCLIENT_H_
#define SRC_ASYNCMQTTCLIENT_H_
#include "AsyncMqttClient.hpp"
#endif // SRC_ASYNCMQTTCLIENT_H_

View File

@@ -0,0 +1,166 @@
#pragma once
#include <functional>
#include <vector>
#include "Arduino.h"
#ifdef ESP32
#include <AsyncTCP.h>
#include <freertos/semphr.h>
#elif defined(ESP8266)
#include <ESPAsyncTCP.h>
#else
#error Platform not supported
#endif
#if ASYNC_TCP_SSL_ENABLED
#include <tcp_axtls.h>
#define SHA1_SIZE 20
#endif
#include "AsyncMqttClient/Flags.hpp"
#include "AsyncMqttClient/ParsingInformation.hpp"
#include "AsyncMqttClient/MessageProperties.hpp"
#include "AsyncMqttClient/Helpers.hpp"
#include "AsyncMqttClient/Callbacks.hpp"
#include "AsyncMqttClient/DisconnectReasons.hpp"
#include "AsyncMqttClient/Storage.hpp"
#include "AsyncMqttClient/Packets/Packet.hpp"
#include "AsyncMqttClient/Packets/ConnAckPacket.hpp"
#include "AsyncMqttClient/Packets/PingRespPacket.hpp"
#include "AsyncMqttClient/Packets/SubAckPacket.hpp"
#include "AsyncMqttClient/Packets/UnsubAckPacket.hpp"
#include "AsyncMqttClient/Packets/PublishPacket.hpp"
#include "AsyncMqttClient/Packets/PubRelPacket.hpp"
#include "AsyncMqttClient/Packets/PubAckPacket.hpp"
#include "AsyncMqttClient/Packets/PubRecPacket.hpp"
#include "AsyncMqttClient/Packets/PubCompPacket.hpp"
#if ESP32
#define SEMAPHORE_TAKE(X) if (xSemaphoreTake(_xSemaphore, 1000 / portTICK_PERIOD_MS) != pdTRUE) { return X; } // Waits max 1000ms
#define SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore);
#elif defined(ESP8266)
#define SEMAPHORE_TAKE(X) void()
#define SEMAPHORE_GIVE() void()
#endif
class AsyncMqttClient {
public:
AsyncMqttClient();
~AsyncMqttClient();
AsyncMqttClient& setKeepAlive(uint16_t keepAlive);
AsyncMqttClient& setClientId(const char* clientId);
AsyncMqttClient& setCleanSession(bool cleanSession);
AsyncMqttClient& setMaxTopicLength(uint16_t maxTopicLength);
AsyncMqttClient& setCredentials(const char* username, const char* password = nullptr);
AsyncMqttClient& setWill(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0);
AsyncMqttClient& setServer(IPAddress ip, uint16_t port);
AsyncMqttClient& setServer(const char* host, uint16_t port);
#if ASYNC_TCP_SSL_ENABLED
AsyncMqttClient& setSecure(bool secure);
AsyncMqttClient& addServerFingerprint(const uint8_t* fingerprint);
#endif
AsyncMqttClient& onConnect(AsyncMqttClientInternals::OnConnectUserCallback callback);
AsyncMqttClient& onDisconnect(AsyncMqttClientInternals::OnDisconnectUserCallback callback);
AsyncMqttClient& onSubscribe(AsyncMqttClientInternals::OnSubscribeUserCallback callback);
AsyncMqttClient& onUnsubscribe(AsyncMqttClientInternals::OnUnsubscribeUserCallback callback);
AsyncMqttClient& onMessage(AsyncMqttClientInternals::OnMessageUserCallback callback);
AsyncMqttClient& onPublish(AsyncMqttClientInternals::OnPublishUserCallback callback);
bool connected() const;
void connect();
void disconnect(bool force = false);
uint16_t subscribe(const char* topic, uint8_t qos);
uint16_t unsubscribe(const char* topic);
uint16_t publish(const char* topic, uint8_t qos, bool retain, const char* payload = nullptr, size_t length = 0, bool dup = false, uint16_t message_id = 0);
private:
AsyncClient _client;
bool _connected;
bool _connectPacketNotEnoughSpace;
bool _disconnectFlagged;
bool _tlsBadFingerprint;
uint32_t _lastClientActivity;
uint32_t _lastServerActivity;
uint32_t _lastPingRequestTime;
char _generatedClientId[13 + 1]; // esp8266abc123
IPAddress _ip;
const char* _host;
bool _useIp;
#if ASYNC_TCP_SSL_ENABLED
bool _secure;
#endif
uint16_t _port;
uint16_t _keepAlive;
bool _cleanSession;
const char* _clientId;
const char* _username;
const char* _password;
const char* _willTopic;
const char* _willPayload;
uint16_t _willPayloadLength;
uint8_t _willQos;
bool _willRetain;
#if ASYNC_TCP_SSL_ENABLED
std::vector<std::array<uint8_t, SHA1_SIZE>> _secureServerFingerprints;
#endif
std::vector<AsyncMqttClientInternals::OnConnectUserCallback> _onConnectUserCallbacks;
std::vector<AsyncMqttClientInternals::OnDisconnectUserCallback> _onDisconnectUserCallbacks;
std::vector<AsyncMqttClientInternals::OnSubscribeUserCallback> _onSubscribeUserCallbacks;
std::vector<AsyncMqttClientInternals::OnUnsubscribeUserCallback> _onUnsubscribeUserCallbacks;
std::vector<AsyncMqttClientInternals::OnMessageUserCallback> _onMessageUserCallbacks;
std::vector<AsyncMqttClientInternals::OnPublishUserCallback> _onPublishUserCallbacks;
AsyncMqttClientInternals::ParsingInformation _parsingInformation;
AsyncMqttClientInternals::Packet* _currentParsedPacket;
uint8_t _remainingLengthBufferPosition;
char _remainingLengthBuffer[4];
uint16_t _nextPacketId;
std::vector<AsyncMqttClientInternals::PendingPubRel> _pendingPubRels;
std::vector<AsyncMqttClientInternals::PendingAck> _toSendAcks;
#ifdef ESP32
SemaphoreHandle_t _xSemaphore = nullptr;
#endif
void _clear();
void _freeCurrentParsedPacket();
// TCP
void _onConnect(AsyncClient* client);
void _onDisconnect(AsyncClient* client);
static void _onError(AsyncClient* client, int8_t error);
void _onTimeout(AsyncClient* client, uint32_t time);
static void _onAck(AsyncClient* client, size_t len, uint32_t time);
void _onData(AsyncClient* client, char* data, size_t len);
void _onPoll(AsyncClient* client);
// MQTT
void _onPingResp();
void _onConnAck(bool sessionPresent, uint8_t connectReturnCode);
void _onSubAck(uint16_t packetId, char status);
void _onUnsubAck(uint16_t packetId);
void _onMessage(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId);
void _onPublish(uint16_t packetId, uint8_t qos);
void _onPubRel(uint16_t packetId);
void _onPubAck(uint16_t packetId);
void _onPubRec(uint16_t packetId);
void _onPubComp(uint16_t packetId);
bool _sendPing();
void _sendAcks();
bool _sendDisconnect();
uint16_t _getNextPacketId();
};

View File

@@ -0,0 +1,28 @@
#pragma once
#include <functional>
#include "DisconnectReasons.hpp"
#include "MessageProperties.hpp"
namespace AsyncMqttClientInternals {
// user callbacks
typedef std::function<void(bool sessionPresent)> OnConnectUserCallback;
typedef std::function<void(AsyncMqttClientDisconnectReason reason)> OnDisconnectUserCallback;
typedef std::function<void(uint16_t packetId, uint8_t qos)> OnSubscribeUserCallback;
typedef std::function<void(uint16_t packetId)> OnUnsubscribeUserCallback;
typedef std::function<void(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total)> OnMessageUserCallback;
typedef std::function<void(uint16_t packetId)> OnPublishUserCallback;
// internal callbacks
typedef std::function<void(bool sessionPresent, uint8_t connectReturnCode)> OnConnAckInternalCallback;
typedef std::function<void()> OnPingRespInternalCallback;
typedef std::function<void(uint16_t packetId, char status)> OnSubAckInternalCallback;
typedef std::function<void(uint16_t packetId)> OnUnsubAckInternalCallback;
typedef std::function<void(char* topic, char* payload, uint8_t qos, bool dup, bool retain, size_t len, size_t index, size_t total, uint16_t packetId)> OnMessageInternalCallback;
typedef std::function<void(uint16_t packetId, uint8_t qos)> OnPublishInternalCallback;
typedef std::function<void(uint16_t packetId)> OnPubRelInternalCallback;
typedef std::function<void(uint16_t packetId)> OnPubAckInternalCallback;
typedef std::function<void(uint16_t packetId)> OnPubRecInternalCallback;
typedef std::function<void(uint16_t packetId)> OnPubCompInternalCallback;
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,15 @@
#pragma once
enum class AsyncMqttClientDisconnectReason : int8_t {
TCP_DISCONNECTED = 0,
MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1,
MQTT_IDENTIFIER_REJECTED = 2,
MQTT_SERVER_UNAVAILABLE = 3,
MQTT_MALFORMED_CREDENTIALS = 4,
MQTT_NOT_AUTHORIZED = 5,
ESP8266_NOT_ENOUGH_SPACE = 6,
TLS_BAD_FINGERPRINT = 7
};

View File

@@ -0,0 +1,57 @@
#pragma once
namespace AsyncMqttClientInternals {
constexpr struct {
const uint8_t RESERVED = 0;
const uint8_t CONNECT = 1;
const uint8_t CONNACK = 2;
const uint8_t PUBLISH = 3;
const uint8_t PUBACK = 4;
const uint8_t PUBREC = 5;
const uint8_t PUBREL = 6;
const uint8_t PUBCOMP = 7;
const uint8_t SUBSCRIBE = 8;
const uint8_t SUBACK = 9;
const uint8_t UNSUBSCRIBE = 10;
const uint8_t UNSUBACK = 11;
const uint8_t PINGREQ = 12;
const uint8_t PINGRESP = 13;
const uint8_t DISCONNECT = 14;
const uint8_t RESERVED2 = 1;
} PacketType;
constexpr struct {
const uint8_t CONNECT_RESERVED = 0x00;
const uint8_t CONNACK_RESERVED = 0x00;
const uint8_t PUBLISH_DUP = 0x08;
const uint8_t PUBLISH_QOS0 = 0x00;
const uint8_t PUBLISH_QOS1 = 0x02;
const uint8_t PUBLISH_QOS2 = 0x04;
const uint8_t PUBLISH_QOSRESERVED = 0x06;
const uint8_t PUBLISH_RETAIN = 0x01;
const uint8_t PUBACK_RESERVED = 0x00;
const uint8_t PUBREC_RESERVED = 0x00;
const uint8_t PUBREL_RESERVED = 0x02;
const uint8_t PUBCOMP_RESERVED = 0x00;
const uint8_t SUBSCRIBE_RESERVED = 0x02;
const uint8_t SUBACK_RESERVED = 0x00;
const uint8_t UNSUBSCRIBE_RESERVED = 0x02;
const uint8_t UNSUBACK_RESERVED = 0x00;
const uint8_t PINGREQ_RESERVED = 0x00;
const uint8_t PINGRESP_RESERVED = 0x00;
const uint8_t DISCONNECT_RESERVED = 0x00;
const uint8_t RESERVED2_RESERVED = 0x00;
} HeaderFlag;
constexpr struct {
const uint8_t USERNAME = 0x80;
const uint8_t PASSWORD = 0x40;
const uint8_t WILL_RETAIN = 0x20;
const uint8_t WILL_QOS0 = 0x00;
const uint8_t WILL_QOS1 = 0x08;
const uint8_t WILL_QOS2 = 0x10;
const uint8_t WILL = 0x04;
const uint8_t CLEAN_SESSION = 0x02;
const uint8_t RESERVED = 0x00;
} ConnectFlag;
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,38 @@
#pragma once
namespace AsyncMqttClientInternals {
class Helpers {
public:
static uint32_t decodeRemainingLength(char* bytes) {
uint32_t multiplier = 1;
uint32_t value = 0;
uint8_t currentByte = 0;
uint8_t encodedByte;
do {
encodedByte = bytes[currentByte++];
value += (encodedByte & 127) * multiplier;
multiplier *= 128;
} while ((encodedByte & 128) != 0);
return value;
}
static uint8_t encodeRemainingLength(uint32_t remainingLength, char* destination) {
uint8_t currentByte = 0;
uint8_t bytesNeeded = 0;
do {
uint8_t encodedByte = remainingLength % 128;
remainingLength /= 128;
if (remainingLength > 0) {
encodedByte = encodedByte | 128;
}
destination[currentByte++] = encodedByte;
bytesNeeded++;
} while (remainingLength > 0);
return bytesNeeded;
}
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,7 @@
#pragma once
struct AsyncMqttClientMessageProperties {
uint8_t qos;
bool dup;
bool retain;
};

View File

@@ -0,0 +1,30 @@
#include "ConnAckPacket.hpp"
using AsyncMqttClientInternals::ConnAckPacket;
ConnAckPacket::ConnAckPacket(ParsingInformation* parsingInformation, OnConnAckInternalCallback callback)
: _parsingInformation(parsingInformation)
, _callback(callback)
, _bytePosition(0)
, _sessionPresent(false)
, _connectReturnCode(0) {
}
ConnAckPacket::~ConnAckPacket() {
}
void ConnAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
char currentByte = data[(*currentBytePosition)++];
if (_bytePosition++ == 0) {
_sessionPresent = (currentByte << 7) >> 7;
} else {
_connectReturnCode = currentByte;
_parsingInformation->bufferState = BufferState::NONE;
_callback(_sessionPresent, _connectReturnCode);
}
}
void ConnAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
(void)data;
(void)currentBytePosition;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Arduino.h"
#include "Packet.hpp"
#include "../ParsingInformation.hpp"
#include "../Callbacks.hpp"
namespace AsyncMqttClientInternals {
class ConnAckPacket : public Packet {
public:
explicit ConnAckPacket(ParsingInformation* parsingInformation, OnConnAckInternalCallback callback);
~ConnAckPacket();
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
private:
ParsingInformation* _parsingInformation;
OnConnAckInternalCallback _callback;
uint8_t _bytePosition;
bool _sessionPresent;
uint8_t _connectReturnCode;
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,11 @@
#pragma once
namespace AsyncMqttClientInternals {
class Packet {
public:
virtual ~Packet() {}
virtual void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) = 0;
virtual void parsePayload(char* data, size_t len, size_t* currentBytePosition) = 0;
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,21 @@
#include "PingRespPacket.hpp"
using AsyncMqttClientInternals::PingRespPacket;
PingRespPacket::PingRespPacket(ParsingInformation* parsingInformation, OnPingRespInternalCallback callback)
: _parsingInformation(parsingInformation)
, _callback(callback) {
}
PingRespPacket::~PingRespPacket() {
}
void PingRespPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
(void)data;
(void)currentBytePosition;
}
void PingRespPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
(void)data;
(void)currentBytePosition;
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include "Arduino.h"
#include "Packet.hpp"
#include "../ParsingInformation.hpp"
#include "../Callbacks.hpp"
namespace AsyncMqttClientInternals {
class PingRespPacket : public Packet {
public:
explicit PingRespPacket(ParsingInformation* parsingInformation, OnPingRespInternalCallback callback);
~PingRespPacket();
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
private:
ParsingInformation* _parsingInformation;
OnPingRespInternalCallback _callback;
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,30 @@
#include "PubAckPacket.hpp"
using AsyncMqttClientInternals::PubAckPacket;
PubAckPacket::PubAckPacket(ParsingInformation* parsingInformation, OnPubAckInternalCallback callback)
: _parsingInformation(parsingInformation)
, _callback(callback)
, _bytePosition(0)
, _packetIdMsb(0)
, _packetId(0) {
}
PubAckPacket::~PubAckPacket() {
}
void PubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
char currentByte = data[(*currentBytePosition)++];
if (_bytePosition++ == 0) {
_packetIdMsb = currentByte;
} else {
_packetId = currentByte | _packetIdMsb << 8;
_parsingInformation->bufferState = BufferState::NONE;
_callback(_packetId);
}
}
void PubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
(void)data;
(void)currentBytePosition;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Arduino.h"
#include "Packet.hpp"
#include "../ParsingInformation.hpp"
#include "../Callbacks.hpp"
namespace AsyncMqttClientInternals {
class PubAckPacket : public Packet {
public:
explicit PubAckPacket(ParsingInformation* parsingInformation, OnPubAckInternalCallback callback);
~PubAckPacket();
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
private:
ParsingInformation* _parsingInformation;
OnPubAckInternalCallback _callback;
uint8_t _bytePosition;
char _packetIdMsb;
uint16_t _packetId;
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,30 @@
#include "PubCompPacket.hpp"
using AsyncMqttClientInternals::PubCompPacket;
PubCompPacket::PubCompPacket(ParsingInformation* parsingInformation, OnPubCompInternalCallback callback)
: _parsingInformation(parsingInformation)
, _callback(callback)
, _bytePosition(0)
, _packetIdMsb(0)
, _packetId(0) {
}
PubCompPacket::~PubCompPacket() {
}
void PubCompPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
char currentByte = data[(*currentBytePosition)++];
if (_bytePosition++ == 0) {
_packetIdMsb = currentByte;
} else {
_packetId = currentByte | _packetIdMsb << 8;
_parsingInformation->bufferState = BufferState::NONE;
_callback(_packetId);
}
}
void PubCompPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
(void)data;
(void)currentBytePosition;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Arduino.h"
#include "Packet.hpp"
#include "../ParsingInformation.hpp"
#include "../Callbacks.hpp"
namespace AsyncMqttClientInternals {
class PubCompPacket : public Packet {
public:
explicit PubCompPacket(ParsingInformation* parsingInformation, OnPubCompInternalCallback callback);
~PubCompPacket();
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
private:
ParsingInformation* _parsingInformation;
OnPubCompInternalCallback _callback;
uint8_t _bytePosition;
char _packetIdMsb;
uint16_t _packetId;
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,30 @@
#include "PubRecPacket.hpp"
using AsyncMqttClientInternals::PubRecPacket;
PubRecPacket::PubRecPacket(ParsingInformation* parsingInformation, OnPubRecInternalCallback callback)
: _parsingInformation(parsingInformation)
, _callback(callback)
, _bytePosition(0)
, _packetIdMsb(0)
, _packetId(0) {
}
PubRecPacket::~PubRecPacket() {
}
void PubRecPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
char currentByte = data[(*currentBytePosition)++];
if (_bytePosition++ == 0) {
_packetIdMsb = currentByte;
} else {
_packetId = currentByte | _packetIdMsb << 8;
_parsingInformation->bufferState = BufferState::NONE;
_callback(_packetId);
}
}
void PubRecPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
(void)data;
(void)currentBytePosition;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Arduino.h"
#include "Packet.hpp"
#include "../ParsingInformation.hpp"
#include "../Callbacks.hpp"
namespace AsyncMqttClientInternals {
class PubRecPacket : public Packet {
public:
explicit PubRecPacket(ParsingInformation* parsingInformation, OnPubRecInternalCallback callback);
~PubRecPacket();
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
private:
ParsingInformation* _parsingInformation;
OnPubRecInternalCallback _callback;
uint8_t _bytePosition;
char _packetIdMsb;
uint16_t _packetId;
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,30 @@
#include "PubRelPacket.hpp"
using AsyncMqttClientInternals::PubRelPacket;
PubRelPacket::PubRelPacket(ParsingInformation* parsingInformation, OnPubRelInternalCallback callback)
: _parsingInformation(parsingInformation)
, _callback(callback)
, _bytePosition(0)
, _packetIdMsb(0)
, _packetId(0) {
}
PubRelPacket::~PubRelPacket() {
}
void PubRelPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
char currentByte = data[(*currentBytePosition)++];
if (_bytePosition++ == 0) {
_packetIdMsb = currentByte;
} else {
_packetId = currentByte | _packetIdMsb << 8;
_parsingInformation->bufferState = BufferState::NONE;
_callback(_packetId);
}
}
void PubRelPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
(void)data;
(void)currentBytePosition;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Arduino.h"
#include "Packet.hpp"
#include "../ParsingInformation.hpp"
#include "../Callbacks.hpp"
namespace AsyncMqttClientInternals {
class PubRelPacket : public Packet {
public:
explicit PubRelPacket(ParsingInformation* parsingInformation, OnPubRelInternalCallback callback);
~PubRelPacket();
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
private:
ParsingInformation* _parsingInformation;
OnPubRelInternalCallback _callback;
uint8_t _bytePosition;
char _packetIdMsb;
uint16_t _packetId;
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,91 @@
#include "PublishPacket.hpp"
using AsyncMqttClientInternals::PublishPacket;
PublishPacket::PublishPacket(ParsingInformation* parsingInformation, OnMessageInternalCallback dataCallback, OnPublishInternalCallback completeCallback)
: _parsingInformation(parsingInformation)
, _dataCallback(dataCallback)
, _completeCallback(completeCallback)
, _dup(false)
, _qos(0)
, _retain(0)
, _bytePosition(0)
, _topicLengthMsb(0)
, _topicLength(0)
, _ignore(false)
, _packetIdMsb(0)
, _packetId(0)
, _payloadLength(0)
, _payloadBytesRead(0) {
_dup = _parsingInformation->packetFlags & HeaderFlag.PUBLISH_DUP;
_retain = _parsingInformation->packetFlags & HeaderFlag.PUBLISH_RETAIN;
char qosMasked = _parsingInformation->packetFlags & 0x06;
switch (qosMasked) {
case HeaderFlag.PUBLISH_QOS0:
_qos = 0;
break;
case HeaderFlag.PUBLISH_QOS1:
_qos = 1;
break;
case HeaderFlag.PUBLISH_QOS2:
_qos = 2;
break;
}
}
PublishPacket::~PublishPacket() {
}
void PublishPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
char currentByte = data[(*currentBytePosition)++];
if (_bytePosition == 0) {
_topicLengthMsb = currentByte;
} else if (_bytePosition == 1) {
_topicLength = currentByte | _topicLengthMsb << 8;
if (_topicLength > _parsingInformation->maxTopicLength) {
_ignore = true;
} else {
_parsingInformation->topicBuffer[_topicLength] = '\0';
}
} else if (_bytePosition >= 2 && _bytePosition < 2 + _topicLength) {
// Starting from here, _ignore might be true
if (!_ignore) _parsingInformation->topicBuffer[_bytePosition - 2] = currentByte;
if (_bytePosition == 2 + _topicLength - 1 && _qos == 0) {
_preparePayloadHandling(_parsingInformation->remainingLength - (_bytePosition + 1));
return;
}
} else if (_bytePosition == 2 + _topicLength) {
_packetIdMsb = currentByte;
} else {
_packetId = currentByte | _packetIdMsb << 8;
_preparePayloadHandling(_parsingInformation->remainingLength - (_bytePosition + 1));
}
_bytePosition++;
}
void PublishPacket::_preparePayloadHandling(uint32_t payloadLength) {
_payloadLength = payloadLength;
if (payloadLength == 0) {
_parsingInformation->bufferState = BufferState::NONE;
if (!_ignore) {
_dataCallback(_parsingInformation->topicBuffer, nullptr, _qos, _dup, _retain, 0, 0, 0, _packetId);
_completeCallback(_packetId, _qos);
}
} else {
_parsingInformation->bufferState = BufferState::PAYLOAD;
}
}
void PublishPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
size_t remainToRead = len - (*currentBytePosition);
if (_payloadBytesRead + remainToRead > _payloadLength) remainToRead = _payloadLength - _payloadBytesRead;
if (!_ignore) _dataCallback(_parsingInformation->topicBuffer, data + (*currentBytePosition), _qos, _dup, _retain, remainToRead, _payloadBytesRead, _payloadLength, _packetId);
_payloadBytesRead += remainToRead;
(*currentBytePosition) += remainToRead;
if (_payloadBytesRead == _payloadLength) {
_parsingInformation->bufferState = BufferState::NONE;
if (!_ignore) _completeCallback(_packetId, _qos);
}
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include "Arduino.h"
#include "Packet.hpp"
#include "../Flags.hpp"
#include "../ParsingInformation.hpp"
#include "../Callbacks.hpp"
namespace AsyncMqttClientInternals {
class PublishPacket : public Packet {
public:
explicit PublishPacket(ParsingInformation* parsingInformation, OnMessageInternalCallback dataCallback, OnPublishInternalCallback completeCallback);
~PublishPacket();
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
private:
ParsingInformation* _parsingInformation;
OnMessageInternalCallback _dataCallback;
OnPublishInternalCallback _completeCallback;
void _preparePayloadHandling(uint32_t payloadLength);
bool _dup;
uint8_t _qos;
bool _retain;
uint8_t _bytePosition;
char _topicLengthMsb;
uint16_t _topicLength;
bool _ignore;
char _packetIdMsb;
uint16_t _packetId;
uint32_t _payloadLength;
uint32_t _payloadBytesRead;
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,46 @@
#include "SubAckPacket.hpp"
using AsyncMqttClientInternals::SubAckPacket;
SubAckPacket::SubAckPacket(ParsingInformation* parsingInformation, OnSubAckInternalCallback callback)
: _parsingInformation(parsingInformation)
, _callback(callback)
, _bytePosition(0)
, _packetIdMsb(0)
, _packetId(0) {
}
SubAckPacket::~SubAckPacket() {
}
void SubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
char currentByte = data[(*currentBytePosition)++];
if (_bytePosition++ == 0) {
_packetIdMsb = currentByte;
} else {
_packetId = currentByte | _packetIdMsb << 8;
_parsingInformation->bufferState = BufferState::PAYLOAD;
}
}
void SubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
char status = data[(*currentBytePosition)++];
/* switch (status) {
case 0:
Serial.println("Success QoS 0");
break;
case 1:
Serial.println("Success QoS 1");
break;
case 2:
Serial.println("Success QoS 2");
break;
case 0x80:
Serial.println("Failure");
break;
} */
_parsingInformation->bufferState = BufferState::NONE;
_callback(_packetId, status);
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Arduino.h"
#include "Packet.hpp"
#include "../ParsingInformation.hpp"
#include "../Callbacks.hpp"
namespace AsyncMqttClientInternals {
class SubAckPacket : public Packet {
public:
explicit SubAckPacket(ParsingInformation* parsingInformation, OnSubAckInternalCallback callback);
~SubAckPacket();
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
private:
ParsingInformation* _parsingInformation;
OnSubAckInternalCallback _callback;
uint8_t _bytePosition;
char _packetIdMsb;
uint16_t _packetId;
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,30 @@
#include "UnsubAckPacket.hpp"
using AsyncMqttClientInternals::UnsubAckPacket;
UnsubAckPacket::UnsubAckPacket(ParsingInformation* parsingInformation, OnUnsubAckInternalCallback callback)
: _parsingInformation(parsingInformation)
, _callback(callback)
, _bytePosition(0)
, _packetIdMsb(0)
, _packetId(0) {
}
UnsubAckPacket::~UnsubAckPacket() {
}
void UnsubAckPacket::parseVariableHeader(char* data, size_t len, size_t* currentBytePosition) {
char currentByte = data[(*currentBytePosition)++];
if (_bytePosition++ == 0) {
_packetIdMsb = currentByte;
} else {
_packetId = currentByte | _packetIdMsb << 8;
_parsingInformation->bufferState = BufferState::NONE;
_callback(_packetId);
}
}
void UnsubAckPacket::parsePayload(char* data, size_t len, size_t* currentBytePosition) {
(void)data;
(void)currentBytePosition;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include "Arduino.h"
#include "Packet.hpp"
#include "../ParsingInformation.hpp"
#include "../Callbacks.hpp"
namespace AsyncMqttClientInternals {
class UnsubAckPacket : public Packet {
public:
explicit UnsubAckPacket(ParsingInformation* parsingInformation, OnUnsubAckInternalCallback callback);
~UnsubAckPacket();
void parseVariableHeader(char* data, size_t len, size_t* currentBytePosition);
void parsePayload(char* data, size_t len, size_t* currentBytePosition);
private:
ParsingInformation* _parsingInformation;
OnUnsubAckInternalCallback _callback;
uint8_t _bytePosition;
char _packetIdMsb;
uint16_t _packetId;
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,21 @@
#pragma once
namespace AsyncMqttClientInternals {
enum class BufferState : uint8_t {
NONE = 0,
REMAINING_LENGTH = 2,
VARIABLE_HEADER = 3,
PAYLOAD = 4
};
struct ParsingInformation {
BufferState bufferState;
uint16_t maxTopicLength;
char* topicBuffer;
uint8_t packetType;
uint16_t packetFlags;
uint32_t remainingLength;
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,13 @@
#pragma once
namespace AsyncMqttClientInternals {
struct PendingPubRel {
uint16_t packetId;
};
struct PendingAck {
uint8_t packetType;
uint8_t headerFlag;
uint16_t packetId;
};
} // namespace AsyncMqttClientInternals

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Marvin Roger
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.

View File

@@ -0,0 +1,18 @@
Async MQTT client for ESP8266 and ESP32 (Github: https://github.com/marvinroger/async-mqtt-client)
=============================
[![Build Status](https://img.shields.io/travis/marvinroger/async-mqtt-client/master.svg?style=flat-square)](https://travis-ci.org/marvinroger/async-mqtt-client)
An Arduino for ESP8266 and ESP32 asynchronous [MQTT](http://mqtt.org/) client implementation, built on [me-no-dev/ESPAsyncTCP (ESP8266)](https://github.com/me-no-dev/ESPAsyncTCP) | [me-no-dev/AsyncTCP (ESP32)](https://github.com/me-no-dev/AsyncTCP) .
## Features
* Compliant with the 3.1.1 version of the protocol
* Fully asynchronous
* Subscribe at QoS 0, 1 and 2
* Publish at QoS 0, 1 and 2
* SSL/TLS support
* Available in the [PlatformIO registry](http://platformio.org/lib/show/346/AsyncMqttClient)
## Requirements, installation and usage
The project is documented in the [/docs folder](docs).

View File

@@ -1,82 +0,0 @@
/*
* E131.cpp
*
* Project: E131 - E.131 (sACN) library for Arduino
* Copyright (c) 2015 Shelby Merrick
* http://www.forkineye.com
*
* This program is provided free for you to use in any way that you wish,
* subject to the laws and regulations where you are using it. Due diligence
* is strongly suggested before using this code. Please give credit where due.
*
* The Author makes no warranty of any kind, express or implied, with regard
* to this program or the documentation contained in this document. The
* Author shall not be liable in any event for incidental or consequential
* damages in connection with, or arising out of, the furnishing, performance
* or use of these programs.
*
*/
#include "E131.h"
#include <string.h>
/* E1.17 ACN Packet Identifier */
const byte E131::ACN_ID[12] = { 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 };
/* Constructor */
E131::E131() {
#ifdef NO_DOUBLE_BUFFER
memset(pbuff1.raw, 0, sizeof(pbuff1.raw));
packet = &pbuff1;
pwbuff = packet;
#else
memset(pbuff1.raw, 0, sizeof(pbuff1.raw));
memset(pbuff2.raw, 0, sizeof(pbuff2.raw));
packet = &pbuff1;
pwbuff = &pbuff2;
#endif
stats.num_packets = 0;
stats.packet_errors = 0;
}
void E131::initUnicast() {
udp.begin(E131_DEFAULT_PORT);
}
void E131::initMulticast(uint16_t universe, uint8_t n) {
IPAddress address = IPAddress(239, 255, ((universe >> 8) & 0xff),
((universe >> 0) & 0xff));
#ifdef ARDUINO_ARCH_ESP32
ip4_addr_t ifaddr;
ip4_addr_t multicast_addr;
ifaddr.addr = static_cast<uint32_t>(WiFi.localIP());
for (uint8_t i = 1; i < n; i++) {
multicast_addr.addr = static_cast<uint32_t>(IPAddress(239, 255,
(((universe + i) >> 8) & 0xff), (((universe + i) >> 0)
& 0xff)));
igmp_joingroup(&ifaddr, &multicast_addr);
}
udp.beginMulticast(address, E131_DEFAULT_PORT);
#else
ip_addr_t ifaddr;
ip_addr_t multicast_addr;
ifaddr.addr = static_cast<uint32_t>(WiFi.localIP());
for (uint8_t i = 1; i < n; i++) {
multicast_addr.addr = static_cast<uint32_t>(IPAddress(239, 255,
(((universe + i) >> 8) & 0xff), (((universe + i) >> 0)
& 0xff)));
igmp_joingroup(&ifaddr, &multicast_addr);
}
udp.beginMulticast(WiFi.localIP(), address, E131_DEFAULT_PORT);
#endif
}
void E131::begin(e131_listen_t type, uint16_t universe, uint8_t n) {
if (type == E131_UNICAST)
initUnicast();
if (type == E131_MULTICAST)
initMulticast(universe, n);
}

View File

@@ -1,196 +0,0 @@
/*
* E131.h
*
* Project: E131 - E.131 (sACN) library for Arduino
* Copyright (c) 2015 Shelby Merrick
* http://www.forkineye.com
*
* This program is provided free for you to use in any way that you wish,
* subject to the laws and regulations where you are using it. Due diligence
* is strongly suggested before using this code. Please give credit where due.
*
* The Author makes no warranty of any kind, express or implied, with regard
* to this program or the documentation contained in this document. The
* Author shall not be liable in any event for incidental or consequential
* damages in connection with, or arising out of, the furnishing, performance
* or use of these programs.
*
*/
#ifndef E131_H_
#define E131_H_
#include "Arduino.h"
/* Network interface detection. WiFi for ESP8266 and Ethernet for AVR */
#if defined (ARDUINO_ARCH_ESP8266)
# include <ESP8266WiFi.h>
# define NO_DOUBLE_BUFFER
#elif defined (ARDUINO_ARCH_ESP32)
# include <WiFi.h>
#endif
# include <WiFiUdp.h>
# include <lwip/ip_addr.h>
# include <lwip/igmp.h>
# define _UDP WiFiUDP
/* Defaults */
#define E131_DEFAULT_PORT 5568
/* E1.31 Packet Offsets */
#define E131_ROOT_PREAMBLE_SIZE 0
#define E131_ROOT_POSTAMBLE_SIZE 2
#define E131_ROOT_ID 4
#define E131_ROOT_FLENGTH 16
#define E131_ROOT_VECTOR 18
#define E131_ROOT_CID 22
#define E131_FRAME_FLENGTH 38
#define E131_FRAME_VECTOR 40
#define E131_FRAME_SOURCE 44
#define E131_FRAME_PRIORITY 108
#define E131_FRAME_RESERVED 109
#define E131_FRAME_SEQ 111
#define E131_FRAME_OPT 112
#define E131_FRAME_UNIVERSE 113
#define E131_DMP_FLENGTH 115
#define E131_DMP_VECTOR 117
#define E131_DMP_TYPE 118
#define E131_DMP_ADDR_FIRST 119
#define E131_DMP_ADDR_INC 121
#define E131_DMP_COUNT 123
#define E131_DMP_DATA 125
/* E1.31 Packet Structure */
typedef union {
struct {
/* Root Layer */
uint16_t preamble_size;
uint16_t postamble_size;
uint8_t acn_id[12];
uint16_t root_flength;
uint32_t root_vector;
uint8_t cid[16];
/* Frame Layer */
uint16_t frame_flength;
uint32_t frame_vector;
uint8_t source_name[64];
uint8_t priority;
uint16_t reserved;
uint8_t sequence_number;
uint8_t options;
uint16_t universe;
/* DMP Layer */
uint16_t dmp_flength;
uint8_t dmp_vector;
uint8_t type;
uint16_t first_address;
uint16_t address_increment;
uint16_t property_value_count;
uint8_t property_values[513];
} __attribute__((packed));
uint8_t raw[638];
} e131_packet_t;
/* Error Types */
typedef enum {
ERROR_NONE,
ERROR_IGNORE,
ERROR_ACN_ID,
ERROR_PACKET_SIZE,
ERROR_VECTOR_ROOT,
ERROR_VECTOR_FRAME,
ERROR_VECTOR_DMP
} e131_error_t;
/* E1.31 Listener Types */
typedef enum {
E131_UNICAST,
E131_MULTICAST
} e131_listen_t;
/* Status structure */
typedef struct {
uint32_t num_packets;
uint32_t packet_errors;
IPAddress last_clientIP;
uint16_t last_clientPort;
} e131_stats_t;
class E131 {
private:
/* Constants for packet validation */
static const uint8_t ACN_ID[];
static const uint32_t VECTOR_ROOT = 4;
static const uint32_t VECTOR_FRAME = 2;
static const uint8_t VECTOR_DMP = 2;
e131_packet_t pbuff1; /* Packet buffer */
#ifndef NO_DOUBLE_BUFFER
e131_packet_t pbuff2; /* Double buffer */
#endif
e131_packet_t *pwbuff; /* Pointer to working packet buffer */
_UDP udp; /* UDP handle */
/* Internal Initializers */
void initUnicast();
void initMulticast(uint16_t universe, uint8_t n = 1);
public:
uint8_t *data; /* Pointer to DMX channel data */
uint16_t universe; /* DMX Universe of last valid packet */
e131_packet_t *packet; /* Pointer to last valid packet */
e131_stats_t stats; /* Statistics tracker */
E131();
/* Generic UDP listener, no physical or IP configuration */
void begin(e131_listen_t type, uint16_t universe = 1, uint8_t n = 1);
/* Main packet parser */
inline uint16_t parsePacket() {
e131_error_t error;
uint16_t retval = 0;
int size = udp.parsePacket();
if (size) {
udp.readBytes(pwbuff->raw, size);
error = validate();
if (!error) {
#ifndef NO_DOUBLE_BUFFER
e131_packet_t *swap = packet;
packet = pwbuff;
pwbuff = swap;
#endif
universe = htons(packet->universe);
data = packet->property_values + 1;
retval = htons(packet->property_value_count) - 1;
stats.num_packets++;
stats.last_clientIP = udp.remoteIP();
stats.last_clientPort = udp.remotePort();
}
}
return retval;
}
/* Packet validater */
inline e131_error_t validate() {
if (memcmp(pwbuff->acn_id, ACN_ID, sizeof(pwbuff->acn_id)))
return ERROR_ACN_ID;
if (htonl(pwbuff->root_vector) != VECTOR_ROOT)
return ERROR_VECTOR_ROOT;
if (htonl(pwbuff->frame_vector) != VECTOR_FRAME)
return ERROR_VECTOR_FRAME;
if (pwbuff->dmp_vector != VECTOR_DMP)
return ERROR_VECTOR_DMP;
if (pwbuff->property_values[0] != 0)
return ERROR_IGNORE;
return ERROR_NONE;
}
};
#endif /* E131_H_ */

View File

@@ -0,0 +1,116 @@
/*
* ESPAsyncE131.cpp
*
* Project: ESPAsyncE131 - Asynchronous E.131 (sACN) library for Arduino ESP8266 and ESP32
* Copyright (c) 2019 Shelby Merrick
* http://www.forkineye.com
*
* This program is provided free for you to use in any way that you wish,
* subject to the laws and regulations where you are using it. Due diligence
* is strongly suggested before using this code. Please give credit where due.
*
* The Author makes no warranty of any kind, express or implied, with regard
* to this program or the documentation contained in this document. The
* Author shall not be liable in any event for incidental or consequential
* damages in connection with, or arising out of, the furnishing, performance
* or use of these programs.
*
*/
#include "ESPAsyncE131.h"
#include <string.h>
// E1.17 ACN Packet Identifier
const byte ESPAsyncE131::ACN_ID[12] = { 0x41, 0x53, 0x43, 0x2d, 0x45, 0x31, 0x2e, 0x31, 0x37, 0x00, 0x00, 0x00 };
// Constructor
ESPAsyncE131::ESPAsyncE131(e131_packet_callback_function callback) {
_callback = callback;
}
/////////////////////////////////////////////////////////
//
// Public begin() members
//
/////////////////////////////////////////////////////////
bool ESPAsyncE131::begin(e131_listen_t type, uint16_t universe, uint8_t n) {
bool success = false;
if (type == E131_UNICAST)
success = initUnicast();
if (type == E131_MULTICAST)
success = initMulticast(universe, n);
return success;
}
/////////////////////////////////////////////////////////
//
// Private init() members
//
/////////////////////////////////////////////////////////
bool ESPAsyncE131::initUnicast() {
bool success = false;
if (udp.listen(E131_DEFAULT_PORT)) {
udp.onPacket(std::bind(&ESPAsyncE131::parsePacket, this,
std::placeholders::_1));
success = true;
}
return success;
}
bool ESPAsyncE131::initMulticast(uint16_t universe, uint8_t n) {
bool success = false;
IPAddress address = IPAddress(239, 255, ((universe >> 8) & 0xff),
((universe >> 0) & 0xff));
if (udp.listenMulticast(address, E131_DEFAULT_PORT)) {
ip4_addr_t ifaddr;
ip4_addr_t multicast_addr;
ifaddr.addr = static_cast<uint32_t>(WiFi.localIP());
for (uint8_t i = 1; i < n; i++) {
multicast_addr.addr = static_cast<uint32_t>(IPAddress(239, 255,
(((universe + i) >> 8) & 0xff), (((universe + i) >> 0)
& 0xff)));
igmp_joingroup(&ifaddr, &multicast_addr);
}
udp.onPacket(std::bind(&ESPAsyncE131::parsePacket, this,
std::placeholders::_1));
success = true;
}
return success;
}
/////////////////////////////////////////////////////////
//
// Packet parsing - Private
//
/////////////////////////////////////////////////////////
void ESPAsyncE131::parsePacket(AsyncUDPPacket _packet) {
e131_error_t error = ERROR_NONE;
sbuff = reinterpret_cast<e131_packet_t *>(_packet.data());
if (memcmp(sbuff->acn_id, ESPAsyncE131::ACN_ID, sizeof(sbuff->acn_id)))
error = ERROR_ACN_ID;
if (htonl(sbuff->root_vector) != ESPAsyncE131::VECTOR_ROOT)
error = ERROR_VECTOR_ROOT;
if (htonl(sbuff->frame_vector) != ESPAsyncE131::VECTOR_FRAME)
error = ERROR_VECTOR_FRAME;
if (sbuff->dmp_vector != ESPAsyncE131::VECTOR_DMP)
error = ERROR_VECTOR_DMP;
if (sbuff->property_values[0] != 0)
error = ERROR_IGNORE;
if (!error) {
_callback(sbuff, _packet.remoteIP());
}
}

View File

@@ -0,0 +1,151 @@
/*
* ESPAsyncE131.h
*
* Project: ESPAsyncE131 - Asynchronous E.131 (sACN) library for Arduino ESP8266 and ESP32
* Copyright (c) 2019 Shelby Merrick
* http://www.forkineye.com
*
* This program is provided free for you to use in any way that you wish,
* subject to the laws and regulations where you are using it. Due diligence
* is strongly suggested before using this code. Please give credit where due.
*
* The Author makes no warranty of any kind, express or implied, with regard
* to this program or the documentation contained in this document. The
* Author shall not be liable in any event for incidental or consequential
* damages in connection with, or arising out of, the furnishing, performance
* or use of these programs.
*
*/
#ifndef ESPASYNCE131_H_
#define ESPASYNCE131_H_
#ifdef ESP32
#include <WiFi.h>
#include <AsyncUDP.h>
#elif defined (ESP8266)
#include <ESPAsyncUDP.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#else
#error Platform not supported
#endif
#include <lwip/ip_addr.h>
#include <lwip/igmp.h>
#include <Arduino.h>
#if LWIP_VERSION_MAJOR == 1
typedef struct ip_addr ip4_addr_t;
#endif
// Defaults
#define E131_DEFAULT_PORT 5568
// E1.31 Packet Offsets
#define E131_ROOT_PREAMBLE_SIZE 0
#define E131_ROOT_POSTAMBLE_SIZE 2
#define E131_ROOT_ID 4
#define E131_ROOT_FLENGTH 16
#define E131_ROOT_VECTOR 18
#define E131_ROOT_CID 22
#define E131_FRAME_FLENGTH 38
#define E131_FRAME_VECTOR 40
#define E131_FRAME_SOURCE 44
#define E131_FRAME_PRIORITY 108
#define E131_FRAME_RESERVED 109
#define E131_FRAME_SEQ 111
#define E131_FRAME_OPT 112
#define E131_FRAME_UNIVERSE 113
#define E131_DMP_FLENGTH 115
#define E131_DMP_VECTOR 117
#define E131_DMP_TYPE 118
#define E131_DMP_ADDR_FIRST 119
#define E131_DMP_ADDR_INC 121
#define E131_DMP_COUNT 123
#define E131_DMP_DATA 125
// E1.31 Packet Structure
typedef union {
struct {
// Root Layer
uint16_t preamble_size;
uint16_t postamble_size;
uint8_t acn_id[12];
uint16_t root_flength;
uint32_t root_vector;
uint8_t cid[16];
// Frame Layer
uint16_t frame_flength;
uint32_t frame_vector;
uint8_t source_name[64];
uint8_t priority;
uint16_t reserved;
uint8_t sequence_number;
uint8_t options;
uint16_t universe;
// DMP Layer
uint16_t dmp_flength;
uint8_t dmp_vector;
uint8_t type;
uint16_t first_address;
uint16_t address_increment;
uint16_t property_value_count;
uint8_t property_values[513];
} __attribute__((packed));
uint8_t raw[638];
} e131_packet_t;
// Error Types
typedef enum {
ERROR_NONE,
ERROR_IGNORE,
ERROR_ACN_ID,
ERROR_PACKET_SIZE,
ERROR_VECTOR_ROOT,
ERROR_VECTOR_FRAME,
ERROR_VECTOR_DMP
} e131_error_t;
// E1.31 Listener Types
typedef enum {
E131_UNICAST,
E131_MULTICAST
} e131_listen_t;
// new packet callback
typedef void (*e131_packet_callback_function) (e131_packet_t* p, IPAddress clientIP);
class ESPAsyncE131 {
private:
// Constants for packet validation
static const uint8_t ACN_ID[];
static const uint32_t VECTOR_ROOT = 4;
static const uint32_t VECTOR_FRAME = 2;
static const uint8_t VECTOR_DMP = 2;
e131_packet_t *sbuff; // Pointer to scratch packet buffer
AsyncUDP udp; // AsyncUDP
// Internal Initializers
bool initUnicast();
bool initMulticast(uint16_t universe, uint8_t n = 1);
// Packet parser callback
void parsePacket(AsyncUDPPacket _packet);
e131_packet_callback_function _callback = nullptr;
public:
ESPAsyncE131(e131_packet_callback_function callback);
// Generic UDP listener, no physical or IP configuration
bool begin(e131_listen_t type, uint16_t universe = 1, uint8_t n = 1);
};
#endif // ESPASYNCE131_H_

View File

@@ -0,0 +1,587 @@
#ifndef Espalexa_h
#define Espalexa_h
/*
* Alexa Voice On/Off/Brightness/Color Control. Emulates a Philips Hue bridge to Alexa.
*
* This was put together from these two excellent projects:
* https://github.com/kakopappa/arduino-esp8266-alexa-wemo-switch
* https://github.com/probonopd/ESP8266HueEmulator
*/
/*
* @title Espalexa library
* @version 2.4.3
* @author Christian Schwinne
* @license MIT
* @contributors d-999
*/
#include "Arduino.h"
//you can use these defines for library config in your sketch. Just use them before #include <Espalexa.h>
//#define ESPALEXA_ASYNC
//in case this is unwanted in your application (will disable the /espalexa value page)
//#define ESPALEXA_NO_SUBPAGE
#ifndef ESPALEXA_MAXDEVICES
#define ESPALEXA_MAXDEVICES 10 //this limit only has memory reasons, set it higher should you need to
#endif
//#define ESPALEXA_DEBUG
#ifdef ESPALEXA_ASYNC
#ifdef ARDUINO_ARCH_ESP32
#include <AsyncTCP.h>
#else
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
#else
#ifdef ARDUINO_ARCH_ESP32
#include <WiFi.h>
#include <WebServer.h> //if you get an error here please update to ESP32 arduino core 1.0.0
#else
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#endif
#endif
#include <WiFiUdp.h>
#ifdef ESPALEXA_DEBUG
#pragma message "Espalexa 2.4.3 debug mode"
#define EA_DEBUG(x) Serial.print (x)
#define EA_DEBUGLN(x) Serial.println (x)
#else
#define EA_DEBUG(x)
#define EA_DEBUGLN(x)
#endif
#include "EspalexaDevice.h"
class Espalexa {
private:
//private member vars
#ifdef ESPALEXA_ASYNC
AsyncWebServer* serverAsync;
AsyncWebServerRequest* server; //this saves many #defines
String body = "";
#elif defined ARDUINO_ARCH_ESP32
WebServer* server;
#else
ESP8266WebServer* server;
#endif
uint8_t currentDeviceCount = 0;
bool discoverable = true;
EspalexaDevice* devices[ESPALEXA_MAXDEVICES] = {};
//Keep in mind that Device IDs go from 1 to DEVICES, cpp arrays from 0 to DEVICES-1!!
WiFiUDP espalexaUdp;
IPAddress ipMulti;
bool udpConnected = false;
char packetBuffer[255]; //buffer to hold incoming udp packet
String escapedMac=""; //lowercase mac address
//private member functions
String boolString(bool st)
{
return(st)?"true":"false";
}
String modeString(EspalexaColorMode m)
{
if (m == EspalexaColorMode::xy) return "xy";
if (m == EspalexaColorMode::hs) return "hs";
return "ct";
}
String typeString(EspalexaDeviceType t)
{
switch (t)
{
case EspalexaDeviceType::dimmable: return "Dimmable light";
case EspalexaDeviceType::whitespectrum: return "Color temperature light";
case EspalexaDeviceType::color: return "Color light";
case EspalexaDeviceType::extendedcolor: return "Extended color light";
}
return "Light";
}
String modelidString(EspalexaDeviceType t)
{
switch (t)
{
case EspalexaDeviceType::dimmable: return "LWB010";
case EspalexaDeviceType::whitespectrum: return "LWT010";
case EspalexaDeviceType::color: return "LST001";
case EspalexaDeviceType::extendedcolor: return "LCT015";
}
return "Plug";
}
//Workaround functions courtesy of Sonoff-Tasmota
uint32_t encodeLightId(uint8_t idx)
{
uint8_t mac[6];
WiFi.macAddress(mac);
uint32_t id = (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4) | (idx & 0xF);
return id;
}
uint32_t decodeLightId(uint32_t id) {
return id & 0xF;
}
//device JSON string: color+temperature device emulates LCT015, dimmable device LWB010, (TODO: on/off Plug 01, color temperature device LWT010, color device LST001)
String deviceJsonString(uint8_t deviceId)
{
deviceId--;
if (deviceId >= currentDeviceCount) return "{}"; //error
EspalexaDevice* dev = devices[deviceId];
String json = "{\"state\":{\"on\":";
json += boolString(dev->getValue());
if (dev->getType() != EspalexaDeviceType::onoff) //bri support
{
json += ",\"bri\":" + String(dev->getLastValue()-1);
if (static_cast<uint8_t>(dev->getType()) > 2) //color support
{
json += ",\"hue\":" + String(dev->getHue()) + ",\"sat\":" + String(dev->getSat());
json += ",\"effect\":\"none\",\"xy\":[" + String(dev->getX()) + "," + String(dev->getY()) + "]";
}
if (static_cast<uint8_t>(dev->getType()) > 1 && dev->getType() != EspalexaDeviceType::color) //white spectrum support
{
json += ",\"ct\":" + String(dev->getCt());
}
}
json += ",\"alert\":\"none";
if (static_cast<uint8_t>(dev->getType()) > 1) json += "\",\"colormode\":\"" + modeString(dev->getColorMode());
json += "\",\"mode\":\"homeautomation\",\"reachable\":true},";
json += "\"type\":\"" + typeString(dev->getType());
json += "\",\"name\":\"" + dev->getName();
json += "\",\"modelid\":\"" + modelidString(dev->getType());
json += "\",\"manufacturername\":\"Philips\",\"productname\":\"E" + String(static_cast<uint8_t>(dev->getType()));
json += "\",\"uniqueid\":\"" + String(encodeLightId(deviceId+1));
json += "\",\"swversion\":\"espalexa-2.4.3\"}";
return json;
}
//Espalexa status page /espalexa
#ifndef ESPALEXA_NO_SUBPAGE
void servePage()
{
EA_DEBUGLN("HTTP Req espalexa ...\n");
String res = "Hello from Espalexa!\r\n\r\n";
for (int i=0; i<currentDeviceCount; i++)
{
EspalexaDevice* dev = devices[i];
res += "Value of device " + String(i+1) + " (" + dev->getName() + "): " + String(dev->getValue()) + " (" + typeString(dev->getType());
if (static_cast<uint8_t>(dev->getType()) > 1) //color support
{
res += ", colormode=" + modeString(dev->getColorMode()) + ", r=" + String(dev->getR()) + ", g=" + String(dev->getG()) + ", b=" + String(dev->getB());
res +=", ct=" + String(dev->getCt()) + ", hue=" + String(dev->getHue()) + ", sat=" + String(dev->getSat()) + ", x=" + String(dev->getX()) + ", y=" + String(dev->getY());
}
res += ")\r\n";
}
res += "\r\nFree Heap: " + (String)ESP.getFreeHeap();
res += "\r\nUptime: " + (String)millis();
res += "\r\n\r\nEspalexa library v2.4.3 by Christian Schwinne 2019";
server->send(200, "text/plain", res);
}
#endif
//not found URI (only if internal webserver is used)
void serveNotFound()
{
EA_DEBUGLN("Not-Found HTTP call:");
#ifndef ESPALEXA_ASYNC
EA_DEBUGLN("URI: " + server->uri());
EA_DEBUGLN("Body: " + server->arg(0));
if(!handleAlexaApiCall(server->uri(), server->arg(0)))
#else
EA_DEBUGLN("URI: " + server->url());
EA_DEBUGLN("Body: " + body);
if(!handleAlexaApiCall(server))
#endif
server->send(404, "text/plain", "Not Found (espalexa-internal)");
}
//send description.xml device property page
void serveDescription()
{
EA_DEBUGLN("# Responding to description.xml ... #\n");
IPAddress localIP = WiFi.localIP();
char s[16];
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
String setup_xml = "<?xml version=\"1.0\" ?>"
"<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
"<specVersion><major>1</major><minor>0</minor></specVersion>"
"<URLBase>http://"+ String(s) +":80/</URLBase>"
"<device>"
"<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>"
"<friendlyName>Espalexa ("+ String(s) +")</friendlyName>"
"<manufacturer>Royal Philips Electronics</manufacturer>"
"<manufacturerURL>http://www.philips.com</manufacturerURL>"
"<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>"
"<modelName>Philips hue bridge 2012</modelName>"
"<modelNumber>929000226503</modelNumber>"
"<modelURL>http://www.meethue.com</modelURL>"
"<serialNumber>"+ escapedMac +"</serialNumber>"
"<UDN>uuid:2f402f80-da50-11e1-9b23-"+ escapedMac +"</UDN>"
"<presentationURL>index.html</presentationURL>"
"</device>"
"</root>";
server->send(200, "text/xml", setup_xml.c_str());
EA_DEBUG("Sending :");
EA_DEBUGLN(setup_xml);
}
//init the server
void startHttpServer()
{
#ifdef ESPALEXA_ASYNC
if (serverAsync == nullptr) {
serverAsync = new AsyncWebServer(80);
serverAsync->onNotFound([=](AsyncWebServerRequest *request){server = request; serveNotFound();});
}
serverAsync->onRequestBody([=](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){
char b[len +1];
b[len] = 0;
memcpy(b, data, len);
body = b; //save the body so we can use it for the API call
EA_DEBUG("Received body: ");
EA_DEBUGLN(body);
});
#ifndef ESPALEXA_NO_SUBPAGE
serverAsync->on("/espalexa", HTTP_GET, [=](AsyncWebServerRequest *request){server = request; servePage();});
#endif
serverAsync->on("/description.xml", HTTP_GET, [=](AsyncWebServerRequest *request){server = request; serveDescription();});
serverAsync->begin();
#else
if (server == nullptr) {
#ifdef ARDUINO_ARCH_ESP32
server = new WebServer(80);
#else
server = new ESP8266WebServer(80);
#endif
server->onNotFound([=](){serveNotFound();});
}
#ifndef ESPALEXA_NO_SUBPAGE
server->on("/espalexa", HTTP_GET, [=](){servePage();});
#endif
server->on("/description.xml", HTTP_GET, [=](){serveDescription();});
server->begin();
#endif
}
//respond to UDP SSDP M-SEARCH
void respondToSearch()
{
IPAddress localIP = WiFi.localIP();
char s[16];
sprintf(s, "%d.%d.%d.%d", localIP[0], localIP[1], localIP[2], localIP[3]);
String response =
"HTTP/1.1 200 OK\r\n"
"EXT:\r\n"
"CACHE-CONTROL: max-age=100\r\n" // SSDP_INTERVAL
"LOCATION: http://"+ String(s) +":80/description.xml\r\n"
"SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/1.17.0\r\n" // _modelName, _modelNumber
"hue-bridgeid: "+ escapedMac +"\r\n"
"ST: urn:schemas-upnp-org:device:basic:1\r\n" // _deviceType
"USN: uuid:2f402f80-da50-11e1-9b23-"+ escapedMac +"::upnp:rootdevice\r\n" // _uuid::_deviceType
"\r\n";
espalexaUdp.beginPacket(espalexaUdp.remoteIP(), espalexaUdp.remotePort());
#ifdef ARDUINO_ARCH_ESP32
espalexaUdp.write((uint8_t*)response.c_str(), response.length());
#else
espalexaUdp.write(response.c_str());
#endif
espalexaUdp.endPacket();
}
public:
Espalexa(){}
//initialize interfaces
#ifdef ESPALEXA_ASYNC
bool begin(AsyncWebServer* externalServer = nullptr)
#elif defined ARDUINO_ARCH_ESP32
bool begin(WebServer* externalServer = nullptr)
#else
bool begin(ESP8266WebServer* externalServer = nullptr)
#endif
{
EA_DEBUGLN("Espalexa Begin...");
EA_DEBUG("MAXDEVICES ");
EA_DEBUGLN(ESPALEXA_MAXDEVICES);
escapedMac = WiFi.macAddress();
escapedMac.replace(":", "");
escapedMac.toLowerCase();
#ifdef ESPALEXA_ASYNC
serverAsync = externalServer;
#else
server = externalServer;
#endif
#ifdef ARDUINO_ARCH_ESP32
udpConnected = espalexaUdp.beginMulticast(IPAddress(239, 255, 255, 250), 1900);
#else
udpConnected = espalexaUdp.beginMulticast(WiFi.localIP(), IPAddress(239, 255, 255, 250), 1900);
#endif
if (udpConnected){
startHttpServer();
EA_DEBUGLN("Done");
return true;
}
EA_DEBUGLN("Failed");
return false;
}
//service loop
void loop() {
#ifndef ESPALEXA_ASYNC
if (server == nullptr) return; //only if begin() was not called
server->handleClient();
#endif
if (!udpConnected) return;
int packetSize = espalexaUdp.parsePacket();
if (!packetSize) return; //no new udp packet
EA_DEBUGLN("Got UDP!");
int len = espalexaUdp.read(packetBuffer, 254);
if (len > 0) {
packetBuffer[len] = 0;
}
espalexaUdp.flush();
if (!discoverable) return; //do not reply to M-SEARCH if not discoverable
String request = packetBuffer;
if(request.indexOf("M-SEARCH") >= 0) {
EA_DEBUGLN(request);
if(request.indexOf("upnp:rootdevice") > 0 || request.indexOf("asic:1") > 0) {
EA_DEBUGLN("Responding search req...");
respondToSearch();
}
}
}
bool addDevice(EspalexaDevice* d)
{
EA_DEBUG("Adding device ");
EA_DEBUGLN((currentDeviceCount+1));
if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false;
if (d == nullptr) return false;
d->setId(currentDeviceCount);
devices[currentDeviceCount] = d;
currentDeviceCount++;
return true;
}
//brightness-only callback
bool addDevice(String deviceName, BrightnessCallbackFunction callback, uint8_t initialValue = 0)
{
EA_DEBUG("Constructing device ");
EA_DEBUGLN((currentDeviceCount+1));
if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false;
EspalexaDevice* d = new EspalexaDevice(deviceName, callback, initialValue);
return addDevice(d);
}
//brightness-only callback
bool addDevice(String deviceName, ColorCallbackFunction callback, uint8_t initialValue = 0)
{
EA_DEBUG("Constructing device ");
EA_DEBUGLN((currentDeviceCount+1));
if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false;
EspalexaDevice* d = new EspalexaDevice(deviceName, callback, initialValue);
return addDevice(d);
}
bool addDevice(String deviceName, DeviceCallbackFunction callback, EspalexaDeviceType t = EspalexaDeviceType::dimmable, uint8_t initialValue = 0)
{
EA_DEBUG("Constructing device ");
EA_DEBUGLN((currentDeviceCount+1));
if (currentDeviceCount >= ESPALEXA_MAXDEVICES) return false;
EspalexaDevice* d = new EspalexaDevice(deviceName, callback, t, initialValue);
return addDevice(d);
}
//basic implementation of Philips hue api functions needed for basic Alexa control
#ifdef ESPALEXA_ASYNC
bool handleAlexaApiCall(AsyncWebServerRequest* request)
{
server = request; //copy request reference
String req = request->url(); //body from global variable
EA_DEBUGLN(request->contentType());
if (request->hasParam("body", true)) // This is necessary, otherwise ESP crashes if there is no body
{
EA_DEBUG("BodyMethod2");
body = request->getParam("body", true)->value();
}
EA_DEBUG("FinalBody: ");
EA_DEBUGLN(body);
#else
bool handleAlexaApiCall(String req, String body)
{
#endif
EA_DEBUGLN("AlexaApiCall");
if (req.indexOf("api") <0) return false; //return if not an API call
EA_DEBUGLN("ok");
if (body.indexOf("devicetype") > 0) //client wants a hue api username, we don't care and give static
{
EA_DEBUGLN("devType");
body = "";
server->send(200, "application/json", "[{\"success\":{\"username\":\"2WLEDHardQrI3WHYTHoMcXHgEspsM8ZZRpSKtBQr\"}}]");
return true;
}
if (req.indexOf("state") > 0) //client wants to control light
{
server->send(200, "application/json", "[{\"success\":{\"/lights/1/state/\": true}}]");
uint32_t devId = req.substring(req.indexOf("lights")+7).toInt();
EA_DEBUG("ls"); EA_DEBUGLN(devId);
devId = decodeLightId(devId);
EA_DEBUGLN(devId);
devId--; //zero-based for devices array
if (devId >= currentDeviceCount) return true; //return if invalid ID
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::none);
if (body.indexOf("false")>0) //OFF command
{
devices[devId]->setValue(0);
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::off);
devices[devId]->doCallback();
return true;
}
if (body.indexOf("true") >0) //ON command
{
devices[devId]->setValue(devices[devId]->getLastValue());
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::on);
}
if (body.indexOf("bri") >0) //BRIGHTNESS command
{
uint8_t briL = body.substring(body.indexOf("bri") +5).toInt();
if (briL == 255)
{
devices[devId]->setValue(255);
} else {
devices[devId]->setValue(briL+1);
}
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::bri);
}
if (body.indexOf("xy") >0) //COLOR command (XY mode)
{
devices[devId]->setColorXY(body.substring(body.indexOf("[") +1).toFloat(), body.substring(body.indexOf(",0") +1).toFloat());
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::xy);
}
if (body.indexOf("hue") >0) //COLOR command (HS mode)
{
devices[devId]->setColor(body.substring(body.indexOf("hue") +5).toInt(), body.substring(body.indexOf("sat") +5).toInt());
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::hs);
}
if (body.indexOf("ct") >0) //COLOR TEMP command (white spectrum)
{
devices[devId]->setColor(body.substring(body.indexOf("ct") +4).toInt());
devices[devId]->setPropertyChanged(EspalexaDeviceProperty::ct);
}
devices[devId]->doCallback();
#ifdef ESPALEXA_DEBUG
if (devices[devId]->getLastChangedProperty() == EspalexaDeviceProperty::none)
EA_DEBUGLN("STATE REQ WITHOUT BODY (likely Content-Type issue #6)");
#endif
return true;
}
int pos = req.indexOf("lights");
if (pos > 0) //client wants light info
{
int devId = req.substring(pos+7).toInt();
EA_DEBUG("l"); EA_DEBUGLN(devId);
if (devId == 0) //client wants all lights
{
EA_DEBUGLN("lAll");
String jsonTemp = "{";
for (int i = 0; i<currentDeviceCount; i++)
{
jsonTemp += "\"" + String(encodeLightId(i+1)) + "\":";
jsonTemp += deviceJsonString(i+1);
if (i < currentDeviceCount-1) jsonTemp += ",";
}
jsonTemp += "}";
server->send(200, "application/json", jsonTemp);
} else //client wants one light (devId)
{
devId = decodeLightId(devId);
EA_DEBUGLN(devId);
if (devId > currentDeviceCount)
{
server->send(200, "application/json", "{}");
} else {
server->send(200, "application/json", deviceJsonString(devId));
}
}
return true;
}
//we don't care about other api commands at this time and send empty JSON
server->send(200, "application/json", "{}");
return true;
}
//set whether Alexa can discover any devices
void setDiscoverable(bool d)
{
discoverable = d;
}
//get EspalexaDevice at specific index
EspalexaDevice* getDevice(uint8_t index)
{
if (index >= currentDeviceCount) return nullptr;
return devices[index];
}
//is an unique device ID
String getEscapedMac()
{
return escapedMac;
}
//convert brightness (0-255) to percentage
uint8_t toPercent(uint8_t bri)
{
uint16_t perc = bri * 100;
return perc / 255;
}
~Espalexa(){delete devices;} //note: Espalexa is NOT meant to be destructed
};
#endif

View File

@@ -0,0 +1,320 @@
//EspalexaDevice Class
#include "EspalexaDevice.h"
EspalexaDevice::EspalexaDevice(){}
EspalexaDevice::EspalexaDevice(String deviceName, BrightnessCallbackFunction gnCallback, uint8_t initialValue) { //constructor for dimmable device
_deviceName = deviceName;
_callback = gnCallback;
_val = initialValue;
_val_last = _val;
_type = EspalexaDeviceType::dimmable;
}
EspalexaDevice::EspalexaDevice(String deviceName, ColorCallbackFunction gnCallback, uint8_t initialValue) { //constructor for color device
_deviceName = deviceName;
_callbackCol = gnCallback;
_val = initialValue;
_val_last = _val;
_type = EspalexaDeviceType::extendedcolor;
}
EspalexaDevice::EspalexaDevice(String deviceName, DeviceCallbackFunction gnCallback, EspalexaDeviceType t, uint8_t initialValue) { //constructor for general device
_deviceName = deviceName;
_callbackDev = gnCallback;
_type = t;
if (t == EspalexaDeviceType::onoff) _type = EspalexaDeviceType::dimmable; //on/off is broken, so make dimmable device instead
_val = initialValue;
_val_last = _val;
}
EspalexaDevice::~EspalexaDevice(){/*nothing to destruct*/}
uint8_t EspalexaDevice::getId()
{
return _id;
}
EspalexaColorMode EspalexaDevice::getColorMode()
{
return _mode;
}
EspalexaDeviceType EspalexaDevice::getType()
{
return _type;
}
String EspalexaDevice::getName()
{
return _deviceName;
}
EspalexaDeviceProperty EspalexaDevice::getLastChangedProperty()
{
return _changed;
}
uint8_t EspalexaDevice::getValue()
{
return _val;
}
uint8_t EspalexaDevice::getPercent()
{
uint16_t perc = _val * 100;
return perc / 255;
}
uint8_t EspalexaDevice::getDegrees()
{
return getPercent();
}
uint16_t EspalexaDevice::getHue()
{
return _hue;
}
uint8_t EspalexaDevice::getSat()
{
return _sat;
}
float EspalexaDevice::getX()
{
return _x;
}
float EspalexaDevice::getY()
{
return _y;
}
uint16_t EspalexaDevice::getCt()
{
if (_ct == 0) return 500;
return _ct;
}
uint32_t EspalexaDevice::getKelvin()
{
if (_ct == 0) return 2000;
return 1000000/_ct;
}
uint32_t EspalexaDevice::getRGB()
{
if (_rgb != 0) return _rgb; //color has not changed
uint8_t rgb[3];
float r, g, b;
if (_mode == EspalexaColorMode::none) return 0;
if (_mode == EspalexaColorMode::ct)
{
//TODO tweak a bit to match hue lamp characteristics
//based on https://gist.github.com/paulkaplan/5184275
float temp = 10000/ _ct; //kelvins = 1,000,000/mired (and that /100)
float r, g, b;
if( temp <= 66 ){
r = 255;
g = temp;
g = 99.470802 * log(g) - 161.119568;
if( temp <= 19){
b = 0;
} else {
b = temp-10;
b = 138.517731 * log(b) - 305.044793;
}
} else {
r = temp - 60;
r = 329.698727 * pow(r, -0.13320476);
g = temp - 60;
g = 288.12217 * pow(g, -0.07551485 );
b = 255;
}
rgb[0] = (byte)constrain(r,0.1,255.1);
rgb[1] = (byte)constrain(g,0.1,255.1);
rgb[2] = (byte)constrain(b,0.1,255.1);
} else if (_mode == EspalexaColorMode::hs)
{
float h = ((float)_hue)/65535.0;
float s = ((float)_sat)/255.0;
byte i = floor(h*6);
float f = h * 6-i;
float p = 255 * (1-s);
float q = 255 * (1-f*s);
float t = 255 * (1-(1-f)*s);
switch (i%6) {
case 0: rgb[0]=255,rgb[1]=t,rgb[2]=p;break;
case 1: rgb[0]=q,rgb[1]=255,rgb[2]=p;break;
case 2: rgb[0]=p,rgb[1]=255,rgb[2]=t;break;
case 3: rgb[0]=p,rgb[1]=q,rgb[2]=255;break;
case 4: rgb[0]=t,rgb[1]=p,rgb[2]=255;break;
case 5: rgb[0]=255,rgb[1]=p,rgb[2]=q;
}
} else if (_mode == EspalexaColorMode::xy)
{
//Source: https://www.developers.meethue.com/documentation/color-conversions-rgb-xy
float z = 1.0f - _x - _y;
float X = (1.0f / _y) * _x;
float Z = (1.0f / _y) * z;
float r = (int)255*(X * 1.656492f - 0.354851f - Z * 0.255038f);
float g = (int)255*(-X * 0.707196f + 1.655397f + Z * 0.036152f);
float b = (int)255*(X * 0.051713f - 0.121364f + Z * 1.011530f);
if (r > b && r > g && r > 1.0f) {
// red is too big
g = g / r;
b = b / r;
r = 1.0f;
} else if (g > b && g > r && g > 1.0f) {
// green is too big
r = r / g;
b = b / g;
g = 1.0f;
} else if (b > r && b > g && b > 1.0f) {
// blue is too big
r = r / b;
g = g / b;
b = 1.0f;
}
// Apply gamma correction
r = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * pow(r, (1.0f / 2.4f)) - 0.055f;
g = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * pow(g, (1.0f / 2.4f)) - 0.055f;
b = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * pow(b, (1.0f / 2.4f)) - 0.055f;
if (r > b && r > g) {
// red is biggest
if (r > 1.0f) {
g = g / r;
b = b / r;
r = 1.0f;
}
} else if (g > b && g > r) {
// green is biggest
if (g > 1.0f) {
r = r / g;
b = b / g;
g = 1.0f;
}
} else if (b > r && b > g) {
// blue is biggest
if (b > 1.0f) {
r = r / b;
g = g / b;
b = 1.0f;
}
}
rgb[0] = 255.0*r;
rgb[1] = 255.0*g;
rgb[2] = 255.0*b;
}
_rgb = ((rgb[0] << 16) | (rgb[1] << 8) | (rgb[2]));
return _rgb;
}
uint8_t EspalexaDevice::getR()
{
return (getRGB() >> 16) & 0xFF;
}
uint8_t EspalexaDevice::getG()
{
return (getRGB() >> 8) & 0xFF;
}
uint8_t EspalexaDevice::getB()
{
return getRGB() & 0xFF;
}
uint8_t EspalexaDevice::getLastValue()
{
if (_val_last == 0) return 255;
return _val_last;
}
void EspalexaDevice::setPropertyChanged(EspalexaDeviceProperty p)
{
_changed = p;
}
void EspalexaDevice::setId(uint8_t id)
{
_id = id;
}
//you need to re-discover the device for the Alexa name to change
void EspalexaDevice::setName(String name)
{
_deviceName = name;
}
void EspalexaDevice::setValue(uint8_t val)
{
if (_val != 0)
{
_val_last = _val;
}
if (val != 0)
{
_val_last = val;
}
_val = val;
}
void EspalexaDevice::setPercent(uint8_t perc)
{
uint16_t val = perc * 255;
val /= 100;
if (val > 255) val = 255;
setValue(val);
}
void EspalexaDevice::setColorXY(float x, float y)
{
_x = x;
_y = y;
_rgb = 0;
_mode = EspalexaColorMode::xy;
}
void EspalexaDevice::setColor(uint16_t hue, uint8_t sat)
{
_hue = hue;
_sat = sat;
_rgb = 0;
_mode = EspalexaColorMode::hs;
}
void EspalexaDevice::setColor(uint16_t ct)
{
_ct = ct;
_rgb = 0;
_mode =EspalexaColorMode::ct;
}
void EspalexaDevice::setColor(uint8_t r, uint8_t g, uint8_t b)
{
float X = r * 0.664511f + g * 0.154324f + b * 0.162028f;
float Y = r * 0.283881f + g * 0.668433f + b * 0.047685f;
float Z = r * 0.000088f + g * 0.072310f + b * 0.986039f;
_x = X / (X + Y + Z);
_y = Y / (X + Y + Z);
_rgb = ((r << 16) | (g << 8) | b);
_mode = EspalexaColorMode::xy;
}
void EspalexaDevice::doCallback()
{
if (_callback != nullptr) {_callback(_val); return;}
if (_callbackDev != nullptr) {_callbackDev(this); return;}
if (_callbackCol != nullptr) _callbackCol(_val, getRGB());
}

View File

@@ -0,0 +1,72 @@
#ifndef EspalexaDevice_h
#define EspalexaDevice_h
#include "Arduino.h"
typedef class EspalexaDevice;
typedef void (*BrightnessCallbackFunction) (uint8_t b);
typedef void (*DeviceCallbackFunction) (EspalexaDevice* d);
typedef void (*ColorCallbackFunction) (uint8_t br, uint32_t col);
enum class EspalexaColorMode : uint8_t { none = 0, ct = 1, hs = 2, xy = 3 };
enum class EspalexaDeviceType : uint8_t { onoff = 0, dimmable = 1, whitespectrum = 2, color = 3, extendedcolor = 4 };
enum class EspalexaDeviceProperty : uint8_t { none = 0, on = 1, off = 2, bri = 3, hs = 4, ct = 5, xy = 6 };
class EspalexaDevice {
private:
String _deviceName;
BrightnessCallbackFunction _callback = nullptr;
DeviceCallbackFunction _callbackDev = nullptr;
ColorCallbackFunction _callbackCol = nullptr;
uint8_t _val, _val_last, _sat = 0;
uint16_t _hue = 0, _ct = 0;
float _x = 0.5, _y = 0.5;
uint32_t _rgb = 0;
uint8_t _id = 0;
EspalexaDeviceType _type;
EspalexaDeviceProperty _changed = EspalexaDeviceProperty::none;
EspalexaColorMode _mode = EspalexaColorMode::xy;
public:
EspalexaDevice();
~EspalexaDevice();
EspalexaDevice(String deviceName, BrightnessCallbackFunction bcb, uint8_t initialValue =0);
EspalexaDevice(String deviceName, DeviceCallbackFunction dcb, EspalexaDeviceType t =EspalexaDeviceType::dimmable, uint8_t initialValue =0);
EspalexaDevice(String deviceName, ColorCallbackFunction ccb, uint8_t initialValue =0);
String getName();
uint8_t getId();
EspalexaDeviceProperty getLastChangedProperty();
uint8_t getValue();
uint8_t getPercent();
uint8_t getDegrees();
uint16_t getHue();
uint8_t getSat();
uint16_t getCt();
uint32_t getKelvin();
float getX();
float getY();
uint32_t getRGB();
uint8_t getR();
uint8_t getG();
uint8_t getB();
EspalexaColorMode getColorMode();
EspalexaDeviceType getType();
void setId(uint8_t id);
void setPropertyChanged(EspalexaDeviceProperty p);
void setValue(uint8_t bri);
void setPercent(uint8_t perc);
void setName(String name);
void setColor(uint16_t ct);
void setColor(uint16_t hue, uint8_t sat);
void setColorXY(float x, float y);
void setColor(uint8_t r, uint8_t g, uint8_t b);
void doCallback();
uint8_t getLastValue(); //last value that was not off (1-255)
};
#endif

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Christian Schwinne
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.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,160 @@
// AsyncJson-v6.h
/*
Original file at: https://github.com/baggior/ESPAsyncWebServer/blob/master/src/AsyncJson.h
Only changes are ArduinoJson lib path and removed content-type check
Async Response to use with ArduinoJson and AsyncWebServer
Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon.
--------------------
Async Request to use with ArduinoJson and AsyncWebServer
Written by Arsène von Wyss (avonwyss)
*/
#ifndef ASYNC_JSON_H_
#define ASYNC_JSON_H_
#include "ArduinoJson-v6.h"
#include <Print.h>
#define DYNAMYC_JSON_DOCUMENT_SIZE 8192
constexpr const char* JSON_MIMETYPE = "application/json";
/*
* Json Response
* */
class ChunkPrint : public Print {
private:
uint8_t* _destination;
size_t _to_skip;
size_t _to_write;
size_t _pos;
public:
ChunkPrint(uint8_t* destination, size_t from, size_t len)
: _destination(destination), _to_skip(from), _to_write(len), _pos{0} {}
virtual ~ChunkPrint(){}
size_t write(uint8_t c){
if (_to_skip > 0) {
_to_skip--;
return 1;
} else if (_to_write > 0) {
_to_write--;
_destination[_pos++] = c;
return 1;
}
return 0;
}
size_t write(const uint8_t *buffer, size_t size)
{
return this->Print::write(buffer, size);
}
};
class AsyncJsonResponse: public AsyncAbstractResponse {
private:
DynamicJsonDocument _jsonBuffer;
JsonVariant _root;
bool _isValid;
public:
AsyncJsonResponse(size_t maxJsonBufferSize = DYNAMYC_JSON_DOCUMENT_SIZE, bool isArray=false) : _jsonBuffer(maxJsonBufferSize), _isValid{false} {
_code = 200;
_contentType = JSON_MIMETYPE;
if(isArray)
_root = _jsonBuffer.createNestedArray();
else
_root = _jsonBuffer.createNestedObject();
}
~AsyncJsonResponse() {}
JsonVariant & getRoot() { return _root; }
bool _sourceValid() const { return _isValid; }
size_t setLength() {
_contentLength = measureJson(_root);
if (_contentLength) { _isValid = true; }
return _contentLength;
}
size_t getSize() { return _jsonBuffer.size(); }
size_t _fillBuffer(uint8_t *data, size_t len){
ChunkPrint dest(data, _sentLength, len);
serializeJson(_root, dest);
return len;
}
};
typedef std::function<void(AsyncWebServerRequest *request, JsonObject json)> ArJsonRequestHandlerFunction;
class AsyncCallbackJsonWebHandler: public AsyncWebHandler {
private:
protected:
const String _uri;
WebRequestMethodComposite _method;
ArJsonRequestHandlerFunction _onRequest;
int _contentLength;
const size_t maxJsonBufferSize;
int _maxContentLength;
public:
AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize=DYNAMYC_JSON_DOCUMENT_SIZE)
: _uri(uri), _method(HTTP_POST|HTTP_PUT|HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {}
void setMethod(WebRequestMethodComposite method){ _method = method; }
void setMaxContentLength(int maxContentLength){ _maxContentLength = maxContentLength; }
void onRequest(ArJsonRequestHandlerFunction fn){ _onRequest = fn; }
virtual bool canHandle(AsyncWebServerRequest *request) override final{
if(!_onRequest)
return false;
if(!(_method & request->method()))
return false;
if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/")))
return false;
request->addInterestingHeader("ANY");
return true;
}
virtual void handleRequest(AsyncWebServerRequest *request) override final {
if(_onRequest) {
if (request->_tempObject != NULL) {
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize);
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject));
if(!error) {
JsonObject json = jsonBuffer.as<JsonObject>();
_onRequest(request, json);
return;
}
}
request->send(_contentLength > _maxContentLength ? 413 : 400);
} else {
request->send(500);
}
}
virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final {
}
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final {
if (_onRequest) {
_contentLength = total;
if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) {
request->_tempObject = malloc(total);
}
if (request->_tempObject != NULL) {
memcpy((uint8_t*)(request->_tempObject) + index, data, len);
}
}
}
virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;}
};
#endif

View File

@@ -1,20 +0,0 @@
Copyright (c) 2008-2015 Nicholas O'Leary
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.

View File

@@ -1,601 +0,0 @@
/*
PubSubClient.cpp - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#include "PubSubClient.h"
#include "Arduino.h"
PubSubClient::PubSubClient() {
this->_state = MQTT_DISCONNECTED;
this->_client = NULL;
this->stream = NULL;
setCallback(NULL);
}
PubSubClient::PubSubClient(Client& client) {
this->_state = MQTT_DISCONNECTED;
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(addr, port);
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(addr,port);
setClient(client);
setStream(stream);
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(addr, port);
setCallback(callback);
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(addr,port);
setCallback(callback);
setClient(client);
setStream(stream);
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(ip, port);
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(ip,port);
setClient(client);
setStream(stream);
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(ip, port);
setCallback(callback);
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(ip,port);
setCallback(callback);
setClient(client);
setStream(stream);
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setClient(client);
setStream(stream);
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setCallback(callback);
setClient(client);
this->stream = NULL;
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setCallback(callback);
setClient(client);
setStream(stream);
}
boolean PubSubClient::connect(const char *id) {
return connect(id,NULL,NULL,0,0,0,0);
}
boolean PubSubClient::connect(const char *id, const char *user, const char *pass) {
return connect(id,user,pass,0,0,0,0);
}
boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage);
}
boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
if (!connected()) {
int result = 0;
if (domain != NULL) {
result = _client->connect(this->domain, this->port);
} else {
result = _client->connect(this->ip, this->port);
}
if (result == 1) {
nextMsgId = 1;
// Leave room in the buffer for header and variable length field
uint16_t length = 5;
unsigned int j;
#if MQTT_VERSION == MQTT_VERSION_3_1
uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION};
#define MQTT_HEADER_VERSION_LENGTH 9
#elif MQTT_VERSION == MQTT_VERSION_3_1_1
uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION};
#define MQTT_HEADER_VERSION_LENGTH 7
#endif
for (j = 0;j<MQTT_HEADER_VERSION_LENGTH;j++) {
buffer[length++] = d[j];
}
uint8_t v;
if (willTopic) {
v = 0x06|(willQos<<3)|(willRetain<<5);
} else {
v = 0x02;
}
if(user != NULL) {
v = v|0x80;
if(pass != NULL) {
v = v|(0x80>>1);
}
}
buffer[length++] = v;
buffer[length++] = ((MQTT_KEEPALIVE) >> 8);
buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF);
length = writeString(id,buffer,length);
if (willTopic) {
length = writeString(willTopic,buffer,length);
length = writeString(willMessage,buffer,length);
}
if(user != NULL) {
length = writeString(user,buffer,length);
if(pass != NULL) {
length = writeString(pass,buffer,length);
}
}
write(MQTTCONNECT,buffer,length-5);
lastInActivity = lastOutActivity = millis();
while (!_client->available()) {
unsigned long t = millis();
if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) {
_state = MQTT_CONNECTION_TIMEOUT;
_client->stop();
return false;
}
}
uint8_t llen;
uint16_t len = readPacket(&llen);
if (len == 4) {
if (buffer[3] == 0) {
lastInActivity = millis();
pingOutstanding = false;
_state = MQTT_CONNECTED;
return true;
} else {
_state = buffer[3];
}
}
_client->stop();
} else {
_state = MQTT_CONNECT_FAILED;
}
return false;
}
return true;
}
// reads a byte into result
boolean PubSubClient::readByte(uint8_t * result) {
uint32_t previousMillis = millis();
while(!_client->available()) {
uint32_t currentMillis = millis();
if(currentMillis - previousMillis >= ((int32_t) MQTT_SOCKET_TIMEOUT * 1000)){
return false;
}
}
*result = _client->read();
return true;
}
// reads a byte into result[*index] and increments index
boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){
uint16_t current_index = *index;
uint8_t * write_address = &(result[current_index]);
if(readByte(write_address)){
*index = current_index + 1;
return true;
}
return false;
}
uint16_t PubSubClient::readPacket(uint8_t* lengthLength) {
uint16_t len = 0;
if(!readByte(buffer, &len)) return 0;
bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH;
uint32_t multiplier = 1;
uint16_t length = 0;
uint8_t digit = 0;
uint16_t skip = 0;
uint8_t start = 0;
do {
if (len == 6) {
// Invalid remaining length encoding - kill the connection
_state = MQTT_DISCONNECTED;
_client->stop();
return 0;
}
if(!readByte(&digit)) return 0;
buffer[len++] = digit;
length += (digit & 127) * multiplier;
multiplier *= 128;
} while ((digit & 128) != 0);
*lengthLength = len-1;
if (isPublish) {
// Read in topic length to calculate bytes to skip over for Stream writing
if(!readByte(buffer, &len)) return 0;
if(!readByte(buffer, &len)) return 0;
skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2];
start = 2;
if (buffer[0]&MQTTQOS1) {
// skip message id
skip += 2;
}
}
for (uint16_t i = start;i<length;i++) {
if(!readByte(&digit)) return 0;
if (this->stream) {
if (isPublish && len-*lengthLength-2>skip) {
this->stream->write(digit);
}
}
if (len < MQTT_MAX_PACKET_SIZE) {
buffer[len] = digit;
}
len++;
}
if (!this->stream && len > MQTT_MAX_PACKET_SIZE) {
len = 0; // This will cause the packet to be ignored.
}
return len;
}
boolean PubSubClient::loop() {
if (connected()) {
unsigned long t = millis();
if ((t - lastInActivity > MQTT_KEEPALIVE*1000UL) || (t - lastOutActivity > MQTT_KEEPALIVE*1000UL)) {
if (pingOutstanding) {
this->_state = MQTT_CONNECTION_TIMEOUT;
_client->stop();
return false;
} else {
buffer[0] = MQTTPINGREQ;
buffer[1] = 0;
_client->write(buffer,2);
lastOutActivity = t;
lastInActivity = t;
pingOutstanding = true;
}
}
if (_client->available()) {
uint8_t llen;
uint16_t len = readPacket(&llen);
uint16_t msgId = 0;
uint8_t *payload;
if (len > 0) {
lastInActivity = t;
uint8_t type = buffer[0]&0xF0;
if (type == MQTTPUBLISH) {
if (callback) {
uint16_t tl = (buffer[llen+1]<<8)+buffer[llen+2]; /* topic length in bytes */
memmove(buffer+llen+2,buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */
buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */
char *topic = (char*) buffer+llen+2;
// make sure payload can be interpreted as 'C' string
buffer[(len < MQTT_MAX_PACKET_SIZE) ? len : MQTT_MAX_PACKET_SIZE -1] = 0;
// msgId only present for QOS>0
if ((buffer[0]&0x06) == MQTTQOS1) {
msgId = (buffer[llen+3+tl]<<8)+buffer[llen+3+tl+1];
payload = buffer+llen+3+tl+2;
callback(topic,payload,len-llen-3-tl-2);
buffer[0] = MQTTPUBACK;
buffer[1] = 2;
buffer[2] = (msgId >> 8);
buffer[3] = (msgId & 0xFF);
_client->write(buffer,4);
lastOutActivity = t;
} else {
payload = buffer+llen+3+tl;
callback(topic,payload,len-llen-3-tl);
}
}
} else if (type == MQTTPINGREQ) {
buffer[0] = MQTTPINGRESP;
buffer[1] = 0;
_client->write(buffer,2);
} else if (type == MQTTPINGRESP) {
pingOutstanding = false;
}
} else if (!connected()) {
// readPacket has closed the connection
return false;
}
}
return true;
}
return false;
}
boolean PubSubClient::publish(const char* topic, const char* payload) {
return publish(topic,(const uint8_t*)payload,strlen(payload),false);
}
boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) {
return publish(topic,(const uint8_t*)payload,strlen(payload),retained);
}
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) {
return publish(topic, payload, plength, false);
}
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
if (connected()) {
if (MQTT_MAX_PACKET_SIZE < 5 + 2+strlen(topic) + plength) {
// Too long
return false;
}
// Leave room in the buffer for header and variable length field
uint16_t length = 5;
length = writeString(topic,buffer,length);
uint16_t i;
for (i=0;i<plength;i++) {
buffer[length++] = payload[i];
}
uint8_t header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
return write(header,buffer,length-5);
}
return false;
}
boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
uint8_t llen = 0;
uint8_t digit;
unsigned int rc = 0;
uint16_t tlen;
unsigned int pos = 0;
unsigned int i;
uint8_t header;
unsigned int len;
if (!connected()) {
return false;
}
tlen = strlen(topic);
header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
buffer[pos++] = header;
len = plength + 2 + tlen;
do {
digit = len % 128;
len = len / 128;
if (len > 0) {
digit |= 0x80;
}
buffer[pos++] = digit;
llen++;
} while(len>0);
pos = writeString(topic,buffer,pos);
rc += _client->write(buffer,pos);
for (i=0;i<plength;i++) {
rc += _client->write((char)pgm_read_byte_near(payload + i));
}
lastOutActivity = millis();
return rc == tlen + 4 + plength;
}
boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
uint8_t lenBuf[4];
uint8_t llen = 0;
uint8_t digit;
uint8_t pos = 0;
uint16_t rc;
uint16_t len = length;
do {
digit = len % 128;
len = len / 128;
if (len > 0) {
digit |= 0x80;
}
lenBuf[pos++] = digit;
llen++;
} while(len>0);
buf[4-llen] = header;
for (int i=0;i<llen;i++) {
buf[5-llen+i] = lenBuf[i];
}
#ifdef MQTT_MAX_TRANSFER_SIZE
uint8_t* writeBuf = buf+(4-llen);
uint16_t bytesRemaining = length+1+llen; //Match the length type
uint8_t bytesToWrite;
boolean result = true;
while((bytesRemaining > 0) && result) {
bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining;
rc = _client->write(writeBuf,bytesToWrite);
result = (rc == bytesToWrite);
bytesRemaining -= rc;
writeBuf += rc;
}
return result;
#else
rc = _client->write(buf+(4-llen),length+1+llen);
lastOutActivity = millis();
return (rc == 1+llen+length);
#endif
}
boolean PubSubClient::subscribe(const char* topic) {
return subscribe(topic, 0);
}
boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
if (qos > 1) {
return false;
}
if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
// Too long
return false;
}
if (connected()) {
// Leave room in the buffer for header and variable length field
uint16_t length = 5;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
buffer[length++] = (nextMsgId >> 8);
buffer[length++] = (nextMsgId & 0xFF);
length = writeString((char*)topic, buffer,length);
buffer[length++] = qos;
return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-5);
}
return false;
}
boolean PubSubClient::unsubscribe(const char* topic) {
if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
// Too long
return false;
}
if (connected()) {
uint16_t length = 5;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
buffer[length++] = (nextMsgId >> 8);
buffer[length++] = (nextMsgId & 0xFF);
length = writeString(topic, buffer,length);
return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-5);
}
return false;
}
void PubSubClient::disconnect() {
buffer[0] = MQTTDISCONNECT;
buffer[1] = 0;
_client->write(buffer,2);
_state = MQTT_DISCONNECTED;
_client->stop();
lastInActivity = lastOutActivity = millis();
}
uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) {
const char* idp = string;
uint16_t i = 0;
pos += 2;
while (*idp) {
buf[pos++] = *idp++;
i++;
}
buf[pos-i-2] = (i >> 8);
buf[pos-i-1] = (i & 0xFF);
return pos;
}
boolean PubSubClient::connected() {
boolean rc;
if (_client == NULL ) {
rc = false;
} else {
rc = (int)_client->connected();
if (!rc) {
if (this->_state == MQTT_CONNECTED) {
this->_state = MQTT_CONNECTION_LOST;
_client->flush();
_client->stop();
}
}
}
return rc;
}
PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) {
IPAddress addr(ip[0],ip[1],ip[2],ip[3]);
return setServer(addr,port);
}
PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) {
this->ip = ip;
this->port = port;
this->domain = NULL;
return *this;
}
PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) {
this->domain = domain;
this->port = port;
return *this;
}
PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) {
this->callback = callback;
return *this;
}
PubSubClient& PubSubClient::setClient(Client& client){
this->_client = &client;
return *this;
}
PubSubClient& PubSubClient::setStream(Stream& stream){
this->stream = &stream;
return *this;
}
int PubSubClient::state() {
return this->_state;
}

View File

@@ -1,144 +0,0 @@
/*
PubSubClient.h - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#ifndef PubSubClient_h
#define PubSubClient_h
#include <Arduino.h>
#include "IPAddress.h"
#include "Client.h"
#include "Stream.h"
#define MQTT_VERSION_3_1 3
#define MQTT_VERSION_3_1_1 4
// MQTT_VERSION : Pick the version
//#define MQTT_VERSION MQTT_VERSION_3_1
#ifndef MQTT_VERSION
#define MQTT_VERSION MQTT_VERSION_3_1_1
#endif
// MQTT_MAX_PACKET_SIZE : Maximum packet size
#ifndef MQTT_MAX_PACKET_SIZE
#define MQTT_MAX_PACKET_SIZE 128
#endif
// MQTT_KEEPALIVE : keepAlive interval in Seconds
#ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 60
#endif
// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds
#ifndef MQTT_SOCKET_TIMEOUT
#define MQTT_SOCKET_TIMEOUT 62
#endif
// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client
// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to
// pass the entire MQTT packet in each write call.
//#define MQTT_MAX_TRANSFER_SIZE 80
// Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT -4
#define MQTT_CONNECTION_LOST -3
#define MQTT_CONNECT_FAILED -2
#define MQTT_DISCONNECTED -1
#define MQTT_CONNECTED 0
#define MQTT_CONNECT_BAD_PROTOCOL 1
#define MQTT_CONNECT_BAD_CLIENT_ID 2
#define MQTT_CONNECT_UNAVAILABLE 3
#define MQTT_CONNECT_BAD_CREDENTIALS 4
#define MQTT_CONNECT_UNAUTHORIZED 5
#define MQTTCONNECT 1 << 4 // Client request to connect to Server
#define MQTTCONNACK 2 << 4 // Connect Acknowledgment
#define MQTTPUBLISH 3 << 4 // Publish message
#define MQTTPUBACK 4 << 4 // Publish Acknowledgment
#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1)
#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2)
#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3)
#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request
#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment
#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request
#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment
#define MQTTPINGREQ 12 << 4 // PING Request
#define MQTTPINGRESP 13 << 4 // PING Response
#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting
#define MQTTReserved 15 << 4 // Reserved
#define MQTTQOS0 (0 << 1)
#define MQTTQOS1 (1 << 1)
#define MQTTQOS2 (2 << 1)
#ifdef ESP8266
#include <functional>
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
#else
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
#endif
class PubSubClient {
private:
Client* _client;
uint8_t buffer[MQTT_MAX_PACKET_SIZE];
uint16_t nextMsgId;
unsigned long lastOutActivity;
unsigned long lastInActivity;
bool pingOutstanding;
MQTT_CALLBACK_SIGNATURE;
uint16_t readPacket(uint8_t*);
boolean readByte(uint8_t * result);
boolean readByte(uint8_t * result, uint16_t * index);
boolean write(uint8_t header, uint8_t* buf, uint16_t length);
uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos);
IPAddress ip;
const char* domain;
uint16_t port;
Stream* stream;
int _state;
public:
PubSubClient();
PubSubClient(Client& client);
PubSubClient(IPAddress, uint16_t, Client& client);
PubSubClient(IPAddress, uint16_t, Client& client, Stream&);
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
PubSubClient(uint8_t *, uint16_t, Client& client);
PubSubClient(uint8_t *, uint16_t, Client& client, Stream&);
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
PubSubClient(const char*, uint16_t, Client& client);
PubSubClient(const char*, uint16_t, Client& client, Stream&);
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
PubSubClient& setServer(IPAddress ip, uint16_t port);
PubSubClient& setServer(uint8_t * ip, uint16_t port);
PubSubClient& setServer(const char * domain, uint16_t port);
PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE);
PubSubClient& setClient(Client& client);
PubSubClient& setStream(Stream& stream);
boolean connect(const char* id);
boolean connect(const char* id, const char* user, const char* pass);
boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
void disconnect();
boolean publish(const char* topic, const char* payload);
boolean publish(const char* topic, const char* payload, boolean retained);
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength);
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
boolean subscribe(const char* topic);
boolean subscribe(const char* topic, uint8_t qos);
boolean unsubscribe(const char* topic);
boolean loop();
boolean connected();
int state();
};
#endif

View File

@@ -1 +0,0 @@
#include "TimeLib.h"

View File

@@ -12,7 +12,7 @@
#include "Timezone.h"
//THIS LINE WAS ADDED FOR COMPATIBILY WITH THE WLED DEPENDENCY STRUCTURE. REMOVE IF YOU USE IT OUTSIDE OF WLED!
#include "../time/Time.h"
#include "../time/TimeLib.h"
#ifdef __AVR__
#include <avr/eeprom.h>

View File

@@ -16,7 +16,8 @@
#else
#include <WProgram.h>
#endif
#include <Time.h> //http://www.arduino.cc/playground/Code/Time
#include "../time/TimeLib.h" //http://www.arduino.cc/playground/Code/Time
//convenient constants for dstRules
enum week_t {Last, First, Second, Third, Fourth};

View File

@@ -1,105 +0,0 @@
#include <Arduino.h>
#include <WiFiClient.h>
#include <WiFiServer.h>
#ifdef ARDUINO_ARCH_ESP32
#include "WebServer.h"
#include <Update.h>
#else
#include <ESP8266WebServer.h>
#endif
#include <WiFiUdp.h>
#include "ESP8266HTTPUpdateServer.h"
const char* ESP8266HTTPUpdateServer::_serverIndex =
R"(<html><head><script>function B(){window.history.back()}</script></head><body><h2>WLED Software Update</h2><br>Get the latest binaries on the <a href="https://github.com/Aircoookie/WLED/tree/master/bin">project GitHub page</a>!<br>
<i>Unsure which binary is correct? Go to the <a href="./build">/build subpage</a> for the details of this version.</i><br>
<b>Be sure to upload a valid .bin file for your ESP! Otherwise you'll need USB recovery!</b><br>
<br><br><button onclick='B()'>Back</button><br><br>
<form method='POST' action='' enctype='multipart/form-data'>
<input type='file' name='update'>
<input type='submit' value='Update!'>
</form>
</body></html>)";
const char* ESP8266HTTPUpdateServer::_failedResponse = R"(Update Failed!)";
const char* ESP8266HTTPUpdateServer::_successResponse = R"(<script>setTimeout(function(){top.location.href="/";},20000);</script>Update Successful! Rebooting, please wait for redirect...)";
ESP8266HTTPUpdateServer::ESP8266HTTPUpdateServer(bool serial_debug)
{
_serial_output = serial_debug;
_server = NULL;
_username = NULL;
_password = NULL;
_authenticated = false;
}
#ifdef ARDUINO_ARCH_ESP32
void ESP8266HTTPUpdateServer::setup(WebServer *server, const char * path, const char * username, const char * password)
#else
void ESP8266HTTPUpdateServer::setup(ESP8266WebServer *server, const char * path, const char * username, const char * password)
#endif
{
_server = server;
_username = (char *)username;
_password = (char *)password;
// handler for the /update form page
_server->on(path, HTTP_GET, [&](){
if(_username != NULL && _password != NULL && !_server->authenticate(_username, _password))
return _server->requestAuthentication();
_server->send(200, "text/html", _serverIndex);
});
// handler for the /update form POST (once file upload finishes)
_server->on(path, HTTP_POST, [&](){
if(!_authenticated)
return _server->requestAuthentication();
_server->send(200, "text/html", Update.hasError() ? _failedResponse : _successResponse);
ESP.restart();
},[&](){
// handler for the file upload, get's the sketch bytes, and writes
// them through the Update object
HTTPUpload& upload = _server->upload();
if(upload.status == UPLOAD_FILE_START){
if (_serial_output)
Serial.setDebugOutput(true);
_authenticated = (_username == NULL || _password == NULL || _server->authenticate(_username, _password));
if(!_authenticated){
if (_serial_output)
Serial.printf("Unauthenticated Update\n");
return;
}
#ifndef ARDUINO_ARCH_ESP32
WiFiUDP::stopAll();
#endif
if (_serial_output)
Serial.printf("Update: %s\n", upload.filename.c_str());
#ifdef ARDUINO_ARCH_ESP32
uint32_t maxSketchSpace = 0x100000; //dirty workaround, limit to 1MB
#else
uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
#endif
if(!Update.begin(maxSketchSpace)){//start with max available size
if (_serial_output) Update.printError(Serial);
}
} else if(_authenticated && upload.status == UPLOAD_FILE_WRITE){
if (_serial_output) Serial.printf(".");
if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){
if (_serial_output) Update.printError(Serial);
}
} else if(_authenticated && upload.status == UPLOAD_FILE_END){
if(Update.end(true)){ //true to set the size to the current progress
if (_serial_output) Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
} else {
if (_serial_output) Update.printError(Serial);
}
if (_serial_output) Serial.setDebugOutput(false);
} else if(_authenticated && upload.status == UPLOAD_FILE_ABORTED){
Update.end();
if (_serial_output) Serial.println("Update was aborted");
}
delay(0);
});
}

View File

@@ -1,74 +0,0 @@
#ifndef __HTTP_UPDATE_SERVER_H
#define __HTTP_UPDATE_SERVER_H
#ifdef ARDUINO_ARCH_ESP32
class WebServer;
class ESP8266HTTPUpdateServer
{
private:
bool _serial_output;
WebServer *_server;
static const char *_serverIndex;
static const char *_failedResponse;
static const char *_successResponse;
char * _username;
char * _password;
bool _authenticated;
public:
ESP8266HTTPUpdateServer(bool serial_debug=false);
void setup(WebServer *server)
{
setup(server, NULL, NULL);
}
void setup(WebServer *server, const char * path)
{
setup(server, path, NULL, NULL);
}
void setup(WebServer *server, const char * username, const char * password)
{
setup(server, "/update", username, password);
}
void setup(WebServer *server, const char * path, const char * username, const char * password);
};
#else
class ESP8266WebServer;
class ESP8266HTTPUpdateServer
{
private:
bool _serial_output;
ESP8266WebServer *_server;
static const char *_serverIndex;
static const char *_failedResponse;
static const char *_successResponse;
char * _username;
char * _password;
bool _authenticated;
public:
ESP8266HTTPUpdateServer(bool serial_debug=false);
void setup(ESP8266WebServer *server)
{
setup(server, NULL, NULL);
}
void setup(ESP8266WebServer *server, const char * path)
{
setup(server, path, NULL, NULL);
}
void setup(ESP8266WebServer *server, const char * username, const char * password)
{
setup(server, "/update", username, password);
}
void setup(ESP8266WebServer *server, const char * path, const char * username, const char * password);
};
#endif
#endif

View File

@@ -1,29 +0,0 @@
/*
ESP8266WebServer.h - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#ifndef ESP8266WEBSERVER_H
#define ESP8266WEBSERVER_H
#include "WebServer.h"
#endif //ESP8266WEBSERVER_H

View File

@@ -1,504 +0,0 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
(This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.)
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
{description}
Copyright (C) {year} {fullname}
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random
Hacker.
{signature of Ty Coon}, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

View File

@@ -1,613 +0,0 @@
/*
Parsing.cpp - HTTP request parsing.
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#include <Arduino.h>
#ifdef ARDUINO_ARCH_ESP32 //only use this library if building for ESP32
#include "WiFiServer.h"
#include "WiFiClient.h"
#include "WebServer.h"
//#define DEBUG_ESP_HTTP_SERVER
#ifdef DEBUG_ESP_PORT
#define DEBUG_OUTPUT DEBUG_ESP_PORT
#else
#define DEBUG_OUTPUT Serial
#endif
static char* readBytesWithTimeout(WiFiClient& client, size_t maxLength, size_t& dataLength, int timeout_ms)
{
char *buf = nullptr;
dataLength = 0;
while (dataLength < maxLength) {
int tries = timeout_ms;
size_t newLength;
while (!(newLength = client.available()) && tries--) delay(1);
if (!newLength) {
break;
}
if (!buf) {
buf = (char *) malloc(newLength + 1);
if (!buf) {
return nullptr;
}
}
else {
char* newBuf = (char *) realloc(buf, dataLength + newLength + 1);
if (!newBuf) {
free(buf);
return nullptr;
}
buf = newBuf;
}
client.readBytes(buf + dataLength, newLength);
dataLength += newLength;
buf[dataLength] = '\0';
}
return buf;
}
bool WebServer::_parseRequest(WiFiClient& client) {
// Read the first line of HTTP request
String req = client.readStringUntil('\r');
client.readStringUntil('\n');
//reset header value
for (int i = 0; i < _headerKeysCount; ++i) {
_currentHeaders[i].value =String();
}
// First line of HTTP request looks like "GET /path HTTP/1.1"
// Retrieve the "/path" part by finding the spaces
int addr_start = req.indexOf(' ');
int addr_end = req.indexOf(' ', addr_start + 1);
if (addr_start == -1 || addr_end == -1) {
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("Invalid request: ");
DEBUG_OUTPUT.println(req);
#endif
return false;
}
String methodStr = req.substring(0, addr_start);
String url = req.substring(addr_start + 1, addr_end);
String versionEnd = req.substring(addr_end + 8);
_currentVersion = atoi(versionEnd.c_str());
String searchStr = "";
int hasSearch = url.indexOf('?');
if (hasSearch != -1){
searchStr = url.substring(hasSearch + 1);
url = url.substring(0, hasSearch);
}
_currentUri = url;
_chunked = false;
HTTPMethod method = HTTP_GET;
if (methodStr == "POST") {
method = HTTP_POST;
} else if (methodStr == "DELETE") {
method = HTTP_DELETE;
} else if (methodStr == "OPTIONS") {
method = HTTP_OPTIONS;
} else if (methodStr == "PUT") {
method = HTTP_PUT;
} else if (methodStr == "PATCH") {
method = HTTP_PATCH;
}
_currentMethod = method;
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("method: ");
DEBUG_OUTPUT.print(methodStr);
DEBUG_OUTPUT.print(" url: ");
DEBUG_OUTPUT.print(url);
DEBUG_OUTPUT.print(" search: ");
DEBUG_OUTPUT.println(searchStr);
#endif
//attach handler
RequestHandler* handler;
for (handler = _firstHandler; handler; handler = handler->next()) {
if (handler->canHandle(_currentMethod, _currentUri))
break;
}
_currentHandler = handler;
String formData;
// below is needed only when POST type request
if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){
String boundaryStr;
String headerName;
String headerValue;
bool isForm = false;
bool isEncoded = false;
uint32_t contentLength = 0;
//parse headers
while(1){
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "") break;//no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1){
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 1);
headerValue.trim();
_collectHeader(headerName.c_str(),headerValue.c_str());
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("headerName: ");
DEBUG_OUTPUT.println(headerName);
DEBUG_OUTPUT.print("headerValue: ");
DEBUG_OUTPUT.println(headerValue);
#endif
if (headerName.equalsIgnoreCase("Content-Type")){
if (headerValue.startsWith("text/plain")){
isForm = false;
} else if (headerValue.startsWith("application/x-www-form-urlencoded")){
isForm = false;
isEncoded = true;
} else if (headerValue.startsWith("multipart/")){
boundaryStr = headerValue.substring(headerValue.indexOf('=')+1);
isForm = true;
}
} else if (headerName.equalsIgnoreCase("Content-Length")){
contentLength = headerValue.toInt();
} else if (headerName.equalsIgnoreCase("Host")){
_hostHeader = headerValue;
}
}
if (!isForm){
size_t plainLength;
char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
if (plainLength < contentLength) {
free(plainBuf);
return false;
}
if (contentLength > 0) {
if (searchStr != "") searchStr += '&';
if(isEncoded){
//url encoded form
String decoded = urlDecode(plainBuf);
size_t decodedLen = decoded.length();
memcpy(plainBuf, decoded.c_str(), decodedLen);
plainBuf[decodedLen] = 0;
searchStr += plainBuf;
}
_parseArguments(searchStr);
if(!isEncoded){
//plain post json or other data
RequestArgument& arg = _currentArgs[_currentArgCount++];
arg.key = "plain";
arg.value = String(plainBuf);
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("Plain: ");
DEBUG_OUTPUT.println(plainBuf);
#endif
free(plainBuf);
} else {
// No content - but we can still have arguments in the URL.
_parseArguments(searchStr);
}
}
if (isForm){
_parseArguments(searchStr);
if (!_parseForm(client, boundaryStr, contentLength)) {
return false;
}
}
} else {
String headerName;
String headerValue;
//parse headers
while(1){
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "") break;//no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1){
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 2);
_collectHeader(headerName.c_str(),headerValue.c_str());
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("headerName: ");
DEBUG_OUTPUT.println(headerName);
DEBUG_OUTPUT.print("headerValue: ");
DEBUG_OUTPUT.println(headerValue);
#endif
if (headerName.equalsIgnoreCase("Host")){
_hostHeader = headerValue;
}
}
_parseArguments(searchStr);
}
client.flush();
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("Request: ");
DEBUG_OUTPUT.println(url);
DEBUG_OUTPUT.print(" Arguments: ");
DEBUG_OUTPUT.println(searchStr);
#endif
return true;
}
bool WebServer::_collectHeader(const char* headerName, const char* headerValue) {
for (int i = 0; i < _headerKeysCount; i++) {
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
_currentHeaders[i].value=headerValue;
return true;
}
}
return false;
}
void WebServer::_parseArguments(String data) {
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("args: ");
DEBUG_OUTPUT.println(data);
#endif
if (_currentArgs)
delete[] _currentArgs;
_currentArgs = 0;
if (data.length() == 0) {
_currentArgCount = 0;
_currentArgs = new RequestArgument[1];
return;
}
_currentArgCount = 1;
for (int i = 0; i < (int)data.length(); ) {
i = data.indexOf('&', i);
if (i == -1)
break;
++i;
++_currentArgCount;
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("args count: ");
DEBUG_OUTPUT.println(_currentArgCount);
#endif
_currentArgs = new RequestArgument[_currentArgCount+1];
int pos = 0;
int iarg;
for (iarg = 0; iarg < _currentArgCount;) {
int equal_sign_index = data.indexOf('=', pos);
int next_arg_index = data.indexOf('&', pos);
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("pos ");
DEBUG_OUTPUT.print(pos);
DEBUG_OUTPUT.print("=@ ");
DEBUG_OUTPUT.print(equal_sign_index);
DEBUG_OUTPUT.print(" &@ ");
DEBUG_OUTPUT.println(next_arg_index);
#endif
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("arg missing value: ");
DEBUG_OUTPUT.println(iarg);
#endif
if (next_arg_index == -1)
break;
pos = next_arg_index + 1;
continue;
}
RequestArgument& arg = _currentArgs[iarg];
arg.key = data.substring(pos, equal_sign_index);
arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("arg ");
DEBUG_OUTPUT.print(iarg);
DEBUG_OUTPUT.print(" key: ");
DEBUG_OUTPUT.print(arg.key);
DEBUG_OUTPUT.print(" value: ");
DEBUG_OUTPUT.println(arg.value);
#endif
++iarg;
if (next_arg_index == -1)
break;
pos = next_arg_index + 1;
}
_currentArgCount = iarg;
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("args count: ");
DEBUG_OUTPUT.println(_currentArgCount);
#endif
}
void WebServer::_uploadWriteByte(uint8_t b){
if (_currentUpload.currentSize == HTTP_UPLOAD_BUFLEN){
if(_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, _currentUpload);
_currentUpload.totalSize += _currentUpload.currentSize;
_currentUpload.currentSize = 0;
}
_currentUpload.buf[_currentUpload.currentSize++] = b;
}
uint8_t WebServer::_uploadReadByte(WiFiClient& client){
int res = client.read();
if(res == -1){
while(!client.available() && client.connected())
yield();
res = client.read();
}
return (uint8_t)res;
}
bool WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){
(void) len;
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("Parse Form: Boundary: ");
DEBUG_OUTPUT.print(boundary);
DEBUG_OUTPUT.print(" Length: ");
DEBUG_OUTPUT.println(len);
#endif
String line;
int retry = 0;
do {
line = client.readStringUntil('\r');
++retry;
} while (line.length() == 0 && retry < 3);
client.readStringUntil('\n');
//start reading the form
if (line == ("--"+boundary)){
RequestArgument* postArgs = new RequestArgument[32];
int postArgsLen = 0;
while(1){
String argName;
String argValue;
String argType;
String argFilename;
bool argIsFile = false;
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase("Content-Disposition")){
int nameStart = line.indexOf('=');
if (nameStart != -1){
argName = line.substring(nameStart+2);
nameStart = argName.indexOf('=');
if (nameStart == -1){
argName = argName.substring(0, argName.length() - 1);
} else {
argFilename = argName.substring(nameStart+2, argName.length() - 1);
argName = argName.substring(0, argName.indexOf('"'));
argIsFile = true;
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("PostArg FileName: ");
DEBUG_OUTPUT.println(argFilename);
#endif
//use GET to set the filename if uploading using blob
if (argFilename == "blob" && hasArg("filename")) argFilename = arg("filename");
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("PostArg Name: ");
DEBUG_OUTPUT.println(argName);
#endif
argType = "text/plain";
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase("Content-Type")){
argType = line.substring(line.indexOf(':')+2);
//skip next line
client.readStringUntil('\r');
client.readStringUntil('\n');
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("PostArg Type: ");
DEBUG_OUTPUT.println(argType);
#endif
if (!argIsFile){
while(1){
line = client.readStringUntil('\r');
client.readStringUntil('\n');
if (line.startsWith("--"+boundary)) break;
if (argValue.length() > 0) argValue += "\n";
argValue += line;
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("PostArg Value: ");
DEBUG_OUTPUT.println(argValue);
DEBUG_OUTPUT.println();
#endif
RequestArgument& arg = postArgs[postArgsLen++];
arg.key = argName;
arg.value = argValue;
if (line == ("--"+boundary+"--")){
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("Done Parsing POST");
#endif
break;
}
} else {
_currentUpload.status = UPLOAD_FILE_START;
_currentUpload.name = argName;
_currentUpload.filename = argFilename;
_currentUpload.type = argType;
_currentUpload.totalSize = 0;
_currentUpload.currentSize = 0;
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("Start File: ");
DEBUG_OUTPUT.print(_currentUpload.filename);
DEBUG_OUTPUT.print(" Type: ");
DEBUG_OUTPUT.println(_currentUpload.type);
#endif
if(_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, _currentUpload);
_currentUpload.status = UPLOAD_FILE_WRITE;
uint8_t argByte = _uploadReadByte(client);
readfile:
while(argByte != 0x0D){
if (!client.connected()) return _parseFormUploadAborted();
_uploadWriteByte(argByte);
argByte = _uploadReadByte(client);
}
argByte = _uploadReadByte(client);
if (!client.connected()) return _parseFormUploadAborted();
if (argByte == 0x0A){
argByte = _uploadReadByte(client);
if (!client.connected()) return _parseFormUploadAborted();
if ((char)argByte != '-'){
//continue reading the file
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
goto readfile;
} else {
argByte = _uploadReadByte(client);
if (!client.connected()) return _parseFormUploadAborted();
if ((char)argByte != '-'){
//continue reading the file
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
goto readfile;
}
}
uint8_t endBuf[boundary.length()];
client.readBytes(endBuf, boundary.length());
if (strstr((const char*)endBuf, boundary.c_str()) != NULL){
if(_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, _currentUpload);
_currentUpload.totalSize += _currentUpload.currentSize;
_currentUpload.status = UPLOAD_FILE_END;
if(_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, _currentUpload);
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("End File: ");
DEBUG_OUTPUT.print(_currentUpload.filename);
DEBUG_OUTPUT.print(" Type: ");
DEBUG_OUTPUT.print(_currentUpload.type);
DEBUG_OUTPUT.print(" Size: ");
DEBUG_OUTPUT.println(_currentUpload.totalSize);
#endif
line = client.readStringUntil(0x0D);
client.readStringUntil(0x0A);
if (line == "--"){
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("Done Parsing POST");
#endif
break;
}
continue;
} else {
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
_uploadWriteByte((uint8_t)('-'));
uint32_t i = 0;
while(i < boundary.length()){
_uploadWriteByte(endBuf[i++]);
}
argByte = _uploadReadByte(client);
goto readfile;
}
} else {
_uploadWriteByte(0x0D);
goto readfile;
}
break;
}
}
}
}
int iarg;
int totalArgs = ((32 - postArgsLen) < _currentArgCount)?(32 - postArgsLen):_currentArgCount;
for (iarg = 0; iarg < totalArgs; iarg++){
RequestArgument& arg = postArgs[postArgsLen++];
arg.key = _currentArgs[iarg].key;
arg.value = _currentArgs[iarg].value;
}
if (_currentArgs) delete[] _currentArgs;
_currentArgs = new RequestArgument[postArgsLen];
for (iarg = 0; iarg < postArgsLen; iarg++){
RequestArgument& arg = _currentArgs[iarg];
arg.key = postArgs[iarg].key;
arg.value = postArgs[iarg].value;
}
_currentArgCount = iarg;
if (postArgs) delete[] postArgs;
return true;
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.print("Error: line: ");
DEBUG_OUTPUT.println(line);
#endif
return false;
}
String WebServer::urlDecode(const String& text)
{
String decoded = "";
char temp[] = "0x00";
unsigned int len = text.length();
unsigned int i = 0;
while (i < len)
{
char decodedChar;
char encodedChar = text.charAt(i++);
if ((encodedChar == '%') && (i + 1 < len))
{
temp[2] = text.charAt(i++);
temp[3] = text.charAt(i++);
decodedChar = strtol(temp, NULL, 16);
}
else {
if (encodedChar == '+')
{
decodedChar = ' ';
}
else {
decodedChar = encodedChar; // normal ascii char
}
}
decoded += decodedChar;
}
return decoded;
}
bool WebServer::_parseFormUploadAborted(){
_currentUpload.status = UPLOAD_FILE_ABORTED;
if(_currentHandler && _currentHandler->canUpload(_currentUri))
_currentHandler->upload(*this, _currentUri, _currentUpload);
return false;
}
#endif

View File

@@ -1,11 +0,0 @@
Notice by Aircoookie: Port of the ESP8266HTTPUpdateServer for ESP32 is also included in this directory.
# WebServer
ESP8266/ESP32 WebServer library
This is an experimental port of the ESP8266WebServer library that should work
on ESP8266 and ESP32. This is NOT an official repo supported by Espressif. Do
not depend on this code for anything important or expect it to be updated. Once
the official repo is created, this repo will be deleted.
Added Travis CI

View File

@@ -1,529 +0,0 @@
/*
WebServer.cpp - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#include <Arduino.h>
#ifdef ARDUINO_ARCH_ESP32 //only use this library if building for ESP32
#include <libb64/cencode.h>
#include "WiFiServer.h"
#include "WiFiClient.h"
#include "WebServer.h"
#include "FS.h"
#include "detail/RequestHandlersImpl.h"
//#define DEBUG_ESP_HTTP_SERVER
#ifdef DEBUG_ESP_PORT
#define DEBUG_OUTPUT DEBUG_ESP_PORT
#else
#define DEBUG_OUTPUT Serial
#endif
const char * AUTHORIZATION_HEADER = "Authorization";
WebServer::WebServer(IPAddress addr, int port)
: _server(addr, port)
, _currentMethod(HTTP_ANY)
, _currentVersion(0)
, _currentStatus(HC_NONE)
, _statusChange(0)
, _currentHandler(0)
, _firstHandler(0)
, _lastHandler(0)
, _currentArgCount(0)
, _currentArgs(0)
, _headerKeysCount(0)
, _currentHeaders(0)
, _contentLength(0)
, _chunked(false)
{
}
WebServer::WebServer(int port)
: _server(port)
, _currentMethod(HTTP_ANY)
, _currentVersion(0)
, _currentStatus(HC_NONE)
, _statusChange(0)
, _currentHandler(0)
, _firstHandler(0)
, _lastHandler(0)
, _currentArgCount(0)
, _currentArgs(0)
, _headerKeysCount(0)
, _currentHeaders(0)
, _contentLength(0)
, _chunked(false)
{
}
WebServer::~WebServer() {
if (_currentHeaders)
delete[]_currentHeaders;
_headerKeysCount = 0;
RequestHandler* handler = _firstHandler;
while (handler) {
RequestHandler* next = handler->next();
delete handler;
handler = next;
}
close();
}
void WebServer::begin() {
_currentStatus = HC_NONE;
_server.begin();
//if(!_headerKeysCount)
//collectHeaders(0, 0);
}
bool WebServer::authenticate(const char * username, const char * password){
if(hasHeader(AUTHORIZATION_HEADER)){
String authReq = header(AUTHORIZATION_HEADER);
if(authReq.startsWith("Basic")){
authReq = authReq.substring(6);
authReq.trim();
char toencodeLen = strlen(username)+strlen(password)+1;
char *toencode = new char[toencodeLen + 1];
if(toencode == NULL){
authReq = String();
return false;
}
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
if(encoded == NULL){
authReq = String();
delete[] toencode;
return false;
}
sprintf(toencode, "%s:%s", username, password);
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)){
authReq = String();
delete[] toencode;
delete[] encoded;
return true;
}
delete[] toencode;
delete[] encoded;
}
authReq = String();
}
return false;
}
void WebServer::requestAuthentication(){
sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
send(401);
}
void WebServer::on(const String &uri, WebServer::THandlerFunction handler) {
on(uri, HTTP_ANY, handler);
}
void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn) {
on(uri, method, fn, _fileUploadHandler);
}
void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) {
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
}
void WebServer::addHandler(RequestHandler* handler) {
_addRequestHandler(handler);
}
void WebServer::_addRequestHandler(RequestHandler* handler) {
if (!_lastHandler) {
_firstHandler = handler;
_lastHandler = handler;
}
else {
_lastHandler->next(handler);
_lastHandler = handler;
}
}
void WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
}
void WebServer::handleClient() {
if (_currentStatus == HC_NONE) {
WiFiClient client = _server.available();
if (!client) {
return;
}
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("New client");
#endif
_currentClient = client;
_currentStatus = HC_WAIT_READ;
_statusChange = millis();
}
if (!_currentClient.connected()) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
return;
}
// Wait for data from client to become available
if (_currentStatus == HC_WAIT_READ) {
if (!_currentClient.available()) {
if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
}
yield();
return;
}
if (!_parseRequest(_currentClient)) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
return;
}
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT);
_contentLength = CONTENT_LENGTH_NOT_SET;
_handleRequest();
if (!_currentClient.connected()) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
return;
} else {
_currentStatus = HC_WAIT_CLOSE;
_statusChange = millis();
return;
}
}
if (_currentStatus == HC_WAIT_CLOSE) {
if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) {
_currentClient = WiFiClient();
_currentStatus = HC_NONE;
} else {
yield();
return;
}
}
}
void WebServer::close() {
#ifdef ESP8266
_server.stop();
#else
// TODO add ESP32 WiFiServer::stop()
_server.end();
#endif
}
void WebServer::stop() {
close();
}
void WebServer::sendHeader(const String& name, const String& value, bool first) {
String headerLine = name;
headerLine += ": ";
headerLine += value;
headerLine += "\r\n";
if (first) {
_responseHeaders = headerLine + _responseHeaders;
}
else {
_responseHeaders += headerLine;
}
}
void WebServer::setContentLength(size_t contentLength) {
_contentLength = contentLength;
}
void WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
response = "HTTP/1."+String(_currentVersion)+" ";
response += String(code);
response += " ";
response += _responseCodeToString(code);
response += "\r\n";
if (!content_type)
content_type = "text/html";
sendHeader("Content-Type", content_type, true);
if (_contentLength == CONTENT_LENGTH_NOT_SET) {
sendHeader("Content-Length", String(contentLength));
} else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
sendHeader("Content-Length", String(_contentLength));
} else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client
//let's do chunked
_chunked = true;
sendHeader("Accept-Ranges","none");
sendHeader("Transfer-Encoding","chunked");
}
sendHeader("Connection", "close");
response += _responseHeaders;
response += "\r\n";
_responseHeaders = String();
}
void WebServer::send(int code, const char* content_type, const String& content) {
String header;
// Can we asume the following?
//if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
// _contentLength = CONTENT_LENGTH_UNKNOWN;
_prepareHeader(header, code, content_type, content.length());
_currentClient.write(header.c_str(), header.length());
if(content.length())
sendContent(content);
}
void WebServer::send_P(int code, PGM_P content_type, PGM_P content) {
size_t contentLength = 0;
if (content != NULL) {
contentLength = strlen_P(content);
}
String header;
char type[64];
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
_prepareHeader(header, code, (const char* )type, contentLength);
_currentClient.write(header.c_str(), header.length());
sendContent_P(content);
}
void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
String header;
char type[64];
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
_prepareHeader(header, code, (const char* )type, contentLength);
sendContent(header);
sendContent_P(content, contentLength);
}
void WebServer::send(int code, char* content_type, const String& content) {
send(code, (const char*)content_type, content);
}
void WebServer::send(int code, const String& content_type, const String& content) {
send(code, (const char*)content_type.c_str(), content);
}
void WebServer::sendContent(const String& content) {
const char * footer = "\r\n";
size_t len = content.length();
if(_chunked) {
char * chunkSize = (char *)malloc(11);
if(chunkSize){
sprintf(chunkSize, "%x%s", len, footer);
_currentClient.write(chunkSize, strlen(chunkSize));
free(chunkSize);
}
}
_currentClient.write(content.c_str(), len);
if(_chunked){
_currentClient.write(footer, 2);
}
}
void WebServer::sendContent_P(PGM_P content) {
sendContent_P(content, strlen_P(content));
}
void WebServer::sendContent_P(PGM_P content, size_t size) {
const char * footer = "\r\n";
if(_chunked) {
char * chunkSize = (char *)malloc(11);
if(chunkSize){
sprintf(chunkSize, "%x%s", size, footer);
_currentClient.write(chunkSize, strlen(chunkSize));
free(chunkSize);
}
}
_currentClient.write_P(content, size);
if(_chunked){
_currentClient.write(footer, 2);
}
}
String WebServer::arg(String name) {
for (int i = 0; i < _currentArgCount; ++i) {
if ( _currentArgs[i].key == name )
return _currentArgs[i].value;
}
return String();
}
String WebServer::arg(int i) {
if (i < _currentArgCount)
return _currentArgs[i].value;
return String();
}
String WebServer::argName(int i) {
if (i < _currentArgCount)
return _currentArgs[i].key;
return String();
}
int WebServer::args() {
return _currentArgCount;
}
bool WebServer::hasArg(String name) {
for (int i = 0; i < _currentArgCount; ++i) {
if (_currentArgs[i].key == name)
return true;
}
return false;
}
String WebServer::header(String name) {
for (int i = 0; i < _headerKeysCount; ++i) {
if (_currentHeaders[i].key.equalsIgnoreCase(name))
return _currentHeaders[i].value;
}
return String();
}
//Modified by Aircoookie to work for WLED
void WebServer::collectHeaders(String headerKey) {
_headerKeysCount = 2;
if (_currentHeaders) delete[]_currentHeaders;
_currentHeaders = new RequestArgument[2];
_currentHeaders[0].key = AUTHORIZATION_HEADER;
_currentHeaders[1].key = headerKey;
}
String WebServer::header(int i) {
if (i < _headerKeysCount)
return _currentHeaders[i].value;
return String();
}
String WebServer::headerName(int i) {
if (i < _headerKeysCount)
return _currentHeaders[i].key;
return String();
}
int WebServer::headers() {
return _headerKeysCount;
}
bool WebServer::hasHeader(String name) {
for (int i = 0; i < _headerKeysCount; ++i) {
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0))
return true;
}
return false;
}
String WebServer::hostHeader() {
return _hostHeader;
}
void WebServer::onFileUpload(THandlerFunction fn) {
_fileUploadHandler = fn;
}
void WebServer::onNotFound(THandlerFunction fn) {
_notFoundHandler = fn;
}
void WebServer::_handleRequest() {
bool handled = false;
if (!_currentHandler){
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.println("request handler not found");
#endif
}
else {
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
#ifdef DEBUG_ESP_HTTP_SERVER
if (!handled) {
DEBUG_OUTPUT.println("request handler failed to handle request");
}
#endif
}
if (!handled) {
if(_notFoundHandler) {
_notFoundHandler();
}
else {
send(404, "text/plain", String("Not found: ") + _currentUri);
}
}
_currentUri = String();
}
String WebServer::_responseCodeToString(int code) {
switch (code) {
case 100: return F("Continue");
case 101: return F("Switching Protocols");
case 200: return F("OK");
case 201: return F("Created");
case 202: return F("Accepted");
case 203: return F("Non-Authoritative Information");
case 204: return F("No Content");
case 205: return F("Reset Content");
case 206: return F("Partial Content");
case 300: return F("Multiple Choices");
case 301: return F("Moved Permanently");
case 302: return F("Found");
case 303: return F("See Other");
case 304: return F("Not Modified");
case 305: return F("Use Proxy");
case 307: return F("Temporary Redirect");
case 400: return F("Bad Request");
case 401: return F("Unauthorized");
case 402: return F("Payment Required");
case 403: return F("Forbidden");
case 404: return F("Not Found");
case 405: return F("Method Not Allowed");
case 406: return F("Not Acceptable");
case 407: return F("Proxy Authentication Required");
case 408: return F("Request Time-out");
case 409: return F("Conflict");
case 410: return F("Gone");
case 411: return F("Length Required");
case 412: return F("Precondition Failed");
case 413: return F("Request Entity Too Large");
case 414: return F("Request-URI Too Large");
case 415: return F("Unsupported Media Type");
case 416: return F("Requested range not satisfiable");
case 417: return F("Expectation Failed");
case 500: return F("Internal Server Error");
case 501: return F("Not Implemented");
case 502: return F("Bad Gateway");
case 503: return F("Service Unavailable");
case 504: return F("Gateway Time-out");
case 505: return F("HTTP Version not supported");
default: return "";
}
}
#endif

View File

@@ -1,236 +0,0 @@
/*
WebServer.h - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/
#ifndef WEBSERVER_H
#define WEBSERVER_H
#include <functional>
#ifdef ESP8266
#define WebServer ESP8266WebServer
#include <ESP8266WiFi.h>
#else
#include <WiFi.h>
#define write_P write
#endif
enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS };
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
UPLOAD_FILE_ABORTED };
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
#define HTTP_DOWNLOAD_UNIT_SIZE 1460
#ifndef HTTP_UPLOAD_BUFLEN
#define HTTP_UPLOAD_BUFLEN 2048
#endif
#define HTTP_MAX_DATA_WAIT 1000 //ms to wait for the client to send the request
#define HTTP_MAX_POST_WAIT 1000 //ms to wait for POST data to arrive
#define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed
#define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection
#define CONTENT_LENGTH_UNKNOWN ((size_t) -1)
#define CONTENT_LENGTH_NOT_SET ((size_t) -2)
class WebServer;
typedef struct {
HTTPUploadStatus status;
String filename;
String name;
String type;
size_t totalSize; // file size
size_t currentSize; // size of data currently in buf
uint8_t buf[HTTP_UPLOAD_BUFLEN];
} HTTPUpload;
#include "detail/RequestHandler.h"
namespace fs {
class FS;
}
class WebServer
{
public:
WebServer(IPAddress addr, int port = 80);
WebServer(int port = 80);
~WebServer();
void begin();
void handleClient();
void close();
void stop();
bool authenticate(const char * username, const char * password);
void requestAuthentication();
typedef std::function<void(void)> THandlerFunction;
void on(const String &uri, THandlerFunction handler);
void on(const String &uri, HTTPMethod method, THandlerFunction fn);
void on(const String &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
void addHandler(RequestHandler* handler);
void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL );
void onNotFound(THandlerFunction fn); //called when handler is not assigned
void onFileUpload(THandlerFunction fn); //handle file uploads
String uri() { return _currentUri; }
HTTPMethod method() { return _currentMethod; }
WiFiClient client() { return _currentClient; }
HTTPUpload& upload() { return _currentUpload; }
String arg(String name); // get request argument value by name
String arg(int i); // get request argument value by number
String argName(int i); // get request argument name by number
int args(); // get arguments count
bool hasArg(String name); // check if argument exists
void collectHeaders(String headerKey); // set the request headers to collect
String header(String name); // get request header value by name
String header(int i); // get request header value by number
String headerName(int i); // get request header name by number
int headers(); // get header count
bool hasHeader(String name); // check if header exists
String hostHeader(); // get request host header if available or empty String if not
// send response to the client
// code - HTTP response code, can be 200 or 404
// content_type - HTTP content type, like "text/plain" or "image/png"
// content - actual content body
void send(int code, const char* content_type = NULL, const String& content = String(""));
void send(int code, char* content_type, const String& content);
void send(int code, const String& content_type, const String& content);
void send_P(int code, PGM_P content_type, PGM_P content);
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
void setContentLength(size_t contentLength);
void sendHeader(const String& name, const String& value, bool first = false);
void sendContent(const String& content);
void sendContent_P(PGM_P content);
void sendContent_P(PGM_P content, size_t size);
static String urlDecode(const String& text);
#ifdef ESP8266
template<typename T> size_t streamFile(T &file, const String& contentType){
setContentLength(file.size());
if (String(file.name()).endsWith(".gz") &&
contentType != "application/x-gzip" &&
contentType != "application/octet-stream"){
sendHeader("Content-Encoding", "gzip");
}
send(200, contentType, "");
return _currentClient.write(file);
}
#else
template<typename T> size_t streamFile(T &file, const String& contentType){
#define STREAMFILE_BUFSIZE 2*1460
setContentLength(file.size());
if (String(file.name()).endsWith(".gz") &&
contentType != "application/x-gzip" &&
contentType != "application/octet-stream") {
sendHeader("Content-Encoding", "gzip");
}
send(200, contentType, "");
uint8_t *buf = (uint8_t *)malloc(STREAMFILE_BUFSIZE);
if (buf == NULL) {
//DBG_OUTPUT_PORT.printf("streamFile malloc failed");
return 0;
}
size_t totalBytesOut = 0;
while (client().connected() && (file.available() > 0)) {
int bytesOut;
int bytesIn = file.read(buf, STREAMFILE_BUFSIZE);
if (bytesIn <= 0) break;
while (1) {
bytesOut = 0;
if (!client().connected()) break;
bytesOut = client().write(buf, bytesIn);
if (bytesIn == bytesOut) break;
//DBG_OUTPUT_PORT.printf("bytesIn %d != bytesOut %d\r\n",
//bytesIn, bytesOut);
delay(1);
}
totalBytesOut += bytesOut;
yield();
}
if (totalBytesOut != file.size()) {
//DBG_OUTPUT_PORT.printf("file size %d bytes out %d\r\n",
// file.size(), totalBytesOut);
}
free(buf);
return totalBytesOut;
}
#endif
protected:
void _addRequestHandler(RequestHandler* handler);
void _handleRequest();
bool _parseRequest(WiFiClient& client);
void _parseArguments(String data);
static String _responseCodeToString(int code);
bool _parseForm(WiFiClient& client, String boundary, uint32_t len);
bool _parseFormUploadAborted();
void _uploadWriteByte(uint8_t b);
uint8_t _uploadReadByte(WiFiClient& client);
void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength);
bool _collectHeader(const char* headerName, const char* headerValue);
struct RequestArgument {
String key;
String value;
};
WiFiServer _server;
WiFiClient _currentClient;
HTTPMethod _currentMethod;
String _currentUri;
uint8_t _currentVersion;
HTTPClientStatus _currentStatus;
unsigned long _statusChange;
RequestHandler* _currentHandler;
RequestHandler* _firstHandler;
RequestHandler* _lastHandler;
THandlerFunction _notFoundHandler;
THandlerFunction _fileUploadHandler;
int _currentArgCount;
RequestArgument* _currentArgs;
HTTPUpload _currentUpload;
int _headerKeysCount;
RequestArgument* _currentHeaders;
size_t _contentLength;
String _responseHeaders;
String _hostHeader;
bool _chunked;
};
#endif //WEBSERVER_H

View File

@@ -1,19 +0,0 @@
#ifndef REQUESTHANDLER_H
#define REQUESTHANDLER_H
class RequestHandler {
public:
virtual ~RequestHandler() { }
virtual bool canHandle(HTTPMethod method, String uri) { (void) method; (void) uri; return false; }
virtual bool canUpload(String uri) { (void) uri; return false; }
virtual bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) { (void) server; (void) requestMethod; (void) requestUri; return false; }
virtual void upload(WebServer& server, String requestUri, HTTPUpload& upload) { (void) server; (void) requestUri; (void) upload; }
RequestHandler* next() { return _next; }
void next(RequestHandler* r) { _next = r; }
private:
RequestHandler* _next = nullptr;
};
#endif //REQUESTHANDLER_H

View File

@@ -1,191 +0,0 @@
#ifndef REQUESTHANDLERSIMPL_H
#define REQUESTHANDLERSIMPL_H
#include "RequestHandler.h"
#ifdef ESP8266
// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules
static const struct {const char endsWith[16]; const char mimeType[32];} mimeTable[] ICACHE_RODATA_ATTR = {
#else
static const struct {const char endsWith[16]; const char mimeType[32];} mimeTable[] = {
#endif
{ ".html", "text/html" },
{ ".htm", "text/html" },
{ ".css", "text/css" },
{ ".txt", "text/plain" },
{ ".js", "application/javascript" },
{ ".json", "application/json" },
{ ".png", "image/png" },
{ ".gif", "image/gif" },
{ ".jpg", "image/jpeg" },
{ ".ico", "image/x-icon" },
{ ".svg", "image/svg+xml" },
{ ".ttf", "application/x-font-ttf" },
{ ".otf", "application/x-font-opentype" },
{ ".woff", "application/font-woff" },
{ ".woff2", "application/font-woff2" },
{ ".eot", "application/vnd.ms-fontobject" },
{ ".sfnt", "application/font-sfnt" },
{ ".xml", "text/xml" },
{ ".pdf", "application/pdf" },
{ ".zip", "application/zip" },
{ ".gz", "application/x-gzip" },
{ ".appcache", "text/cache-manifest" },
{ "", "application/octet-stream" } };
class FunctionRequestHandler : public RequestHandler {
public:
FunctionRequestHandler(WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const String &uri, HTTPMethod method)
: _fn(fn)
, _ufn(ufn)
, _uri(uri)
, _method(method)
{
}
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (_method != HTTP_ANY && _method != requestMethod)
return false;
if (requestUri != _uri)
return false;
return true;
}
bool canUpload(String requestUri) override {
if (!_ufn || !canHandle(HTTP_POST, requestUri))
return false;
return true;
}
bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) override {
(void) server;
if (!canHandle(requestMethod, requestUri))
return false;
_fn();
return true;
}
void upload(WebServer& server, String requestUri, HTTPUpload& upload) override {
(void) server;
(void) upload;
if (canUpload(requestUri))
_ufn();
}
protected:
WebServer::THandlerFunction _fn;
WebServer::THandlerFunction _ufn;
String _uri;
HTTPMethod _method;
};
class StaticRequestHandler : public RequestHandler {
public:
StaticRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
: _fs(fs)
, _uri(uri)
, _path(path)
, _cache_header(cache_header)
{
_isFile = fs.exists(path);
#ifdef ESP8266
DEBUGV("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header);
#else
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.printf("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header);
#endif
#endif
_baseUriLength = _uri.length();
}
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (requestMethod != HTTP_GET)
return false;
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
return false;
return true;
}
bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) override {
if (!canHandle(requestMethod, requestUri))
return false;
#ifdef ESP8266
DEBUGV("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
#else
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.printf("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
#endif
#endif
String path(_path);
if (!_isFile) {
// Base URI doesn't point to a file.
// If a directory is requested, look for index file.
if (requestUri.endsWith("/")) requestUri += "index.htm";
// Append whatever follows this URI in request to get the file path.
path += requestUri.substring(_baseUriLength);
}
#ifdef ESP8266
DEBUGV("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
#else
#ifdef DEBUG_ESP_HTTP_SERVER
DEBUG_OUTPUT.printf("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
#endif
#endif
String contentType = getContentType(path);
// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for
// if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
if (!path.endsWith(".gz") && !_fs.exists(path)) {
String pathWithGz = path + ".gz";
if(_fs.exists(pathWithGz))
path += ".gz";
}
File f = _fs.open(path, "r");
if (!f)
return false;
if (_cache_header.length() != 0)
server.sendHeader("Cache-Control", _cache_header);
server.streamFile(f, contentType);
return true;
}
static String getContentType(const String& path) {
char buff[sizeof(mimeTable[0].mimeType)];
// Check all entries but last one for match, return if found
for (size_t i=0; i < sizeof(mimeTable)/sizeof(mimeTable[0])-1; i++) {
strcpy_P(buff, mimeTable[i].endsWith);
if (path.endsWith(buff)) {
strcpy_P(buff, mimeTable[i].mimeType);
return String(buff);
}
}
// Fall-through and just return default type
strcpy_P(buff, mimeTable[sizeof(mimeTable)/sizeof(mimeTable[0])-1].mimeType);
return String(buff);
}
protected:
FS _fs;
String _uri;
String _path;
String _cache_header;
bool _isFile;
size_t _baseUriLength;
};
#endif //REQUESTHANDLERSIMPL_H

View File

@@ -1,4 +1,4 @@
https://github.com/kitesurfer1404/WS2812FX/
The WS2812FX implementation was heavily altered and differs from its master branch.
Due to regural changes to the library code it is kept in the source dir for now.
Due to regular changes to the library code it is kept in the source dir for now.

Some files were not shown because too many files have changed in this diff Show More