From 9a896a7ab12086e6e95c127f6910a4d9e7d81408 Mon Sep 17 00:00:00 2001 From: iranl Date: Fri, 16 Aug 2024 13:02:37 +0200 Subject: [PATCH] Remove old and modified libs, switch to ESPAsyncWebserver, add support for ESP32-H2 and multiple Ethernet modules (#455) * Asyncwebserver * Squashed commit of the following: commit 575ef02f593918ec6654c87407a4d11fc17071b8 Author: technyon Date: Mon Aug 12 17:56:11 2024 +0200 merge master commit 35e5adf4ecd80f9829e8801181f35dd2c1d94759 Merge: a2cc7be2 21adca01 Author: technyon Date: Mon Aug 12 17:41:04 2024 +0200 Merge branch 'master' of github.com:technyon/nuki_hub into DM9051 commit a2cc7be2954cbd8767ab8186296c0b14134d1d0b Author: technyon Date: Mon Aug 12 10:51:50 2024 +0200 update nuki ble commit 20c809f3dca28b29b219d1ff3a183f1981316de5 Author: technyon Date: Mon Aug 12 10:44:46 2024 +0200 backup commit dd41c218efb5270f5efeb734e64dff695920db16 Merge: 153000b5 e84b944a Author: technyon Date: Mon Aug 12 10:40:03 2024 +0200 Merge branch 'master' of github.com:technyon/nuki_hub into DM9051 commit 153000b5b1af7df1fbeb5263df94eb26f689cc0a Author: technyon Date: Mon Aug 12 10:23:07 2024 +0200 fix linker error commit a93bbfbfc4301e46ff3696a763dd13c6c89efefb Author: technyon Date: Sun Aug 11 11:27:07 2024 +0200 backup commit f611c75ce8c35f829bcad6cf7e86188f4b3ec331 Merge: f1964917 063fbab6 Author: technyon Date: Sun Aug 11 11:24:47 2024 +0200 merge master commit f1964917b4dade3920f1ecdb699c58630199e6da Author: technyon Date: Sat Aug 10 15:17:45 2024 +0200 update platformio.ini commit f448e5e8a7e93be38e09e2ab0b622199a3721af6 Author: technyon Date: Sat Aug 10 11:28:09 2024 +0200 add SPIClass instance for DM9051 commit 1f190e9aa08033535a2eb442a92e6e20409bbda1 Author: technyon Date: Sat Aug 10 11:22:26 2024 +0200 add definitions and constructor for DM9051 commit 726b3602ae91594ee1210ad5b6714f75cc5e42a7 Merge: 50a2eb13 4af90cbc Author: technyon Date: Sat Aug 10 10:19:34 2024 +0200 merge master commit 50a2eb136d75d90921f1c6974f18bc107bddc123 Author: technyon Date: Fri Aug 9 11:52:09 2024 +0200 add comment commit 9437e485cae169efdf8e5a7bf188a1c7e792d1e5 Author: technyon Date: Sun Aug 4 08:29:21 2024 +0200 move LAN8720 definitions to seperate file * Remove Core 2 Ethernet library * Custom Ethernet * GPIO and Preferences * H2 --- .github/workflows/beta.yml | 7 +- .github/workflows/build.yml | 7 +- .github/workflows/nightly.yml | 7 +- .github/workflows/release.yml | 7 +- Docker/README.md | 2 +- Makefile | 3 - README.md | 53 +- clion/CMakeLists.txt | 17 +- .../CMakeLists.txt | 0 .../LICENSE | 0 .../README.md | 2 +- .../docs/_config.yml | 0 .../docs/index.md | 2 +- .../examples/CaptivePortal/CaptivePortal.ino | 0 .../examples/Draft/Draft.ino | 0 .../examples/Filters/Filters.ino | 0 .../examples/SimpleServer/SimpleServer.ino | 0 .../examples/StreamFiles/StreamConcat.h | 0 .../examples/StreamFiles/StreamFiles.ino | 0 .../examples/StreamFiles/StreamString.h | 0 .../examples/issues/Issue14/Issue14.ino | 0 .../library.json | 2 +- .../library.properties | 2 +- .../platformio.ini | 12 +- .../src/AsyncEventSource.cpp | 0 .../src/AsyncEventSource.h | 0 .../src/AsyncJson.h | 0 .../src/AsyncMessagePack.h | 0 .../src/AsyncWebSocket.cpp | 0 .../src/AsyncWebSocket.h | 31 +- .../src/ChunkPrint.h | 0 .../src/ESPAsyncWebServer.h | 129 +- .../src/WebAuthentication.cpp | 0 .../src/WebAuthentication.h | 0 .../src/WebHandlerImpl.h | 0 .../src/WebHandlers.cpp | 10 +- .../src/WebRequest.cpp | 102 +- .../src/WebResponseImpl.h | 27 +- .../src/WebResponses.cpp | 34 +- .../src/WebServer.cpp | 8 + .../src/literals.h | 2 + .../src/port/SHA1Builder.cpp | 0 .../src/port/SHA1Builder.h | 0 lib/Ethernet/.codespellrc | 7 - lib/Ethernet/AUTHORS | 36 - lib/Ethernet/README.adoc | 31 - lib/Ethernet/docs/api.md | 2607 ----------- .../docs/arduino_mega_ethernet_pins.png | Bin 19276 -> 0 bytes .../docs/arduino_uno_ethernet_pins.png | Bin 11418 -> 0 bytes lib/Ethernet/docs/readme.md | 16 - .../AdvancedChatServer/AdvancedChatServer.ino | 119 - .../BarometricPressureWebServer.ino | 247 - .../examples/ChatServer/ChatServer.ino | 96 - .../DhcpAddressPrinter/DhcpAddressPrinter.ino | 94 - .../DhcpChatServer/DhcpChatServer.ino | 101 - .../examples/LinkStatus/LinkStatus.ino | 44 - .../examples/PagerServer/PagerServer.ino | 71 - .../examples/TelnetClient/TelnetClient.ino | 109 - .../UDPSendReceiveString.ino | 138 - .../examples/UdpNtpClient/UdpNtpClient.ino | 145 - lib/Ethernet/examples/WebClient/WebClient.ino | 136 - .../WebClientRepeating/WebClientRepeating.ino | 126 - lib/Ethernet/examples/WebServer/WebServer.ino | 122 - lib/Ethernet/keywords.txt | 67 - lib/Ethernet/library.properties | 10 - lib/Ethernet/src/Dhcp.cpp | 437 -- lib/Ethernet/src/Dhcp.h | 137 - lib/Ethernet/src/Dns.cpp | 353 -- lib/Ethernet/src/Dns.h | 40 - lib/Ethernet/src/Ethernet.cpp | 243 - lib/Ethernet/src/Ethernet.h | 323 -- lib/Ethernet/src/EthernetClient.cpp | 213 - lib/Ethernet/src/EthernetClient.h | 3 - lib/Ethernet/src/EthernetServer.cpp | 179 - lib/Ethernet/src/EthernetServer.h | 3 - lib/Ethernet/src/EthernetUdp.cpp | 190 - lib/Ethernet/src/EthernetUdp.h | 38 - lib/Ethernet/src/socket.cpp | 538 --- lib/Ethernet/src/utility/w5100.cpp | 481 -- lib/Ethernet/src/utility/w5100.h | 487 -- .../examples/Authorization/Authorization.ino | 79 - lib/HTTPClient/examples/Authorization/ci.json | 5 - .../BasicHttpClient/BasicHttpClient.ino | 100 - .../examples/BasicHttpClient/ci.json | 5 - .../BasicHttpsClient/BasicHttpsClient.ino | 132 - .../examples/BasicHttpsClient/ci.json | 5 - .../HTTPClientEnterprise.ino | 107 - .../examples/HTTPClientEnterprise/ci.json | 5 - .../ReuseConnection/ReuseConnection.ino | 64 - .../examples/ReuseConnection/ci.json | 5 - .../StreamHttpClient/StreamHttpClient.ino | 97 - .../examples/StreamHttpClient/ci.json | 5 - lib/HTTPClient/library.properties | 9 - lib/HTTPClient/src/HTTPClient.cpp | 1645 ------- lib/HTTPClient/src/HTTPClient.h | 322 -- lib/NetworkClientSecure/README.md | 133 - .../WiFiClientInsecure/WiFiClientInsecure.ino | 70 - .../examples/WiFiClientInsecure/ci.json | 5 - .../examples/WiFiClientPSK/WiFiClientPSK.ino | 86 - .../examples/WiFiClientPSK/ci.json | 5 - .../WiFiClientSecure/WiFiClientSecure.ino | 112 - .../examples/WiFiClientSecure/ci.json | 5 - .../WiFiClientSecureEnterprise.ino | 132 - .../WiFiClientSecureEnterprise/ci.json | 5 - .../WiFiClientSecureProtocolUpgrade.ino | 190 - .../WiFiClientSecureProtocolUpgrade/ci.json | 5 - .../WiFiClientShowPeerCredentials.ino | 98 - .../WiFiClientShowPeerCredentials/ci.json | 5 - .../WiFiClientTrustOnFirstUse.ino | 270 -- .../WiFiClientTrustOnFirstUse/ci.json | 5 - lib/NetworkClientSecure/keywords.txt | 36 - lib/NetworkClientSecure/library.properties | 9 - .../src/NetworkClientSecure.cpp | 460 -- .../src/NetworkClientSecure.h | 132 - .../src/WiFiClientSecure.h | 3 - lib/NetworkClientSecure/src/ssl_client.cpp | 633 --- lib/NetworkClientSecure/src/ssl_client.h | 56 - .../AdvancedWebServer/AdvancedWebServer.ino | 147 - .../examples/FSBrowser/FSBrowser.ino | 304 -- .../examples/FSBrowser/data/edit.htm.gz | Bin 4116 -> 0 bytes .../examples/FSBrowser/data/favicon.ico | Bin 1150 -> 0 bytes .../examples/FSBrowser/data/graphs.js.gz | Bin 1971 -> 0 bytes .../examples/FSBrowser/data/index.htm | 97 - .../examples/HelloServer/HelloServer.ino | 74 - .../HttpAdvancedAuth/HttpAdvancedAuth.ino | 60 - .../examples/HttpBasicAuth/HttpBasicAuth.ino | 42 - .../examples/PathArgServer/PathArgServer.ino | 57 - .../examples/SDWebServer/SDWebServer.ino | 314 -- .../SDWebServer/SdRoot/edit/index.htm | 674 --- .../examples/SDWebServer/SdRoot/index.htm | 22 - .../examples/SDWebServer/SdRoot/pins.png | Bin 177869 -> 0 bytes .../SimpleAuthentification.ino | 133 - .../examples/WebUpdate/WebUpdate.ino | 69 - lib/WebServer/keywords.txt | 38 - lib/WebServer/library.properties | 9 - lib/WebServer/src/EthClient.h | 22 - lib/WebServer/src/EthServer.h | 20 - lib/WebServer/src/HTTP_Method.h | 9 - lib/WebServer/src/Parsing.cpp | 589 --- lib/WebServer/src/Uri.h | 29 - lib/WebServer/src/WebServer.cpp | 705 --- lib/WebServer/src/WebServer.h | 215 - lib/WebServer/src/detail/RequestHandler.h | 31 - .../src/detail/RequestHandlersImpl.h | 152 - lib/WebServer/src/detail/mimetable.cpp | 35 - lib/WebServer/src/detail/mimetable.h | 47 - lib/WebServer/src/hardware/W5500EthClient.cpp | 120 - lib/WebServer/src/hardware/W5500EthClient.h | 29 - lib/WebServer/src/hardware/W5500EthServer.cpp | 75 - lib/WebServer/src/hardware/W5500EthServer.h | 35 - lib/WebServer/src/hardware/WifiEthClient.cpp | 78 - lib/WebServer/src/hardware/WifiEthClient.h | 28 - lib/WebServer/src/hardware/WifiEthServer.cpp | 56 - lib/WebServer/src/hardware/WifiEthServer.h | 25 - lib/WebServer/src/uri/UriBraces.h | 66 - lib/WebServer/src/uri/UriGlob.h | 22 - lib/WebServer/src/uri/UriRegex.h | 44 - lib/WiFiManager/WiFiManager.cpp | 398 +- lib/WiFiManager/WiFiManager.h | 209 +- pio_package.py => pio_package_post.py | 12 +- pio_package_pre.py | 45 +- platformio.ini | 73 +- resources/how-to-flash.txt | 11 + sdkconfig.defaults | 11 +- src/CMakeLists.txt | 5 +- src/Config.h | 15 +- src/Gpio.cpp | 148 +- src/Gpio.h | 22 +- src/NukiNetwork.cpp | 288 +- src/NukiNetwork.h | 11 +- src/NukiNetworkLock.cpp | 14 +- src/NukiNetworkLock.h | 3 +- src/NukiNetworkOpener.h | 2 - src/NukiOpenerWrapper.cpp | 18 +- src/NukiOpenerWrapper.h | 1 + src/NukiWrapper.cpp | 14 +- src/NukiWrapper.h | 1 + src/Ota.cpp | 52 - src/Ota.h | 20 - src/PreferencesKeys.h | 53 +- src/RestartReason.h | 9 +- src/WebCfgServer.cpp | 3973 ++++++++++------- src/WebCfgServer.h | 97 +- src/main.cpp | 137 +- src/networkDevices/ClientSyncW5500.cpp | 75 - src/networkDevices/ClientSyncW5500.h | 25 - src/networkDevices/DM9051Definitions.h | 13 + src/networkDevices/EthLan8720Device.cpp | 179 - src/networkDevices/EthLan8720Device.h | 83 - src/networkDevices/EthernetDevice.cpp | 221 + src/networkDevices/EthernetDevice.h | 102 + src/networkDevices/IPConfiguration.cpp | 2 +- src/networkDevices/LAN8720Definitions.h | 20 + src/networkDevices/W5500Definitions.h | 22 + src/networkDevices/W5500Device.cpp | 238 - src/networkDevices/W5500Device.h | 53 - src/networkDevices/WifiDevice.cpp | 18 +- src/networkDevices/WifiDevice.h | 1 + src/networkDevices/espMqttClientW5500.cpp | 13 - src/networkDevices/espMqttClientW5500.h | 22 - updater/pio_package.py | 52 - updater/pio_package_post.py | 28 + updater/pio_package_pre.py | 45 +- updater/platformio.ini | 36 +- updater/sdkconfig.defaults | 9 +- updater/src/CMakeLists.txt | 20 +- 206 files changed, 4055 insertions(+), 20829 deletions(-) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/CMakeLists.txt (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/LICENSE (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/README.md (99%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/docs/_config.yml (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/docs/index.md (99%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/examples/CaptivePortal/CaptivePortal.ino (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/examples/Draft/Draft.ino (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/examples/Filters/Filters.ino (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/examples/SimpleServer/SimpleServer.ino (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/examples/StreamFiles/StreamConcat.h (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/examples/StreamFiles/StreamFiles.ino (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/examples/StreamFiles/StreamString.h (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/examples/issues/Issue14/Issue14.ino (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/library.json (98%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/library.properties (96%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/platformio.ini (88%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/AsyncEventSource.cpp (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/AsyncEventSource.h (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/AsyncJson.h (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/AsyncMessagePack.h (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/AsyncWebSocket.cpp (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/AsyncWebSocket.h (98%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/ChunkPrint.h (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/ESPAsyncWebServer.h (78%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/WebAuthentication.cpp (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/WebAuthentication.h (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/WebHandlerImpl.h (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/WebHandlers.cpp (96%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/WebRequest.cpp (90%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/WebResponseImpl.h (71%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/WebResponses.cpp (94%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/WebServer.cpp (97%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/literals.h (99%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/port/SHA1Builder.cpp (100%) rename lib/{ESP Async WebServer => ESPAsyncWebServer}/src/port/SHA1Builder.h (100%) delete mode 100644 lib/Ethernet/.codespellrc delete mode 100644 lib/Ethernet/AUTHORS delete mode 100644 lib/Ethernet/README.adoc delete mode 100644 lib/Ethernet/docs/api.md delete mode 100644 lib/Ethernet/docs/arduino_mega_ethernet_pins.png delete mode 100644 lib/Ethernet/docs/arduino_uno_ethernet_pins.png delete mode 100644 lib/Ethernet/docs/readme.md delete mode 100644 lib/Ethernet/examples/AdvancedChatServer/AdvancedChatServer.ino delete mode 100644 lib/Ethernet/examples/BarometricPressureWebServer/BarometricPressureWebServer.ino delete mode 100644 lib/Ethernet/examples/ChatServer/ChatServer.ino delete mode 100644 lib/Ethernet/examples/DhcpAddressPrinter/DhcpAddressPrinter.ino delete mode 100644 lib/Ethernet/examples/DhcpChatServer/DhcpChatServer.ino delete mode 100644 lib/Ethernet/examples/LinkStatus/LinkStatus.ino delete mode 100644 lib/Ethernet/examples/PagerServer/PagerServer.ino delete mode 100644 lib/Ethernet/examples/TelnetClient/TelnetClient.ino delete mode 100644 lib/Ethernet/examples/UDPSendReceiveString/UDPSendReceiveString.ino delete mode 100644 lib/Ethernet/examples/UdpNtpClient/UdpNtpClient.ino delete mode 100644 lib/Ethernet/examples/WebClient/WebClient.ino delete mode 100644 lib/Ethernet/examples/WebClientRepeating/WebClientRepeating.ino delete mode 100644 lib/Ethernet/examples/WebServer/WebServer.ino delete mode 100644 lib/Ethernet/keywords.txt delete mode 100644 lib/Ethernet/library.properties delete mode 100644 lib/Ethernet/src/Dhcp.cpp delete mode 100644 lib/Ethernet/src/Dhcp.h delete mode 100644 lib/Ethernet/src/Dns.cpp delete mode 100644 lib/Ethernet/src/Dns.h delete mode 100644 lib/Ethernet/src/Ethernet.cpp delete mode 100644 lib/Ethernet/src/Ethernet.h delete mode 100644 lib/Ethernet/src/EthernetClient.cpp delete mode 100644 lib/Ethernet/src/EthernetClient.h delete mode 100644 lib/Ethernet/src/EthernetServer.cpp delete mode 100644 lib/Ethernet/src/EthernetServer.h delete mode 100644 lib/Ethernet/src/EthernetUdp.cpp delete mode 100644 lib/Ethernet/src/EthernetUdp.h delete mode 100644 lib/Ethernet/src/socket.cpp delete mode 100644 lib/Ethernet/src/utility/w5100.cpp delete mode 100644 lib/Ethernet/src/utility/w5100.h delete mode 100644 lib/HTTPClient/examples/Authorization/Authorization.ino delete mode 100644 lib/HTTPClient/examples/Authorization/ci.json delete mode 100644 lib/HTTPClient/examples/BasicHttpClient/BasicHttpClient.ino delete mode 100644 lib/HTTPClient/examples/BasicHttpClient/ci.json delete mode 100644 lib/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino delete mode 100644 lib/HTTPClient/examples/BasicHttpsClient/ci.json delete mode 100644 lib/HTTPClient/examples/HTTPClientEnterprise/HTTPClientEnterprise.ino delete mode 100644 lib/HTTPClient/examples/HTTPClientEnterprise/ci.json delete mode 100644 lib/HTTPClient/examples/ReuseConnection/ReuseConnection.ino delete mode 100644 lib/HTTPClient/examples/ReuseConnection/ci.json delete mode 100644 lib/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino delete mode 100644 lib/HTTPClient/examples/StreamHttpClient/ci.json delete mode 100644 lib/HTTPClient/library.properties delete mode 100644 lib/HTTPClient/src/HTTPClient.cpp delete mode 100644 lib/HTTPClient/src/HTTPClient.h delete mode 100644 lib/NetworkClientSecure/README.md delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientInsecure/WiFiClientInsecure.ino delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientInsecure/ci.json delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientPSK/WiFiClientPSK.ino delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientPSK/ci.json delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientSecure/WiFiClientSecure.ino delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientSecure/ci.json delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientSecureEnterprise/WiFiClientSecureEnterprise.ino delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientSecureEnterprise/ci.json delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientSecureProtocolUpgrade/WiFiClientSecureProtocolUpgrade.ino delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientSecureProtocolUpgrade/ci.json delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientShowPeerCredentials/WiFiClientShowPeerCredentials.ino delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientShowPeerCredentials/ci.json delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino delete mode 100644 lib/NetworkClientSecure/examples/WiFiClientTrustOnFirstUse/ci.json delete mode 100644 lib/NetworkClientSecure/keywords.txt delete mode 100644 lib/NetworkClientSecure/library.properties delete mode 100644 lib/NetworkClientSecure/src/NetworkClientSecure.cpp delete mode 100644 lib/NetworkClientSecure/src/NetworkClientSecure.h delete mode 100644 lib/NetworkClientSecure/src/WiFiClientSecure.h delete mode 100644 lib/NetworkClientSecure/src/ssl_client.cpp delete mode 100644 lib/NetworkClientSecure/src/ssl_client.h delete mode 100644 lib/WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino delete mode 100644 lib/WebServer/examples/FSBrowser/FSBrowser.ino delete mode 100644 lib/WebServer/examples/FSBrowser/data/edit.htm.gz delete mode 100644 lib/WebServer/examples/FSBrowser/data/favicon.ico delete mode 100644 lib/WebServer/examples/FSBrowser/data/graphs.js.gz delete mode 100644 lib/WebServer/examples/FSBrowser/data/index.htm delete mode 100644 lib/WebServer/examples/HelloServer/HelloServer.ino delete mode 100644 lib/WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino delete mode 100644 lib/WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino delete mode 100644 lib/WebServer/examples/PathArgServer/PathArgServer.ino delete mode 100644 lib/WebServer/examples/SDWebServer/SDWebServer.ino delete mode 100644 lib/WebServer/examples/SDWebServer/SdRoot/edit/index.htm delete mode 100644 lib/WebServer/examples/SDWebServer/SdRoot/index.htm delete mode 100644 lib/WebServer/examples/SDWebServer/SdRoot/pins.png delete mode 100644 lib/WebServer/examples/SimpleAuthentification/SimpleAuthentification.ino delete mode 100644 lib/WebServer/examples/WebUpdate/WebUpdate.ino delete mode 100644 lib/WebServer/keywords.txt delete mode 100644 lib/WebServer/library.properties delete mode 100644 lib/WebServer/src/EthClient.h delete mode 100644 lib/WebServer/src/EthServer.h delete mode 100644 lib/WebServer/src/HTTP_Method.h delete mode 100644 lib/WebServer/src/Parsing.cpp delete mode 100644 lib/WebServer/src/Uri.h delete mode 100644 lib/WebServer/src/WebServer.cpp delete mode 100644 lib/WebServer/src/WebServer.h delete mode 100644 lib/WebServer/src/detail/RequestHandler.h delete mode 100644 lib/WebServer/src/detail/RequestHandlersImpl.h delete mode 100644 lib/WebServer/src/detail/mimetable.cpp delete mode 100644 lib/WebServer/src/detail/mimetable.h delete mode 100644 lib/WebServer/src/hardware/W5500EthClient.cpp delete mode 100644 lib/WebServer/src/hardware/W5500EthClient.h delete mode 100644 lib/WebServer/src/hardware/W5500EthServer.cpp delete mode 100644 lib/WebServer/src/hardware/W5500EthServer.h delete mode 100644 lib/WebServer/src/hardware/WifiEthClient.cpp delete mode 100644 lib/WebServer/src/hardware/WifiEthClient.h delete mode 100644 lib/WebServer/src/hardware/WifiEthServer.cpp delete mode 100644 lib/WebServer/src/hardware/WifiEthServer.h delete mode 100644 lib/WebServer/src/uri/UriBraces.h delete mode 100644 lib/WebServer/src/uri/UriGlob.h delete mode 100644 lib/WebServer/src/uri/UriRegex.h rename pio_package.py => pio_package_post.py (87%) delete mode 100644 src/Ota.cpp delete mode 100644 src/Ota.h delete mode 100644 src/networkDevices/ClientSyncW5500.cpp delete mode 100644 src/networkDevices/ClientSyncW5500.h create mode 100644 src/networkDevices/DM9051Definitions.h delete mode 100644 src/networkDevices/EthLan8720Device.cpp delete mode 100644 src/networkDevices/EthLan8720Device.h create mode 100644 src/networkDevices/EthernetDevice.cpp create mode 100644 src/networkDevices/EthernetDevice.h create mode 100644 src/networkDevices/LAN8720Definitions.h create mode 100644 src/networkDevices/W5500Definitions.h delete mode 100644 src/networkDevices/W5500Device.cpp delete mode 100644 src/networkDevices/W5500Device.h delete mode 100644 src/networkDevices/espMqttClientW5500.cpp delete mode 100644 src/networkDevices/espMqttClientW5500.h delete mode 100644 updater/pio_package.py create mode 100644 updater/pio_package_post.py diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 5ae76d4..76ff2a9 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - board: [esp32dev, esp32-s3, esp32-c3, esp32-c6, esp32-solo1] + board: [esp32, esp32-s3, esp32-c3, esp32-c6, esp32-h2, esp32-solo1] build: [release, debug] env: BOARD: ${{ matrix.board }} @@ -23,11 +23,6 @@ jobs: run: | # remove dash character export VARIANT=${VARIANT//-/} - - if [ "$VARIANT" = "esp32dev" ]; then - VARIANT="esp32" - fi - echo "VARIANT=${VARIANT}" | tee -a ${GITHUB_ENV} - uses: actions/checkout@v4 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1dc3ea5..f6ad16f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - board: [esp32dev, esp32-s3, esp32-c3, esp32-c6, esp32-solo1] + board: [esp32, esp32-s3, esp32-c3, esp32-c6, esp32-h2, esp32-solo1] build: [release, debug] env: BOARD: ${{ matrix.board }} @@ -29,11 +29,6 @@ jobs: run: | # remove dash character export VARIANT=${VARIANT//-/} - - if [ "$VARIANT" = "esp32dev" ]; then - VARIANT="esp32" - fi - echo "VARIANT=${VARIANT}" | tee -a ${GITHUB_ENV} - uses: actions/checkout@v4 with: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 09c197a..4a00ebb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - board: [esp32dev, esp32-s3, esp32-c3, esp32-c6, esp32-solo1] + board: [esp32, esp32-s3, esp32-c3, esp32-c6, esp32-h2, esp32-solo1] build: [release, debug] env: BOARD: ${{ matrix.board }} @@ -45,11 +45,6 @@ jobs: run: | # remove dash character export VARIANT=${VARIANT//-/} - - if [ "$VARIANT" = "esp32dev" ]; then - VARIANT="esp32" - fi - echo "VARIANT=${VARIANT}" | tee -a ${GITHUB_ENV} - uses: actions/checkout@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c4c0b9..b8a2eb1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - board: [esp32dev, esp32-s3, esp32-c3, esp32-c6, esp32-solo1] + board: [esp32, esp32-s3, esp32-c3, esp32-c6, esp32-h2, esp32-solo1] build: [release, debug] env: BOARD: ${{ matrix.board }} @@ -23,11 +23,6 @@ jobs: run: | # remove dash character export VARIANT=${VARIANT//-/} - - if [ "$VARIANT" = "esp32dev" ]; then - VARIANT="esp32" - fi - echo "VARIANT=${VARIANT}" | tee -a ${GITHUB_ENV} - uses: actions/checkout@v4 with: diff --git a/Docker/README.md b/Docker/README.md index 5727b8a..140d733 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -2,7 +2,7 @@ You can build this project using Docker. Just run the following commands in the console: -## Build with PlatformIO (will build for the ESP32, ESP32-S3, ESP32-C3, ESP32-C6 and ESP32-Solo1) +## Build with PlatformIO (will build for the ESP32, ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2 and ESP32-Solo1) ```console git clone https://github.com/technyon/nuki_hub --recursive cd nuki_hub/Docker diff --git a/Makefile b/Makefile index 6b3070f..c9a48b5 100644 --- a/Makefile +++ b/Makefile @@ -22,9 +22,6 @@ debug: $(DEBUG_BOARDS) all: release updater debug # Alias -.PHONY: esp32 -esp32: esp32dev - esp%: @echo "Building $@" pio run --environment $@ diff --git a/README.md b/README.md index fce2513..4c7f7fa 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,10 @@ Feel free to join us on Discord: https://discord.gg/9nPq85bP4p - Nuki Hub is compiled against all ESP32 models with Wi-Fi and Bluetooh Low Energy (BLE) which are supported by ESP-IDF 5.1.4 and Arduino Core 3.0.4. - Tested stable builds are provided for the ESP32, ESP32-S3 and ESP32-C3. - Untested builds are provided for the ESP32-Solo1. -- Support for the ESP32-C6 is experimental. There could be more frequent crashes than on other ESP32 devices and connections with the Nuki device could be slower than on other ESP32 devices. +- Support for the ESP32-C6 and ESP32-H2 is experimental. There could be more frequent crashes than on other ESP32 devices and connections with the Nuki device could be slower than on other ESP32 devices. Not supported ESP32 devices: - The ESP32-S2 has no BLE and as such can't run Nuki Hub. -- The ESP32-H2 has no Wi-FI and Nuki Hub is not compiled against this target because of this (at this time). Supported Nuki devices: - Nuki Smart Lock 1.0 @@ -36,7 +35,7 @@ Feel free to join us on Discord: https://discord.gg/9nPq85bP4p - Nuki Keypad 2.0 Supported Ethernet devices:
-As an alternative to Wi-Fi (which is available on any supported ESP32), the following ESP32 modules with wired ethernet are supported: +As an alternative to Wi-Fi (which is available on any supported ESP32), the following ESP32 modules with built-in wired ethernet are supported: - [Olimex ESP32-POE](https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/open-source-hardware) - [Olimex ESP32-POE-ISO](https://www.olimex.com/Products/IoT/ESP32/ESP32-POE-ISO/open-source-hardware) - [WT32-ETH01](http://en.wireless-tag.com/product-item-2.html) @@ -45,6 +44,9 @@ As an alternative to Wi-Fi (which is available on any supported ESP32), the foll - [LilyGO-T-ETH-POE](https://github.com/Xinyuan-LilyGO/LilyGO-T-ETH-POE) - [GL-S10 (Revisions 2.1, 2.3 / 1.0 is not supported)](https://www.gl-inet.com/products/gl-s10/) +In principle all ESP32 (and variants) devices with built-in ethernet port are supported, but might require additional setup using the "Custom LAN setup" option. +See the "[Connecting via Ethernet](#connecting-via-ethernet-optional)" section for more information. + ## Support Nuki Hub development If you haven't ordered your Nuki product yet, you can support me by using my referrer code when placing your order:
@@ -76,7 +78,7 @@ In a browser navigate to the IP address assigned to the ESP32 via DHCP (often fo Next click on "Edit" below "MQTT and Network Configuration" and enter the address and port (usually 1883) of your MQTT broker and a username and a password if required by your MQTT broker.

The firmware supports SSL encryption for MQTT, however most people and especially home users don't use this.
-In that case leave all fields starting with "MQTT SSL" blank. Otherwise see the "[MQTT Encryption](#mqtt-encryption-optional-wi-fi-and-lan8720-only)" section of this README. +In that case leave all fields starting with "MQTT SSL" blank. Otherwise see the "[MQTT Encryption](#mqtt-encryption-optional)" section of this README. ## Pairing with a Nuki Lock or Opener @@ -114,9 +116,9 @@ In a browser navigate to the IP address assigned to the ESP32. - Home Assistant discovery topic: Set to the Home Assistant auto discovery topic, leave empty to disable auto discovery. Usually "homeassistant" unless you manually changed this setting on the Home Assistant side. - Home Assistant device configuration URL: When using Home Assistant discovery the link to the Nuki Hub Web Configuration will be published to Home Assistant. By default when this setting is left empty this will link to the current IP of the Nuki Hub. When using a reverse proxy to access the Web Configuration you can set a custom URL here. - Set Nuki Opener Lock/Unlock action in Home Assistant to Continuous mode (Opener only): By default the lock entity in Home Assistant will enable Ring-to-Open (RTO) when unlocking and disable RTO when locking. By enabling this setting this behaviour will change and now unlocking will enable Continuous Mode and locking will disable Continuous Mode, for more information see the "[Home Assistant Discovery](#home-assistant-discovery-optional)" section of this README. -- MQTT SSL CA Certificate: Optionally set to the CA SSL certificate of the MQTT broker, see the "[MQTT Encryption](#mqtt-encryption-optional-wi-fi-and-lan8720-only)" section of this README. -- MQTT SSL Client Certificate: Optionally set to the Client SSL certificate of the MQTT broker, see the "[MQTT Encryption](#mqtt-encryption-optional-wi-fi-and-lan8720-only)" section of this README. -- MQTT SSL Client Key: Optionally set to the Client SSL key of the MQTT broker, see the "[MQTT Encryption](#mqtt-encryption-optional-wi-fi-and-lan8720-only)" section of this README. +- MQTT SSL CA Certificate: Optionally set to the CA SSL certificate of the MQTT broker, see the "[MQTT Encryption](#mqtt-encryption-optional)" section of this README. +- MQTT SSL Client Certificate: Optionally set to the Client SSL certificate of the MQTT broker, see the "[MQTT Encryption](#mqtt-encryption-optional)" section of this README. +- MQTT SSL Client Key: Optionally set to the Client SSL key of the MQTT broker, see the "[MQTT Encryption](#mqtt-encryption-optional)" section of this README. - Network hardware: "Wi-Fi only" by default, set to one of the specified ethernet modules if available, see the "Supported Ethernet devices" and "[Connecting via Ethernet](#connecting-via-ethernet-optional)" section of this README. - Disable fallback to Wi-Fi / Wi-Fi config portal: By default the Nuki Hub will fallback to Wi-Fi and open the Wi-Fi configuration portal when the network connection fails. Enable this setting to disable this fallback. - Connect to AP with the best signal in an environment with multiple APs with the same SSID: Enable to perform a scan for the Access Point with the best signal strenght for the specified SSID in a multi AP/Mesh environment. @@ -302,7 +304,7 @@ In a browser navigate to the IP address assigned to the ESP32. ### Maintanence -- maintenance/networkDevice: Set to the name of the network device that is used by the ESP. When using Wi-Fi will be set to "Built-in Wi-Fi". If using Ethernet will be set to "Wiznet W5500", "Olimex (LAN8720)", "WT32-ETH01", "M5STACK PoESP32 Unit", "LilyGO T-ETH-POE" or "GL-S10". +- maintenance/networkDevice: Set to the name of the network device that is used by the ESP. When using Wi-Fi will be set to "Built-in Wi-Fi". If using Ethernet will be set to "Wiznet W5500", "ETH01-Evo", "Olimex (LAN8720)", "WT32-ETH01", "M5STACK PoESP32 Unit", "LilyGO T-ETH-POE" or "GL-S10". - maintenance/reset: Set to 1 to trigger a reboot of the ESP. Auto-resets to 0. - maintenance/update: Set to 1 to auto update Nuki Hub to the latest version from GitHub. Requires the setting "Allow updating using MQTT" to be enabled. Auto-resets to 0. - maintenance/mqttConnectionState: Last Will and Testament (LWT) topic. "online" when Nuki Hub is connected to the MQTT broker, "offline" if Nuki Hub is not connected to the MQTT broker. @@ -441,7 +443,7 @@ Updating to version 9.00 requires a change to the partition table of the ESP32.< Please follow the instructions for the [First time installation](#first-time-installation) once when updating to Nuki Hub 9.00 from an earlier version.
Your settings will not be affected when updating using the above instructions (do not select erase device when updating using Webflash).
-## MQTT Encryption (optional; Wi-Fi and LAN8720 only) +## MQTT Encryption (optional) The communication via MQTT can be SSL encrypted.
To enable SSL encryption, supply the necessary information in the MQTT Configuration page.
@@ -607,28 +609,27 @@ To enable GPIO control, go the the "GPIO Configuration" page where each GPIO can ## Connecting via Ethernet (Optional) -If you prefer to connect to the MQTT Broker via Ethernet instead of Wi-Fi, you either use one of the supported ESP32 modules (see about section above), -or wire a seperate Wiznet W5x00 Module (W5100, W5200, W5500 are supported). To use a supported module, flash the firmware, connect via Wi-Fi and -select the correct network hardware in the "MQTT and Network Configuration" section. +If you prefer to connect to via Ethernet instead of Wi-Fi, you either use one of the supported ESP32 modules with built-in ethernet (see "[Supported devices](#supported-devices)" section) +or wire a seperate SPI Ethernet module.
+Currently the Wiznet W5x00 Module (W5100, W5200, W5500), DN9051 and KSZ8851SNL chips are supported.
+To use a supported module, flash the firmware, connect via Wi-Fi and select the correct network hardware in the "MQTT and Network Configuration" section. To wire an external W5x00 module to the ESP, use this wiring scheme: -- Connect W5x00 to ESP32 SPI0:
- - W5x00 SCK to GPIO18
- - W5x00 MISO to GPIO19
- - W5x00 MOSI to GPIO23
- - W5x00 CS/SS to GPIO5
-- Optionally connect:
- - W5x00 reset to GPIO33
+- Connect W5x00 to ESP32 SPI:
+ - W5x00 SCK to GPIO 8
+ - W5x00 MISO to GPIO 9
+ - W5x00 MOSI to GPIO 10
+ - W5x00 CS/SS to GPIO 5
+ Optional: + - W5x00 RST to GPIO 4
+ - W5x00 INT/IRQ to GPIO 3
Now connect via Wi-Fi and change the network hardware to "Generic W5500".
-If the W5500 hwardware isn't detected, Wi-Fi is used as a fallback.
+ +If Ethernet hwardware isn't detected, Wi-Fi is used as a fallback, unless this is disabled in the settings.

-Note: Encrypted MQTT is only available for Wi-Fi and LAN8720 modules, W5x00 modules don't support encryption
-(that leaves Olimex, WT32-ETH01 and M5Stack PoESP32 Unit if encryption is desired).
-Note: LAN8720 modules are only supported on the ESP32, not on the ESP32-S3, ESP32-C3 or ESP-C6
-
-If encryption is needed, Olimex is the easiest option, since it has USB for flashing onboard. +Note: LAN8720 modules are only supported on the ESP32 and ESP32-Solo1, not on the ESP32-S3, ESP32-C3 or ESP-C6
## Troubleshooting @@ -716,7 +717,7 @@ source .venv/bin/activate git clone https://github.com/technyon/nuki_hub --recursive cd nuki_hub -# install tools platformio and esptool +# install tools platformio and esptool make deps # build all binary boards diff --git a/clion/CMakeLists.txt b/clion/CMakeLists.txt index 9999a1f..1ce88d9 100644 --- a/clion/CMakeLists.txt +++ b/clion/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.16.0) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(nukihub) +add_compile_definitions(CONFIG_IDF_TARGET_ESP32) + set(SRCFILES ../src/Config.h ../src/NukiDeviceId.cpp @@ -12,18 +14,17 @@ set(SRCFILES ../src/NukiNetworkOpener.cpp ../src/networkDevices/NetworkDevice.h ../src/networkDevices/NetworkDevice.cpp + ../src/networkDevices/LAN8720Definitions.h + ../src/networkDevices/DM9051Definitions.h + ../src/networkDevices/W5500Definitions.h ../src/networkDevices/WifiDevice.cpp - ../src/networkDevices/W5500Device.cpp - ../src/networkDevices/EthLan8720Device.cpp - ../src/networkDevices/ClientSyncW5500.cpp - ../src/networkDevices/espMqttClientW5500.cpp + ../src/networkDevices/EthernetDevice.cpp ../src/networkDevices/IPConfiguration.cpp ../src/LockActionResult.h ../src/QueryCommand.h ../src/NukiWrapper.cpp ../src/NukiOpenerWrapper.cpp ../src/MqttTopics.h - ../src/Ota.cpp ../src/WebCfgServerConstants.h ../src/WebCfgServer.cpp ../src/PresenceDetection.cpp @@ -59,10 +60,8 @@ file(GLOB_RECURSE SRCFILESREC lib/NimBLE-Arduino/src/*.c lib/NimBLE-Arduino/src/*.cpp lib/NimBLE-Arduino/src/*.h - lib/WebServer/src/*.cpp - lib/WebServer/src/*.h - lib/Ethernet/src/*.cpp - lib/Ethernet/src/*.h + lib/ESP Async WebServer/src/*.cpp + lib/ESP Async WebServer/src/*.h lib/espMqttClient/src/*.cpp lib/espMqttClient/src/*.h lib/espMqttClient/src/Packets/*.cpp diff --git a/lib/ESP Async WebServer/CMakeLists.txt b/lib/ESPAsyncWebServer/CMakeLists.txt similarity index 100% rename from lib/ESP Async WebServer/CMakeLists.txt rename to lib/ESPAsyncWebServer/CMakeLists.txt diff --git a/lib/ESP Async WebServer/LICENSE b/lib/ESPAsyncWebServer/LICENSE similarity index 100% rename from lib/ESP Async WebServer/LICENSE rename to lib/ESPAsyncWebServer/LICENSE diff --git a/lib/ESP Async WebServer/README.md b/lib/ESPAsyncWebServer/README.md similarity index 99% rename from lib/ESP Async WebServer/README.md rename to lib/ESPAsyncWebServer/README.md index 19ab602..6ca1d1e 100644 --- a/lib/ESP Async WebServer/README.md +++ b/lib/ESPAsyncWebServer/README.md @@ -14,7 +14,7 @@ This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubo **WARNING** The library name was changed from `ESP Async WebServer` to `ESPAsyncWebServer` as per the Arduino Lint recommendations. ``` -mathieucarbou/ESPAsyncWebServer @ 3.1.2 +mathieucarbou/ESPAsyncWebServer @ 3.1.5 ``` Dependency: diff --git a/lib/ESP Async WebServer/docs/_config.yml b/lib/ESPAsyncWebServer/docs/_config.yml similarity index 100% rename from lib/ESP Async WebServer/docs/_config.yml rename to lib/ESPAsyncWebServer/docs/_config.yml diff --git a/lib/ESP Async WebServer/docs/index.md b/lib/ESPAsyncWebServer/docs/index.md similarity index 99% rename from lib/ESP Async WebServer/docs/index.md rename to lib/ESPAsyncWebServer/docs/index.md index 19ab602..6ca1d1e 100644 --- a/lib/ESP Async WebServer/docs/index.md +++ b/lib/ESPAsyncWebServer/docs/index.md @@ -14,7 +14,7 @@ This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubo **WARNING** The library name was changed from `ESP Async WebServer` to `ESPAsyncWebServer` as per the Arduino Lint recommendations. ``` -mathieucarbou/ESPAsyncWebServer @ 3.1.2 +mathieucarbou/ESPAsyncWebServer @ 3.1.5 ``` Dependency: diff --git a/lib/ESP Async WebServer/examples/CaptivePortal/CaptivePortal.ino b/lib/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino similarity index 100% rename from lib/ESP Async WebServer/examples/CaptivePortal/CaptivePortal.ino rename to lib/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino diff --git a/lib/ESP Async WebServer/examples/Draft/Draft.ino b/lib/ESPAsyncWebServer/examples/Draft/Draft.ino similarity index 100% rename from lib/ESP Async WebServer/examples/Draft/Draft.ino rename to lib/ESPAsyncWebServer/examples/Draft/Draft.ino diff --git a/lib/ESP Async WebServer/examples/Filters/Filters.ino b/lib/ESPAsyncWebServer/examples/Filters/Filters.ino similarity index 100% rename from lib/ESP Async WebServer/examples/Filters/Filters.ino rename to lib/ESPAsyncWebServer/examples/Filters/Filters.ino diff --git a/lib/ESP Async WebServer/examples/SimpleServer/SimpleServer.ino b/lib/ESPAsyncWebServer/examples/SimpleServer/SimpleServer.ino similarity index 100% rename from lib/ESP Async WebServer/examples/SimpleServer/SimpleServer.ino rename to lib/ESPAsyncWebServer/examples/SimpleServer/SimpleServer.ino diff --git a/lib/ESP Async WebServer/examples/StreamFiles/StreamConcat.h b/lib/ESPAsyncWebServer/examples/StreamFiles/StreamConcat.h similarity index 100% rename from lib/ESP Async WebServer/examples/StreamFiles/StreamConcat.h rename to lib/ESPAsyncWebServer/examples/StreamFiles/StreamConcat.h diff --git a/lib/ESP Async WebServer/examples/StreamFiles/StreamFiles.ino b/lib/ESPAsyncWebServer/examples/StreamFiles/StreamFiles.ino similarity index 100% rename from lib/ESP Async WebServer/examples/StreamFiles/StreamFiles.ino rename to lib/ESPAsyncWebServer/examples/StreamFiles/StreamFiles.ino diff --git a/lib/ESP Async WebServer/examples/StreamFiles/StreamString.h b/lib/ESPAsyncWebServer/examples/StreamFiles/StreamString.h similarity index 100% rename from lib/ESP Async WebServer/examples/StreamFiles/StreamString.h rename to lib/ESPAsyncWebServer/examples/StreamFiles/StreamString.h diff --git a/lib/ESP Async WebServer/examples/issues/Issue14/Issue14.ino b/lib/ESPAsyncWebServer/examples/issues/Issue14/Issue14.ino similarity index 100% rename from lib/ESP Async WebServer/examples/issues/Issue14/Issue14.ino rename to lib/ESPAsyncWebServer/examples/issues/Issue14/Issue14.ino diff --git a/lib/ESP Async WebServer/library.json b/lib/ESPAsyncWebServer/library.json similarity index 98% rename from lib/ESP Async WebServer/library.json rename to lib/ESPAsyncWebServer/library.json index e2c7953..eb946c6 100644 --- a/lib/ESP Async WebServer/library.json +++ b/lib/ESPAsyncWebServer/library.json @@ -1,6 +1,6 @@ { "name": "ESPAsyncWebServer", - "version": "3.1.2", + "version": "3.1.5", "description": "Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.", "keywords": "http,async,websocket,webserver", "homepage": "https://github.com/mathieucarbou/ESPAsyncWebServer", diff --git a/lib/ESP Async WebServer/library.properties b/lib/ESPAsyncWebServer/library.properties similarity index 96% rename from lib/ESP Async WebServer/library.properties rename to lib/ESPAsyncWebServer/library.properties index 8c610fd..3036044 100644 --- a/lib/ESP Async WebServer/library.properties +++ b/lib/ESPAsyncWebServer/library.properties @@ -1,5 +1,5 @@ name=ESPAsyncWebServer -version=3.1.2 +version=3.1.5 author=Me-No-Dev maintainer=Mathieu Carbou sentence=Asynchronous HTTP and WebSocket Server Library for ESP32, ESP8266 and RP2040 diff --git a/lib/ESP Async WebServer/platformio.ini b/lib/ESPAsyncWebServer/platformio.ini similarity index 88% rename from lib/ESP Async WebServer/platformio.ini rename to lib/ESPAsyncWebServer/platformio.ini index e621af8..6f68d0d 100644 --- a/lib/ESP Async WebServer/platformio.ini +++ b/lib/ESPAsyncWebServer/platformio.ini @@ -30,7 +30,7 @@ lib_deps = mathieucarbou/AsyncTCP @ 3.2.4 [env:arduino-2] -platform = espressif32@6.7.0 +platform = espressif32@6.8.1 board = esp32dev lib_deps = bblanchon/ArduinoJson @ 7.1.0 @@ -39,8 +39,8 @@ lib_deps = [env:arduino-3] platform = espressif32 platform_packages= - platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.3 - platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.3/esp32-arduino-libs-3.0.3.zip + platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.4 + platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.4/esp32-arduino-libs-3.0.4.zip board = esp32dev lib_deps = bblanchon/ArduinoJson @ 7.1.0 @@ -66,14 +66,14 @@ lib_deps = khoih-prog/AsyncTCP_RP2040W @ 1.2.0 [env:pioarduino-esp32dev] -platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.03/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.04/platform-espressif32.zip board = esp32dev lib_deps = bblanchon/ArduinoJson @ 7.1.0 - mathieucarbou/AsyncTCP @ 3.2.3 + mathieucarbou/AsyncTCP @ 3.2.4 [env:pioarduino-c6] -platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.03/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.04/platform-espressif32.zip board = esp32-c6-devkitc-1 lib_deps = bblanchon/ArduinoJson @ 7.1.0 diff --git a/lib/ESP Async WebServer/src/AsyncEventSource.cpp b/lib/ESPAsyncWebServer/src/AsyncEventSource.cpp similarity index 100% rename from lib/ESP Async WebServer/src/AsyncEventSource.cpp rename to lib/ESPAsyncWebServer/src/AsyncEventSource.cpp diff --git a/lib/ESP Async WebServer/src/AsyncEventSource.h b/lib/ESPAsyncWebServer/src/AsyncEventSource.h similarity index 100% rename from lib/ESP Async WebServer/src/AsyncEventSource.h rename to lib/ESPAsyncWebServer/src/AsyncEventSource.h diff --git a/lib/ESP Async WebServer/src/AsyncJson.h b/lib/ESPAsyncWebServer/src/AsyncJson.h similarity index 100% rename from lib/ESP Async WebServer/src/AsyncJson.h rename to lib/ESPAsyncWebServer/src/AsyncJson.h diff --git a/lib/ESP Async WebServer/src/AsyncMessagePack.h b/lib/ESPAsyncWebServer/src/AsyncMessagePack.h similarity index 100% rename from lib/ESP Async WebServer/src/AsyncMessagePack.h rename to lib/ESPAsyncWebServer/src/AsyncMessagePack.h diff --git a/lib/ESP Async WebServer/src/AsyncWebSocket.cpp b/lib/ESPAsyncWebServer/src/AsyncWebSocket.cpp similarity index 100% rename from lib/ESP Async WebServer/src/AsyncWebSocket.cpp rename to lib/ESPAsyncWebServer/src/AsyncWebSocket.cpp diff --git a/lib/ESP Async WebServer/src/AsyncWebSocket.h b/lib/ESPAsyncWebServer/src/AsyncWebSocket.h similarity index 98% rename from lib/ESP Async WebServer/src/AsyncWebSocket.h rename to lib/ESPAsyncWebServer/src/AsyncWebSocket.h index ee7bde1..34256a7 100644 --- a/lib/ESP Async WebServer/src/AsyncWebSocket.h +++ b/lib/ESPAsyncWebServer/src/AsyncWebSocket.h @@ -230,18 +230,12 @@ class AsyncWebSocketClient { size_t queueLen() const; size_t printf(const char* format, ...) __attribute__((format(printf, 2, 3))); -#ifndef ESP32 - size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3))); -#endif void text(AsyncWebSocketSharedBuffer buffer); void text(const uint8_t* message, size_t len); void text(const char* message, size_t len); void text(const char* message); void text(const String& message); -#ifndef ESP32 - void text(const __FlashStringHelper* message); -#endif // ESP32 void text(AsyncWebSocketMessageBuffer* buffer); void binary(AsyncWebSocketSharedBuffer buffer); @@ -249,9 +243,6 @@ class AsyncWebSocketClient { void binary(const char* message, size_t len); void binary(const char* message); void binary(const String& message); -#ifndef ESP32 - void binary(const __FlashStringHelper* message, size_t len); -#endif // ESP32 void binary(AsyncWebSocketMessageBuffer* buffer); bool canSend() const; @@ -263,6 +254,12 @@ class AsyncWebSocketClient { void _onTimeout(uint32_t time); void _onDisconnect(); void _onData(void* pbuf, size_t plen); + +#ifdef ESP8266 + size_t printf_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3))); + void text(const __FlashStringHelper* message); + void binary(const __FlashStringHelper* message, size_t len); +#endif }; using AwsHandshakeHandler = std::function; @@ -308,17 +305,11 @@ class AsyncWebSocket : public AsyncWebHandler { void text(uint32_t id, const String& message); void text(uint32_t id, AsyncWebSocketMessageBuffer* buffer); void text(uint32_t id, AsyncWebSocketSharedBuffer buffer); -#ifdef ESP8266 - void text(uint32_t id, const __FlashStringHelper* message); -#endif // ESP8266 void textAll(const uint8_t* message, size_t len); void textAll(const char* message, size_t len); void textAll(const char* message); void textAll(const String& message); -#ifdef ESP8266 - void textAll(const __FlashStringHelper* message); -#endif // ESP8266 void textAll(AsyncWebSocketMessageBuffer* buffer); void textAll(AsyncWebSocketSharedBuffer buffer); @@ -326,9 +317,6 @@ class AsyncWebSocket : public AsyncWebHandler { void binary(uint32_t id, const char* message, size_t len); void binary(uint32_t id, const char* message); void binary(uint32_t id, const String& message); -#ifdef ESP8266 - void binary(uint32_t id, const __FlashStringHelper* message, size_t len); -#endif // ESP8266 void binary(uint32_t id, AsyncWebSocketMessageBuffer* buffer); void binary(uint32_t id, AsyncWebSocketSharedBuffer buffer); @@ -336,9 +324,6 @@ class AsyncWebSocket : public AsyncWebHandler { void binaryAll(const char* message, size_t len); void binaryAll(const char* message); void binaryAll(const String& message); -#ifdef ESP8266 - void binaryAll(const __FlashStringHelper* message, size_t len); -#endif // ESP8266 void binaryAll(AsyncWebSocketMessageBuffer* buffer); void binaryAll(AsyncWebSocketSharedBuffer buffer); @@ -346,6 +331,10 @@ class AsyncWebSocket : public AsyncWebHandler { size_t printfAll(const char* format, ...) __attribute__((format(printf, 2, 3))); #ifdef ESP8266 + void text(uint32_t id, const __FlashStringHelper* message); + void textAll(const __FlashStringHelper* message); + void binary(uint32_t id, const __FlashStringHelper* message, size_t len); + void binaryAll(const __FlashStringHelper* message, size_t len); size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__((format(printf, 3, 4))); size_t printfAll_P(PGM_P formatP, ...) __attribute__((format(printf, 2, 3))); #endif diff --git a/lib/ESP Async WebServer/src/ChunkPrint.h b/lib/ESPAsyncWebServer/src/ChunkPrint.h similarity index 100% rename from lib/ESP Async WebServer/src/ChunkPrint.h rename to lib/ESPAsyncWebServer/src/ChunkPrint.h diff --git a/lib/ESP Async WebServer/src/ESPAsyncWebServer.h b/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h similarity index 78% rename from lib/ESP Async WebServer/src/ESPAsyncWebServer.h rename to lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h index f729f7e..de08bc0 100644 --- a/lib/ESP Async WebServer/src/ESPAsyncWebServer.h +++ b/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h @@ -35,20 +35,20 @@ #include #include #elif defined(TARGET_RP2040) - #include #include - #include #include + #include + #include #else #error Platform not supported #endif #include "literals.h" -#define ASYNCWEBSERVER_VERSION "3.1.2" +#define ASYNCWEBSERVER_VERSION "3.1.5" #define ASYNCWEBSERVER_VERSION_MAJOR 3 #define ASYNCWEBSERVER_VERSION_MINOR 1 -#define ASYNCWEBSERVER_VERSION_REVISION 2 +#define ASYNCWEBSERVER_VERSION_REVISION 5 #define ASYNCWEBSERVER_FORK_mathieucarbou #ifdef ASYNCWEBSERVER_REGEX @@ -68,20 +68,20 @@ class AsyncStaticWebHandler; class AsyncCallbackWebHandler; class AsyncResponseStream; -#if defined (TARGET_RP2040) - typedef enum http_method WebRequestMethod; +#if defined(TARGET_RP2040) +typedef enum http_method WebRequestMethod; #else #ifndef WEBSERVER_H - typedef enum { - HTTP_GET = 0b00000001, - HTTP_POST = 0b00000010, - HTTP_DELETE = 0b00000100, - HTTP_PUT = 0b00001000, - HTTP_PATCH = 0b00010000, - HTTP_HEAD = 0b00100000, - HTTP_OPTIONS = 0b01000000, - HTTP_ANY = 0b01111111, - } WebRequestMethod; +typedef enum { + HTTP_GET = 0b00000001, + HTTP_POST = 0b00000010, + HTTP_DELETE = 0b00000100, + HTTP_PUT = 0b00001000, + HTTP_PATCH = 0b00010000, + HTTP_HEAD = 0b00100000, + HTTP_OPTIONS = 0b01000000, + HTTP_ANY = 0b01111111, +} WebRequestMethod; #endif #endif @@ -138,6 +138,7 @@ class AsyncWebHeader { AsyncWebHeader() = default; AsyncWebHeader(const AsyncWebHeader&) = default; + AsyncWebHeader(const char* name, const char* value) : _name(name), _value(value) {} AsyncWebHeader(const String& name, const String& value) : _name(name), _value(value) {} AsyncWebHeader(const String& data) { if (!data) @@ -153,7 +154,14 @@ class AsyncWebHeader { const String& name() const { return _name; } const String& value() const { return _value; } - String toString() const { return _name + (char)0x3a + (char)0x20 /*": "*/ + _value + asyncsrv::T_rn; } + String toString() const { + String str = _name; + str.concat((char)0x3a); + str.concat((char)0x20); + str.concat(_value); + str.concat(asyncsrv::T_rn); + return str; + } }; /* @@ -293,14 +301,37 @@ class AsyncWebServerRequest { void redirect(const String& url) { return redirect(url.c_str()); }; void send(AsyncWebServerResponse* response); - void send(int code, const String& contentType = String(), const String& content = String()); - void send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr); - void send(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr); - void send(FS& fs, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr); - void send(File content, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr); - void send(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr); - void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); + + void send(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, callback)); } + void send(int code, const String& contentType, const String& content = emptyString, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, callback)); } + + void send(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, len, callback)); } + void send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, len, callback)); } + + void send(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) { + if (fs.exists(path) || (!download && fs.exists(path + asyncsrv::T__gz))) { + send(beginResponse(fs, path, contentType, download, callback)); + } else + send(404); + } + void send(FS& fs, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { send(fs, path, contentType.c_str(), download, callback); } + + void send(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr) { + if (content) { + send(beginResponse(content, path, contentType, download, callback)); + } else + send(404); + } + void send(File content, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { send(content, path, contentType.c_str(), download, callback); } + + void send(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(stream, contentType, len, callback)); } + void send(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) { send(beginResponse(stream, contentType, len, callback)); } + + void send(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginResponse(contentType, len, callback, templateCallback)); } + void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginResponse(contentType, len, callback, templateCallback)); } + + void sendChunked(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginChunkedResponse(contentType, callback, templateCallback)); } + void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { send(beginChunkedResponse(contentType, callback, templateCallback)); } [[deprecated("Replaced by send(...)")]] void send_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { @@ -311,16 +342,33 @@ class AsyncWebServerRequest { send(code, contentType, content, callback); } - AsyncWebServerResponse* beginResponse(int code, const String& contentType = String(), const String& content = String()); - AsyncWebServerResponse* beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr); - AsyncWebServerResponse* beginResponse(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr); - AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr); - AsyncWebServerResponse* beginResponse(File content, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr); - AsyncWebServerResponse* beginResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr); - AsyncWebServerResponse* beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - AsyncWebServerResponse* beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); - AsyncResponseStream* beginResponseStream(const String& contentType, size_t bufferSize = 1460); +#ifdef ESP8266 + void send(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr) { send(beginResponse(code, contentType, content, callback)); } +#endif + AsyncWebServerResponse* beginResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty, AwsTemplateProcessor callback = nullptr); + AsyncWebServerResponse* beginResponse(int code, const String& contentType, const String& content = emptyString, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content.c_str(), callback); } + + AsyncWebServerResponse* beginResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr); + AsyncWebServerResponse* beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { return beginResponse(code, contentType.c_str(), content, len, callback); } + + AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); + AsyncWebServerResponse* beginResponse(FS& fs, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(fs, path, contentType.c_str(), download, callback); } + + AsyncWebServerResponse* beginResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); + AsyncWebServerResponse* beginResponse(File content, const String& path, const String& contentType = emptyString, bool download = false, AwsTemplateProcessor callback = nullptr) { return beginResponse(content, path, contentType.c_str(), download, callback); } + + AsyncWebServerResponse* beginResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr); + AsyncWebServerResponse* beginResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) { return beginResponse(stream, contentType.c_str(), len, callback); } + + AsyncWebServerResponse* beginResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); + AsyncWebServerResponse* beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) { return beginResponse(contentType.c_str(), len, callback, templateCallback); } + + AsyncWebServerResponse* beginChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); + AsyncWebServerResponse* beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); + + AsyncResponseStream* beginResponseStream(const char* contentType, size_t bufferSize = 1460); + AsyncResponseStream* beginResponseStream(const String& contentType, size_t bufferSize = 1460) { return beginResponseStream(contentType.c_str(), bufferSize); } [[deprecated("Replaced by beginResponse(...)")]] AsyncWebServerResponse* beginResponse_P(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) { @@ -331,6 +379,10 @@ class AsyncWebServerRequest { return beginResponse(code, contentType, content, callback); } +#ifdef ESP8266 + AsyncWebServerResponse* beginResponse(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback = nullptr); +#endif + size_t headers() const; // get header count // check if header exists @@ -348,7 +400,8 @@ class AsyncWebServerRequest { const AsyncWebHeader* getHeader(size_t num) const; size_t params() const; // get arguments count - bool hasParam(const String& name, bool post = false, bool file = false) const; + bool hasParam(const char* name, bool post = false, bool file = false) const; + bool hasParam(const String& name, bool post = false, bool file = false) const { return hasParam(name.c_str(), post, file); }; #ifdef ESP8266 bool hasParam(const __FlashStringHelper* data, bool post = false, bool file = false) const { return hasParam(String(data).c_str(), post, file); }; #endif @@ -526,8 +579,10 @@ class AsyncWebServerResponse { virtual ~AsyncWebServerResponse(); virtual void setCode(int code); virtual void setContentLength(size_t len); - virtual void setContentType(const String& type); - virtual void addHeader(const String& name, const String& value); + void setContentType(const String& type) { setContentType(type.c_str()); } + virtual void setContentType(const char* type); + virtual void addHeader(const char* name, const char* value); + void addHeader(const String& name, const String& value) { addHeader(name.c_str(), value.c_str()); } virtual String _assembleHead(uint8_t version); virtual bool _started() const; virtual bool _finished() const; diff --git a/lib/ESP Async WebServer/src/WebAuthentication.cpp b/lib/ESPAsyncWebServer/src/WebAuthentication.cpp similarity index 100% rename from lib/ESP Async WebServer/src/WebAuthentication.cpp rename to lib/ESPAsyncWebServer/src/WebAuthentication.cpp diff --git a/lib/ESP Async WebServer/src/WebAuthentication.h b/lib/ESPAsyncWebServer/src/WebAuthentication.h similarity index 100% rename from lib/ESP Async WebServer/src/WebAuthentication.h rename to lib/ESPAsyncWebServer/src/WebAuthentication.h diff --git a/lib/ESP Async WebServer/src/WebHandlerImpl.h b/lib/ESPAsyncWebServer/src/WebHandlerImpl.h similarity index 100% rename from lib/ESP Async WebServer/src/WebHandlerImpl.h rename to lib/ESPAsyncWebServer/src/WebHandlerImpl.h diff --git a/lib/ESP Async WebServer/src/WebHandlers.cpp b/lib/ESPAsyncWebServer/src/WebHandlers.cpp similarity index 96% rename from lib/ESP Async WebServer/src/WebHandlers.cpp rename to lib/ESPAsyncWebServer/src/WebHandlers.cpp index 580ceba..d904a39 100644 --- a/lib/ESP Async WebServer/src/WebHandlers.cpp +++ b/lib/ESPAsyncWebServer/src/WebHandlers.cpp @@ -231,16 +231,16 @@ void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* request) { } else if (_cache_control.length() && request->hasHeader(T_INM) && request->header(T_INM).equals(etag)) { request->_tempFile.close(); AsyncWebServerResponse* response = new AsyncBasicResponse(304); // Not modified - response->addHeader(T_Cache_Control, _cache_control); - response->addHeader(T_ETag, etag); + response->addHeader(T_Cache_Control, _cache_control.c_str()); + response->addHeader(T_ETag, etag.c_str()); request->send(response); } else { AsyncWebServerResponse* response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback); if (_last_modified.length()) - response->addHeader(T_Last_Modified, _last_modified); + response->addHeader(T_Last_Modified, _last_modified.c_str()); if (_cache_control.length()) { - response->addHeader(T_Cache_Control, _cache_control); - response->addHeader(T_ETag, etag); + response->addHeader(T_Cache_Control, _cache_control.c_str()); + response->addHeader(T_ETag, etag.c_str()); } request->send(response); } diff --git a/lib/ESP Async WebServer/src/WebRequest.cpp b/lib/ESPAsyncWebServer/src/WebRequest.cpp similarity index 90% rename from lib/ESP Async WebServer/src/WebRequest.cpp rename to lib/ESPAsyncWebServer/src/WebRequest.cpp index f0d6bdf..95fda7e 100644 --- a/lib/ESP Async WebServer/src/WebRequest.cpp +++ b/lib/ESPAsyncWebServer/src/WebRequest.cpp @@ -274,7 +274,7 @@ bool AsyncWebServerRequest::_parseReqHead() { if (!_temp.startsWith(T_HTTP_1_0)) _version = 1; - _temp = String(); + _temp = emptyString; return true; } @@ -307,7 +307,7 @@ bool AsyncWebServerRequest::_parseReqHeader() { if (name.equalsIgnoreCase(T_UPGRADE) && value.equalsIgnoreCase(T_WS)) { // WebSocket request can be uniquely identified by header: [Upgrade: websocket] _reqconntype = RCT_WS; - } else if (name.equalsIgnoreCase(T_ACCEPT)){ + } else if (name.equalsIgnoreCase(T_ACCEPT)) { String lowcase(value); lowcase.toLowerCase(); #ifndef ESP8266 @@ -327,7 +327,7 @@ bool AsyncWebServerRequest::_parseReqHeader() { _temp.clear(); #else // Ancient PRI core does not have String::clear() method 8-() - _temp = String(); + _temp = emptyString; #endif return true; } @@ -345,10 +345,10 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) { _params.emplace_back(urlDecode(name), urlDecode(value), true); #ifndef TARGET_RP2040 - _temp.clear(); + _temp.clear(); #else - // Ancient PRI core does not have String::clear() method 8-() - _temp = String(); + // Ancient PRI core does not have String::clear() method 8-() + _temp = emptyString; #endif } } @@ -390,10 +390,10 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) { if (!_parsedLength) { _multiParseState = EXPECT_BOUNDARY; - _temp = String(); - _itemName = String(); - _itemFilename = String(); - _itemType = String(); + _temp = emptyString; + _itemName = emptyString; + _itemFilename = emptyString; + _itemType = emptyString; } if (_multiParseState == WAIT_FOR_RETURN1) { @@ -450,13 +450,13 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) { _itemIsFile = true; } } - _temp = String(); + _temp = emptyString; } else { _multiParseState = WAIT_FOR_RETURN1; // value starts from here _itemSize = 0; _itemStartIndex = _parsedLength; - _itemValue = String(); + _itemValue = emptyString; if (_itemIsFile) { if (_itemBuffer) free(_itemBuffer); @@ -654,9 +654,9 @@ size_t AsyncWebServerRequest::params() const { return _params.size(); } -bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const { +bool AsyncWebServerRequest::hasParam(const char* name, bool post, bool file) const { for (const auto& p : _params) { - if (p.name() == name && p.isPost() == post && p.isFile() == file) { + if (p.name().equals(name) && p.isPost() == post && p.isFile() == file) { return true; } } @@ -689,48 +689,52 @@ void AsyncWebServerRequest::addInterestingHeader(const char* name) { _interestingHeaders.emplace_back(name); } -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content) { +AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const char* contentType, const char* content, AwsTemplateProcessor callback) { + if (callback) + return new AsyncProgmemResponse(code, contentType, (const uint8_t*)content, strlen(content), callback); return new AsyncBasicResponse(code, contentType, content); } -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) { +AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) { return new AsyncProgmemResponse(code, contentType, content, len, callback); } -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback) { - return new AsyncProgmemResponse(code, contentType, (const uint8_t*)content, strlen_P(content), callback); -} - -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(FS& fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) { +AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) { if (fs.exists(path) || (!download && fs.exists(path + T__gz))) return new AsyncFileResponse(fs, path, contentType, download, callback); return NULL; } -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) { +AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) { if (content == true) return new AsyncFileResponse(content, path, contentType, download, callback); return NULL; } -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback) { +AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback) { return new AsyncStreamResponse(stream, contentType, len, callback); } -AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) { +AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) { return new AsyncCallbackResponse(contentType, len, callback, templateCallback); } -AsyncWebServerResponse* AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) { +AsyncWebServerResponse* AsyncWebServerRequest::beginChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) { if (_version) return new AsyncChunkedResponse(contentType, callback, templateCallback); return new AsyncCallbackResponse(contentType, 0, callback, templateCallback); } -AsyncResponseStream* AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize) { +AsyncResponseStream* AsyncWebServerRequest::beginResponseStream(const char* contentType, size_t bufferSize) { return new AsyncResponseStream(contentType, bufferSize); } +#ifdef ESP8266 +AsyncWebServerResponse* AsyncWebServerRequest::beginResponse(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback) { + return new AsyncProgmemResponse(code, contentType, (const uint8_t*)content, strlen_P(content), callback); +} +#endif + void AsyncWebServerRequest::send(AsyncWebServerResponse* response) { _response = response; if (_response == NULL) { @@ -748,44 +752,6 @@ void AsyncWebServerRequest::send(AsyncWebServerResponse* response) { } } -void AsyncWebServerRequest::send(int code, const String& contentType, const String& content) { - send(beginResponse(code, contentType, content)); -} - -void AsyncWebServerRequest::send(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) { - send(beginResponse(code, contentType, content, len, callback)); -} - -void AsyncWebServerRequest::send(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback) { - send(beginResponse(code, contentType, content, callback)); -} - -void AsyncWebServerRequest::send(FS& fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) { - if (fs.exists(path) || (!download && fs.exists(path + T__gz))) { - send(beginResponse(fs, path, contentType, download, callback)); - } else - send(404); -} - -void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) { - if (content == true) { - send(beginResponse(content, path, contentType, download, callback)); - } else - send(404); -} - -void AsyncWebServerRequest::send(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback) { - send(beginResponse(stream, contentType, len, callback)); -} - -void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) { - send(beginResponse(contentType, len, callback, templateCallback)); -} - -void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) { - send(beginChunkedResponse(contentType, callback, templateCallback)); -} - void AsyncWebServerRequest::redirect(const char* url) { AsyncWebServerResponse* response = beginResponse(302); response->addHeader(T_LOCATION, url); @@ -834,11 +800,11 @@ void AsyncWebServerRequest::requestAuthentication(const char* realm, bool isDige String header(T_BASIC_REALM); header.concat(realm); header += '"'; - r->addHeader(T_WWW_AUTH, header); + r->addHeader(T_WWW_AUTH, header.c_str()); } else { String header(T_DIGEST_); header.concat(requestDigestAuthentication(realm)); - r->addHeader(T_WWW_AUTH, header); + r->addHeader(T_WWW_AUTH, header.c_str()); } send(r); } @@ -949,7 +915,7 @@ const char* AsyncWebServerRequest::methodToString() const { return T_OPTIONS; return T_UNKNOWN; } -#else // ESP8266 +#else // ESP8266 const __FlashStringHelper* AsyncWebServerRequest::methodToString() const { if (_method == HTTP_ANY) return FPSTR(T_ANY); @@ -988,7 +954,7 @@ const char* AsyncWebServerRequest::requestedConnTypeToString() const { return T_ERROR; } } -#else // ESP8266 +#else // ESP8266 const __FlashStringHelper* AsyncWebServerRequest::requestedConnTypeToString() const { switch (_reqconntype) { case RCT_NOT_USED: diff --git a/lib/ESP Async WebServer/src/WebResponseImpl.h b/lib/ESPAsyncWebServer/src/WebResponseImpl.h similarity index 71% rename from lib/ESP Async WebServer/src/WebResponseImpl.h rename to lib/ESPAsyncWebServer/src/WebResponseImpl.h index 26ec223..a6f71bb 100644 --- a/lib/ESP Async WebServer/src/WebResponseImpl.h +++ b/lib/ESPAsyncWebServer/src/WebResponseImpl.h @@ -28,6 +28,7 @@ #endif #include #include +#include "literals.h" // It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. @@ -36,7 +37,8 @@ class AsyncBasicResponse : public AsyncWebServerResponse { String _content; public: - AsyncBasicResponse(int code, const String& contentType = String(), const String& content = String()); + explicit AsyncBasicResponse(int code, const char* contentType = asyncsrv::empty, const char* content = asyncsrv::empty); + AsyncBasicResponse(int code, const String& contentType, const String& content = emptyString) : AsyncBasicResponse(code, contentType.c_str(), content.c_str()) {} void _respond(AsyncWebServerRequest* request); size_t _ack(AsyncWebServerRequest* request, size_t len, uint32_t time); bool _sourceValid() const { return true; } @@ -76,11 +78,13 @@ class AsyncFileResponse : public AsyncAbstractResponse { private: File _content; String _path; - void _setContentType(const String& path); + void _setContentTypeFromPath(const String& path); public: - AsyncFileResponse(FS& fs, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr); - AsyncFileResponse(File content, const String& path, const String& contentType = String(), bool download = false, AwsTemplateProcessor callback = nullptr); + AsyncFileResponse(FS& fs, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); + AsyncFileResponse(FS& fs, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callback = nullptr) : AsyncFileResponse(fs, path, contentType.c_str(), download, callback) {} + AsyncFileResponse(File content, const String& path, const char* contentType = asyncsrv::empty, bool download = false, AwsTemplateProcessor callback = nullptr); + AsyncFileResponse(File content, const String& path, const String& contentType, bool download = false, AwsTemplateProcessor callack = nullptr) : AsyncFileResponse(content, path, contentType.c_str(), download, callack) {} ~AsyncFileResponse(); bool _sourceValid() const { return !!(_content); } virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override; @@ -91,7 +95,8 @@ class AsyncStreamResponse : public AsyncAbstractResponse { Stream* _content; public: - AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr); + AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback = nullptr); + AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncStreamResponse(stream, contentType.c_str(), len, callback) {} bool _sourceValid() const { return !!(_content); } virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override; }; @@ -102,7 +107,8 @@ class AsyncCallbackResponse : public AsyncAbstractResponse { size_t _filledLength; public: - AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); + AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); + AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncCallbackResponse(contentType.c_str(), len, callback, templateCallback) {} bool _sourceValid() const { return !!(_content); } virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override; }; @@ -113,7 +119,8 @@ class AsyncChunkedResponse : public AsyncAbstractResponse { size_t _filledLength; public: - AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); + AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr); + AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback = nullptr) : AsyncChunkedResponse(contentType.c_str(), callback, templateCallback) {} bool _sourceValid() const { return !!(_content); } virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override; }; @@ -124,7 +131,8 @@ class AsyncProgmemResponse : public AsyncAbstractResponse { size_t _readLength; public: - AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr); + AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr); + AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback = nullptr) : AsyncProgmemResponse(code, contentType.c_str(), content, len, callback) {} bool _sourceValid() const { return true; } virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override; }; @@ -136,7 +144,8 @@ class AsyncResponseStream : public AsyncAbstractResponse, public Print { std::unique_ptr _content; public: - AsyncResponseStream(const String& contentType, size_t bufferSize); + AsyncResponseStream(const char* contentType, size_t bufferSize); + AsyncResponseStream(const String& contentType, size_t bufferSize) : AsyncResponseStream(contentType.c_str(), bufferSize) {} ~AsyncResponseStream(); bool _sourceValid() const { return (_state < RESPONSE_END); } virtual size_t _fillBuffer(uint8_t* buf, size_t maxLen) override; diff --git a/lib/ESP Async WebServer/src/WebResponses.cpp b/lib/ESPAsyncWebServer/src/WebResponses.cpp similarity index 94% rename from lib/ESP Async WebServer/src/WebResponses.cpp rename to lib/ESPAsyncWebServer/src/WebResponses.cpp index 86aa947..f1994b1 100644 --- a/lib/ESP Async WebServer/src/WebResponses.cpp +++ b/lib/ESPAsyncWebServer/src/WebResponses.cpp @@ -234,12 +234,12 @@ void AsyncWebServerResponse::setContentLength(size_t len) { _contentLength = len; } -void AsyncWebServerResponse::setContentType(const String& type) { +void AsyncWebServerResponse::setContentType(const char* type) { if (_state == RESPONSE_SETUP) _contentType = type; } -void AsyncWebServerResponse::addHeader(const String& name, const String& value) { +void AsyncWebServerResponse::addHeader(const char* name, const char* value) { _headers.emplace_back(name, value); } @@ -298,7 +298,7 @@ size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest* request, size_t len, /* * String/Code Response * */ -AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content) { +AsyncBasicResponse::AsyncBasicResponse(int code, const char* contentType, const char* content) { _code = code; _content = content; _contentType = contentType; @@ -353,7 +353,7 @@ size_t AsyncBasicResponse::_ack(AsyncWebServerRequest* request, size_t len, uint // we can fit in this packet if (space > available) { _writtenLength += request->client()->write(_content.c_str(), available); - _content = String(); + _content = emptyString; _state = RESPONSE_WAIT_ACK; return available; } @@ -465,7 +465,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest* request, size_t len, u } if (headLen) { - _head = String(); + _head = emptyString; } if (outLen) { @@ -607,7 +607,7 @@ AsyncFileResponse::~AsyncFileResponse() { _content.close(); } -void AsyncFileResponse::_setContentType(const String& path) { +void AsyncFileResponse::_setContentTypeFromPath(const String& path) { #if HAVE_EXTERN_GET_Content_Type_FUNCTION #ifndef ESP8266 extern const char* getContentType(const String& path); @@ -657,7 +657,7 @@ void AsyncFileResponse::_setContentType(const String& path) { #endif } -AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { +AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { _code = 200; _path = path; @@ -672,8 +672,8 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const String& c _content = fs.open(_path, fs::FileOpenMode::read); _contentLength = _content.size(); - if (contentType.length() == 0) - _setContentType(path); + if (strlen(contentType) == 0) + _setContentTypeFromPath(path); else _contentType = contentType; @@ -691,7 +691,7 @@ AsyncFileResponse::AsyncFileResponse(FS& fs, const String& path, const String& c addHeader(T_Content_Disposition, buf); } -AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { +AsyncFileResponse::AsyncFileResponse(File content, const String& path, const char* contentType, bool download, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { _code = 200; _path = path; @@ -705,8 +705,8 @@ AsyncFileResponse::AsyncFileResponse(File content, const String& path, const Str _content = content; _contentLength = _content.size(); - if (contentType.length() == 0) - _setContentType(path); + if (strlen(contentType) == 0) + _setContentTypeFromPath(path); else _contentType = contentType; @@ -730,7 +730,7 @@ size_t AsyncFileResponse::_fillBuffer(uint8_t* data, size_t len) { * Stream Response * */ -AsyncStreamResponse::AsyncStreamResponse(Stream& stream, const String& contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { +AsyncStreamResponse::AsyncStreamResponse(Stream& stream, const char* contentType, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { _code = 200; _content = &stream; _contentLength = len; @@ -750,7 +750,7 @@ size_t AsyncStreamResponse::_fillBuffer(uint8_t* data, size_t len) { * Callback Response * */ -AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) : AsyncAbstractResponse(templateCallback) { +AsyncCallbackResponse::AsyncCallbackResponse(const char* contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback) : AsyncAbstractResponse(templateCallback) { _code = 200; _content = callback; _contentLength = len; @@ -772,7 +772,7 @@ size_t AsyncCallbackResponse::_fillBuffer(uint8_t* data, size_t len) { * Chunked Response * */ -AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) : AsyncAbstractResponse(processorCallback) { +AsyncChunkedResponse::AsyncChunkedResponse(const char* contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback) : AsyncAbstractResponse(processorCallback) { _code = 200; _content = callback; _contentLength = 0; @@ -794,7 +794,7 @@ size_t AsyncChunkedResponse::_fillBuffer(uint8_t* data, size_t len) { * Progmem Response * */ -AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { +AsyncProgmemResponse::AsyncProgmemResponse(int code, const char* contentType, const uint8_t* content, size_t len, AwsTemplateProcessor callback) : AsyncAbstractResponse(callback) { _code = code; _content = content; _contentType = contentType; @@ -818,7 +818,7 @@ size_t AsyncProgmemResponse::_fillBuffer(uint8_t* data, size_t len) { * Response Stream (You can print/write/printf to it, up to the contentLen bytes) * */ -AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize) { +AsyncResponseStream::AsyncResponseStream(const char* contentType, size_t bufferSize) { _code = 200; _contentLength = 0; _contentType = contentType; diff --git a/lib/ESP Async WebServer/src/WebServer.cpp b/lib/ESPAsyncWebServer/src/WebServer.cpp similarity index 97% rename from lib/ESP Async WebServer/src/WebServer.cpp rename to lib/ESPAsyncWebServer/src/WebServer.cpp index 0e29a54..d7c9a02 100644 --- a/lib/ESP Async WebServer/src/WebServer.cpp +++ b/lib/ESPAsyncWebServer/src/WebServer.cpp @@ -24,11 +24,19 @@ using namespace asyncsrv; bool ON_STA_FILTER(AsyncWebServerRequest* request) { + #ifndef CONFIG_IDF_TARGET_ESP32H2 return WiFi.localIP() == request->client()->localIP(); + #else + return false; + #endif } bool ON_AP_FILTER(AsyncWebServerRequest* request) { + #ifndef CONFIG_IDF_TARGET_ESP32H2 return WiFi.localIP() != request->client()->localIP(); + #else + return false; + #endif } #ifndef HAVE_FS_FILE_OPEN_MODE diff --git a/lib/ESP Async WebServer/src/literals.h b/lib/ESPAsyncWebServer/src/literals.h similarity index 99% rename from lib/ESP Async WebServer/src/literals.h rename to lib/ESPAsyncWebServer/src/literals.h index c60a3eb..8a7293b 100644 --- a/lib/ESP Async WebServer/src/literals.h +++ b/lib/ESPAsyncWebServer/src/literals.h @@ -2,6 +2,8 @@ namespace asyncsrv { +static constexpr const char* empty = ""; + #ifndef ESP8622 static constexpr const char* T_100_CONTINUE = "100-continue"; static constexpr const char* T_ACCEPT = "Accept"; diff --git a/lib/ESP Async WebServer/src/port/SHA1Builder.cpp b/lib/ESPAsyncWebServer/src/port/SHA1Builder.cpp similarity index 100% rename from lib/ESP Async WebServer/src/port/SHA1Builder.cpp rename to lib/ESPAsyncWebServer/src/port/SHA1Builder.cpp diff --git a/lib/ESP Async WebServer/src/port/SHA1Builder.h b/lib/ESPAsyncWebServer/src/port/SHA1Builder.h similarity index 100% rename from lib/ESP Async WebServer/src/port/SHA1Builder.h rename to lib/ESPAsyncWebServer/src/port/SHA1Builder.h diff --git a/lib/Ethernet/.codespellrc b/lib/Ethernet/.codespellrc deleted file mode 100644 index a231f49..0000000 --- a/lib/Ethernet/.codespellrc +++ /dev/null @@ -1,7 +0,0 @@ -# See: https://github.com/codespell-project/codespell#using-a-config-file -[codespell] -# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: -ignore-words-list = nd, -check-filenames = -check-hidden = -skip = ./.git diff --git a/lib/Ethernet/AUTHORS b/lib/Ethernet/AUTHORS deleted file mode 100644 index 1faeec4..0000000 --- a/lib/Ethernet/AUTHORS +++ /dev/null @@ -1,36 +0,0 @@ -Alberto Panu https://github.com/bigjohnson -Alasdair Allan https://github.com/aallan -Alice Pintus https://github.com/00alis -Adrian McEwen https://github.com/amcewen -Arduino LLC https://arduino.cc/ -Arnie97 https://github.com/Arnie97 -Arturo Guadalupi https://github.com/agdl -Bjoern Hartmann https://people.eecs.berkeley.edu/~bjoern/ -chaveiro https://github.com/chaveiro -Cristian Maglie https://github.com/cmaglie -David A. Mellis https://github.com/damellis -Dino Tinitigan https://github.com/bigdinotech -Eddy https://github.com/eddyst -Federico Vanzati https://github.com/Fede85 -Federico Fissore https://github.com/ffissore -Jack Christensen https://github.com/JChristensen -Johann Richard https://github.com/johannrichard -Jordan Terrell https://github.com/iSynaptic -Justin Paulin https://github.com/interwho -lathoub https://github.com/lathoub -Martino Facchin https://github.com/facchinm -Matthias Hertel https://github.com/mathertel -Matthijs Kooijman https://github.com/matthijskooijman -Matt Robinson https://github.com/ribbons -MCQN Ltd. http://mcqn.com/ -Michael Amie https://github.com/michaelamie -Michael Margolis https://github.com/michaelmargolis -Norbert Truchsess https://github.com/ntruchsess -Paul Stoffregen https://github.com/PaulStoffregen -per1234 https://github.com/per1234 -Richard Sim -Scott Fitzgerald https://github.com/shfitz -Thibaut Viard https://github.com/aethaniel -Tom Igoe https://github.com/tigoe -WIZnet http://www.wiznet.co.kr -Zach Eveland https://github.com/zeveland diff --git a/lib/Ethernet/README.adoc b/lib/Ethernet/README.adoc deleted file mode 100644 index ed937b1..0000000 --- a/lib/Ethernet/README.adoc +++ /dev/null @@ -1,31 +0,0 @@ -:repository-owner: arduino-libraries -:repository-name: Ethernet - -= {repository-name} Library for Arduino = - -image:https://github.com/{repository-owner}/{repository-name}/actions/workflows/check-arduino.yml/badge.svg["Check Arduino status", link="https://github.com/{repository-owner}/{repository-name}/actions/workflows/check-arduino.yml"] -image:https://github.com/{repository-owner}/{repository-name}/actions/workflows/compile-examples.yml/badge.svg["Compile Examples status", link="https://github.com/{repository-owner}/{repository-name}/actions/workflows/compile-examples.yml"] -image:https://github.com/{repository-owner}/{repository-name}/actions/workflows/spell-check.yml/badge.svg["Spell Check status", link="https://github.com/{repository-owner}/{repository-name}/actions/workflows/spell-check.yml"] - -With the Arduino Ethernet Shield, this library allows an Arduino board to connect to the internet. - -For more information about this library please visit us at -https://www.arduino.cc/en/Reference/{repository-name} - -== License == - -Copyright (c) 2010 Arduino LLC. All right 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 diff --git a/lib/Ethernet/docs/api.md b/lib/Ethernet/docs/api.md deleted file mode 100644 index 76265c8..0000000 --- a/lib/Ethernet/docs/api.md +++ /dev/null @@ -1,2607 +0,0 @@ -# Ethernet Library - -## Ethernet Class - -### `Ethernet.begin()` - -#### Description - -Initializes the Ethernet library and network settings. - -With version 1.0, the library supports DHCP. Using Ethernet.begin(mac) with the proper network setup, the Ethernet shield will automatically obtain an IP address. This increases the sketch size significantly. To make sure the DHCP lease is properly renewed when needed, be sure to call Ethernet.maintain() regularly. - - -#### Syntax - -``` -Ethernet.begin(mac); -Ethernet.begin(mac, ip); -Ethernet.begin(mac, ip, dns); -Ethernet.begin(mac, ip, dns, gateway); -Ethernet.begin(mac, ip, dns, gateway, subnet); -``` - -#### Parameters -- mac: the MAC (Media access control) address for the device (array of 6 bytes). this is the Ethernet hardware address of your shield. Newer Arduino Ethernet Shields include a sticker with the device's MAC address. For older shields, choose your own. -- ip: the IP address of the device (array of 4 bytes) -- dns: the IP address of the DNS server (array of 4 bytes). optional: defaults to the device IP address with the last octet set to 1 -- gateway: the IP address of the network gateway (array of 4 bytes). optional: defaults to the device IP address with the last octet set to 1 -- subnet: the subnet mask of the network (array of 4 bytes). optional: defaults to 255.255.255.0 - -#### Returns -- The DHCP version of this function, Ethernet.begin(mac), returns an int: 1 on a successful DHCP connection, 0 on failure. -- The other versions don't return anything. - -#### Example - -``` -#include -#include - -// the media access control (ethernet hardware) address for the shield: -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -//the IP address for the shield: -byte ip[] = { 10, 0, 0, 177 }; - -void setup() -{ - Ethernet.begin(mac, ip); -} - -void loop () {} -``` - -### `Ethernet.dnsServerIP()` - -#### Description - -Returns the DNS server IP address for the device. - - -#### Syntax - -``` -Ethernet.dnsServerIP() - -``` - -#### Parameters -none - -#### Returns -- the DNS server IP address for the device (IPAddress). - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -void setup() { - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - Ethernet.begin(mac, ip); - - Serial.print("The DNS server IP address is: "); - Serial.println(Ethernet.dnsServerIP()); -} - -void loop () {} -``` - -### `Ethernet.gatewayIP()` - -#### Description - -Returns the gateway IP address for the device. - - -#### Syntax - -``` -Ethernet.gatewayIP() - -``` - -#### Parameters -none - -#### Returns -- the gateway IP address for the device (IPAddress). - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -void setup() { - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - Ethernet.begin(mac, ip); - - Serial.print("The gateway IP address is: "); - Serial.println(Ethernet.gatewayIP()); -} - -void loop () {} -``` - -### `Ethernet.hardwareStatus()` - -#### Description - -Ethernet.hardwareStatus() tells you which WIZnet Ethernet controller chip was detected during Ethernet.begin(), if any. This can be used for troubleshooting. If no Ethernet controller was detected then there is likely a hardware problem. - - -#### Syntax - -``` -Ethernet.hardwareStatus() - -``` - -#### Parameters -none - -#### Returns -- which WIZnet Ethernet controller chip was detected during Ethernet.begin() (EthernetHardwareStatus): - -``` -EthernetNoHardware -EthernetW5100 -EthernetW5200 -EthernetW5500 -``` - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -void setup() { - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - Ethernet.begin(mac, ip); - - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found."); - } - else if (Ethernet.hardwareStatus() == EthernetW5100) { - Serial.println("W5100 Ethernet controller detected."); - } - else if (Ethernet.hardwareStatus() == EthernetW5200) { - Serial.println("W5200 Ethernet controller detected."); - } - else if (Ethernet.hardwareStatus() == EthernetW5500) { - Serial.println("W5500 Ethernet controller detected."); - } -} - -void loop () {} -``` - -### `Ethernet.init()` - -#### Description - -Used to configure the CS (chip select) pin for the Ethernet controller chip. The Ethernet library has a default CS pin, which is usually correct, but with some non-standard Ethernet hardware you might need to use a different CS pin. - - -#### Syntax - -``` -Ethernet.init(sspin) - -``` - -#### Parameters -- sspin: the pin number to use for CS (byte) - -#### Returns -Nothing - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -void setup() { - Ethernet.init(53); // use pin 53 for Ethernet CS - Ethernet.begin(mac, ip); -} - -void loop () {} -``` - -### `Ethernet.linkStatus()` - -#### Description - -Tells you whether the link is active. LinkOFF could indicate the Ethernet cable is unplugged or defective. This feature is only available when using the W5200 and W5500 Ethernet controller chips. - - -#### Syntax - -``` -Ethernet.linkStatus() - -``` - -#### Parameters -none - -#### Returns -- the link status (EthernetLinkStatus): - -- Unknown - -- LinkON - -- LinkOFF - -#### Example - -``` -#include -#include - -void setup() { - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } -} - -void loop () { - if (Ethernet.linkStatus() == Unknown) { - Serial.println("Link status unknown. Link status detection is only available with W5200 and W5500."); - } - else if (Ethernet.linkStatus() == LinkON) { - Serial.println("Link status: On"); - } - else if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Link status: Off"); - } -} -``` - -### `Ethernet.localIP()` - -#### Description - -Obtains the IP address of the Ethernet shield. Useful when the address is auto assigned through DHCP. - - -#### Syntax - -``` -Ethernet.localIP(); - -``` - -#### Parameters -none - -#### Returns -- the IP address - -#### Example - -``` -#include -#include - -// Enter a MAC address for your controller below. -// Newer Ethernet shields have a MAC address printed on a sticker on the shield -byte mac[] = { - 0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02 }; - -// Initialize the Ethernet client library -// with the IP address and port of the server -// that you want to connect to (port 80 is default for HTTP): -EthernetClient client; - -void setup() { - // start the serial library: - Serial.begin(9600); - // start the Ethernet connection: - if (Ethernet.begin(mac) == 0) { - Serial.println("Failed to configure Ethernet using DHCP"); - // no point in carrying on, so do nothing forevermore: - for(;;) - ; - } - // print your local IP address: - Serial.println(Ethernet.localIP()); - -} - -void loop() { - -} -``` - -### `Ethernet.MACAddress()` - -#### Description - -Fills the supplied buffer with the MAC address of the device. - - -#### Syntax - -``` -Ethernet.MACAddress(mac_address) - -``` - -#### Parameters -- mac_address: buffer to receive the MAC address (array of 6 bytes) - -#### Returns -Nothing - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -void setup() { - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - Ethernet.begin(mac, ip); - - byte macBuffer[6]; // create a buffer to hold the MAC address - Ethernet.MACAddress(macBuffer); // fill the buffer - Serial.print("The MAC address is: "); - for (byte octet = 0; octet < 6; octet++) { - Serial.print(macBuffer[octet], HEX); - if (octet < 5) { - Serial.print('-'); - } - } -} - -void loop () {} - -``` - -### `Ethernet.maintain()` - -#### Description - -Allows for the renewal of DHCP leases. When assigned an IP address via DHCP, ethernet devices are given a lease on the address for an amount of time. With Ethernet.maintain(), it is possible to request a renewal from the DHCP server. Depending on the server's configuration, you may receive the same address, a new one, or none at all. - -You can call this function as often as you want, it will only re-request a DHCP lease when needed (returning 0 in all other cases). The easiest way is to just call it on every loop() invocation, but less often is also fine. Not calling this function (or calling it significantly less then once per second) will prevent the lease to be renewed when the DHCP protocol requires this, continuing to use the expired lease instead (which will not directly break connectivity, but if the DHCP server leases the same address to someone else, things will likely break). - -Ethernet.maintain() was added to Arduino 1.0.1. - - -#### Syntax - -``` -Ethernet.maintain(); - -``` - -#### Parameters -none - -#### Returns - -byte: - -- 0: nothing happened - -- 1: renew failed - -- 2: renew success - -- 3: rebind fail - -- 4: rebind success - -### `Ethernet.setDnsServerIP()` - -#### Description - -Set the IP address of the DNS server. Not for use with DHCP. - - -#### Syntax - -``` -Ethernet.setDnsServerIP(dns_server) - -``` - -#### Parameters -- dns_server: the IP address of the DNS server (IPAddress) - -#### Returns -Nothing - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); -IPAddress myDns(192, 168, 1, 1); - -void setup() { - Ethernet.begin(mac, ip, myDns); - IPAddress newDns(192, 168, 1, 1); - Ethernet.setDnsServerIP(newDns); // change the DNS server IP address -} - -void loop () {} -``` - -### `Ethernet.setGatewayIP()` - -#### Description - -Set the IP address of the network gateway. Not for use with DHCP. - - -#### Syntax - -``` -Ethernet.setGatewayIP(gateway) - -``` - -#### Parameters -- gateway: the IP address of the network gateway (IPAddress) - -#### Returns -Nothing - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); -IPAddress myDns(192, 168, 1, 1); -IPAddress gateway(192, 168, 1, 1); - -void setup() { - Ethernet.begin(mac, ip, myDns, gateway); - IPAddress newGateway(192, 168, 100, 1); - Ethernet.setGatewayIP(newGateway); // change the gateway IP address -} - -void loop () {} -``` - -### `Ethernet.setLocalIP()` - -#### Description - -Set the IP address of the device. Not for use with DHCP. - - -#### Syntax - -``` -Ethernet.setLocalIP(local_ip) - -``` - -#### Parameters -- local_ip: the IP address to use (IPAddress) - -#### Returns -Nothing - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -void setup() { - Ethernet.begin(mac, ip); - IPAddress newIp(10, 0, 0, 178); - Ethernet.setLocalIP(newIp); // change the IP address -} - -void loop () {} -``` - -### `Ethernet.setMACAddress()` - -#### Description - -Set the MAC address. Not for use with DHCP. - - -#### Syntax - -``` -Ethernet.setMACAddress(mac) - -``` - -#### Parameters -- mac: the MAC address to use (array of 6 bytes) - -#### Returns -Nothing - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -void setup() { - Ethernet.begin(mac, ip); - byte newMac[] = {0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02}; - Ethernet.setMACAddress(newMac); // change the MAC address -} - -void loop () {} -``` - -### `Ethernet.setRetransmissionCount()` - -#### Description - -Set the number of transmission attempts the Ethernet controller will make before giving up. The initial value is 8. 8 transmission attempts times the 200 ms default timeout equals a blocking delay of 1600 ms during a communications failure. You might prefer to set a lower number to make your program more responsive in the event something goes wrong with communications. Despite the name, this sets the total number of transmission attempts (not the number of retries after the first attempt fails) so the minimum value you would ever want to set is 1. - - -#### Syntax - -``` -Ethernet.setRetransmissionCount(number) - -``` - -#### Parameters -- number: number of transmission attempts the Ethernet controller should make before giving up (byte) - -#### Returns -Nothing - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -void setup() { - Ethernet.begin(mac, ip); - Ethernet.setRetransmissionCount(1); // configure the Ethernet controller to only attempt one transmission before giving up -} - -void loop () {} -``` - -### `Ethernet.setRetransmissionTimeout()` - -#### Description - -Set the Ethernet controller's timeout. The initial value is 200 ms. A 200 ms timeout times the default of 8 attempts equals a blocking delay of 1600 ms during a communications failure. You might prefer to set a shorter timeout to make your program more responsive in the event something goes wrong with communications. You will need to do some experimentation to determine an appropriate value for your specific application. - - -#### Syntax - -``` -Ethernet.setRetransmissionTimeout(milliseconds) - -``` - -#### Parameters -- milliseconds: the timeout duration (uint16_t) - -#### Returns -Nothing - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -void setup() { - Ethernet.begin(mac, ip); - Ethernet.setRetransmissionTimeout(50); // set the Ethernet controller's timeout to 50 ms -} - -void loop () {} -``` - -### `Ethernet.setSubnetMask()` - -#### Description - -Set the subnet mask of the network. Not for use with DHCP. - - -#### Syntax - -``` -Ethernet.setSubnetMask(subnet) - -``` - -#### Parameters -- subnet: the subnet mask of the network (IPAddress) - -#### Returns -Nothing - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); -IPAddress myDns(192, 168, 1, 1); -IPAddress gateway(192, 168, 1, 1); -IPAddress subnet(255, 255, 0, 0); - -void setup() { - Ethernet.begin(mac, ip, myDns, gateway, subnet); - IPAddress newSubnet(255, 255, 255, 0); - Ethernet.setSubnetMask(newSubnet); // change the subnet mask -} - -void loop () {} -``` - -### `Ethernet.subnetMask()` - -#### Description - -Returns the subnet mask of the device. - - -#### Syntax - -``` -Ethernet.subnetMask() - -``` - -#### Parameters -none - -#### Returns -- the subnet mask of the device (IPAddress) - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -void setup() { - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - Ethernet.begin(mac, ip); - - Serial.print("The subnet mask is: "); - Serial.println(Ethernet.subnetMask()); -} - -void loop () {} -``` - -## IPAddress Class - -### `IPAddress()` - -#### Description - -Defines an IP address. It can be used to declare both local and remote addresses. - - -#### Syntax - -``` -IPAddress(address); - -``` - -#### Parameters -- address: a comma delimited list representing the address (4 bytes, ex. 192, 168, 1, 1) - -#### Returns -None - -#### Example - -``` -#include -#include - -// network configuration. dns server, gateway and subnet are optional. - - // the media access control (ethernet hardware) address for the shield: -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; - -// the dns server ip -IPAddress dnServer(192, 168, 0, 1); -// the router's gateway address: -IPAddress gateway(192, 168, 0, 1); -// the subnet: -IPAddress subnet(255, 255, 255, 0); - -//the IP address is dependent on your network -IPAddress ip(192, 168, 0, 2); - -void setup() { - Serial.begin(9600); - - // initialize the ethernet device - Ethernet.begin(mac, ip, dnServer, gateway, subnet); - //print out the IP address - Serial.print("IP = "); - Serial.println(Ethernet.localIP()); -} - -void loop() { -} -``` - -## Server Class - -### `Server` - -#### Description -Server is the base class for all Ethernet server based calls. It is not called directly, but invoked whenever you use a function that relies on it. - -### `EthernetServer()` - -#### Description - -Create a server that listens for incoming connections on the specified port. - - -#### Syntax - -``` -Server(port); - -``` - -#### Parameters -- port: the port to listen on (int) - -#### Returns -None - -#### Example - -``` -#include -#include - -// network configuration. gateway and subnet are optional. - - // the media access control (ethernet hardware) address for the shield: -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -//the IP address for the shield: -byte ip[] = { 10, 0, 0, 177 }; -// the router's gateway address: -byte gateway[] = { 10, 0, 0, 1 }; -// the subnet: -byte subnet[] = { 255, 255, 0, 0 }; - -// telnet defaults to port 23 -EthernetServer server = EthernetServer(23); - -void setup() -{ - // initialize the ethernet device - Ethernet.begin(mac, ip, gateway, subnet); - - // start listening for clients - server.begin(); -} - -void loop() -{ - // if an incoming client connects, there will be bytes available to read: - EthernetClient client = server.available(); - if (client == true) { - // read bytes from the incoming client and write them back - // to any clients connected to the server: - server.write(client.read()); - } -} -``` - -### `server.begin()` - -#### Description - -Tells the server to begin listening for incoming connections. - - -#### Syntax - -``` -server.begin() - -``` - -#### Parameters -None - -#### Returns -None - -#### Example - -``` -#include -#include - -// the media access control (ethernet hardware) address for the shield: -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -//the IP address for the shield: -byte ip[] = { 10, 0, 0, 177 }; -// the router's gateway address: -byte gateway[] = { 10, 0, 0, 1 }; -// the subnet: -byte subnet[] = { 255, 255, 0, 0 }; - -// telnet defaults to port 23 -EthernetServer server = EthernetServer(23); - -void setup() -{ - // initialize the ethernet device - Ethernet.begin(mac, ip, gateway, subnet); - - // start listening for clients - server.begin(); -} - -void loop() -{ - // if an incoming client connects, there will be bytes available to read: - EthernetClient client = server.available(); - if (client == true) { - // read bytes from the incoming client and write them back - // to any clients connected to the server: - server.write(client.read()); - } -} -``` - -### `server.accept()` - -#### Description - -The traditional server.available() function would only tell you of a new client after it sent data, which makes some protocols like FTP impossible to properly implement. - -The intention is programs will use either available() or accept(), but not both. With available(), the client connection continues to be managed by EthernetServer. You don’t need to keep a client object, since calling available() will give you whatever client has sent data. Simple servers can be written with very little code using available(). - -With accept(), EthernetServer gives you the client only once, regardless of whether it has sent any data. You must keep track of the connected clients. This requires more code, but you gain more control. - - -#### Syntax - -``` -server.accept() - -``` - -#### Parameters -none - -#### Returns -- a Client object. If no client has data available for reading, this object will evaluate to false in an if-statement. (EthernetClient). - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(192, 168, 69, 104); - -// telnet defaults to port 23 -EthernetServer server(23); - -EthernetClient clients[8]; - -void setup() { - Ethernet.begin(mac, ip); - - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // start listening for clients - server.begin(); -} - -void loop() { - // check for any new client connecting, and say hello (before any incoming data) - EthernetClient newClient = server.accept(); - if (newClient) { - for (byte i = 0; i < 8; i++) { - if (!clients[i]) { - newClient.print("Hello, client number: "); - newClient.println(i); - // Once we "accept", the client is no longer tracked by EthernetServer - // so we must store it into our list of clients - clients[i] = newClient; - break; - } - } - } - - // check for incoming data from all clients - for (byte i = 0; i < 8; i++) { - while (clients[i] && clients[i].available() > 0) { - // read incoming data from the client - Serial.write(clients[i].read()); - } - } - - // stop any clients which disconnect - for (byte i = 0; i < 8; i++) { - if (clients[i] && !clients[i].connected()) { - clients[i].stop(); - } - } -} -``` - -### `server.available()` - -#### Description - -Gets a client that is connected to the server and has data available for reading. The connection persists when the returned client object goes out of scope; you can close it by calling client.stop(). - - -#### Syntax - -``` -server.available() - -``` - -#### Parameters -None - -#### Returns -- a Client object; if no Client has data available for reading, this object will evaluate to false in an if-statement (see the example below) - -#### Example - -``` -#include -#include - -// the media access control (ethernet hardware) address for the shield: -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -//the IP address for the shield: -byte ip[] = { 10, 0, 0, 177 }; -// the router's gateway address: -byte gateway[] = { 10, 0, 0, 1 }; -// the subnet: -byte subnet[] = { 255, 255, 0, 0 }; - - -// telnet defaults to port 23 -EthernetServer server = EthernetServer(23); - -void setup() -{ - // initialize the ethernet device - Ethernet.begin(mac, ip, gateway, subnet); - - // start listening for clients - server.begin(); -} - -void loop() -{ - // if an incoming client connects, there will be bytes available to read: - EthernetClient client = server.available(); - if (client) { - // read bytes from the incoming client and write them back - // to any clients connected to the server: - server.write(client.read()); - } -} -``` - -### `if(server)` - -#### Description -Indicates whether the server is listening for new clients. You can use this to detect whether server.begin() was successful. It can also tell you when no more sockets are available to listen for more clients, because the maximum number have connected. - - -#### Syntax - -``` -if(server) - -``` - -#### Parameters -none - -#### Returns -- whether the server is listening for new clients (bool). - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -// telnet defaults to port 23 -EthernetServer server = EthernetServer(23); - -void setup() { - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // initialize the Ethernet device - Ethernet.begin(mac, ip); - - // start listening for clients - server.begin(); -} - -void loop() { - if (server) { - Serial.println("Server is listening"); - } - else { - Serial.println("Server is not listening"); - } -} -``` - -### `server.write()` - -#### Description - -Write data to all the clients connected to a server. This data is sent as a byte or series of bytes. - - -#### Syntax - -``` -server.write(val) -server.write(buf, len) - -``` - -#### Parameters -- val: a value to send as a single byte (byte or char) - -- buf: an array to send as a series of bytes (byte or char) - -- len: the length of the buffer - -#### Returns -- byte -- write() returns the number of bytes written. It is not necessary to read this. - -#### Example - -``` -#include -#include - -// network configuration. gateway and subnet are optional. - - // the media access control (ethernet hardware) address for the shield: -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -//the IP address for the shield: -byte ip[] = { 10, 0, 0, 177 }; -// the router's gateway address: -byte gateway[] = { 10, 0, 0, 1 }; -// the subnet: -byte subnet[] = { 255, 255, 0, 0 }; - -// telnet defaults to port 23 -EthernetServer server = EthernetServer(23); - -void setup() -{ - // initialize the ethernet device - Ethernet.begin(mac, ip, gateway, subnet); - - // start listening for clients - server.begin(); -} - -void loop() -{ - // if an incoming client connects, there will be bytes available to read: - EthernetClient client = server.available(); - if (client == true) { - // read bytes from the incoming client and write them back - // to any clients connected to the server: - server.write(client.read()); - } -} -``` - -### `server.print()` - -#### Description - -Print data to all the clients connected to a server. Prints numbers as a sequence of digits, each an ASCII character (e.g. the number 123 is sent as the three characters '1', '2', '3'). - - -#### Syntax - -``` -server.print(data) -server.print(data, BASE) - -``` - -#### Parameters -- data: the data to print (char, byte, int, long, or string) - -- BASE (optional): the base in which to print numbers: BIN for binary (base 2), DEC for decimal (base 10), OCT for octal (base 8), HEX for hexadecimal (base 16). - -#### Returns -- byte -- print() will return the number of bytes written, though reading that number is optional - - -### `server.println()` - -#### Description - -Print data, followed by a newline, to all the clients connected to a server. Prints numbers as a sequence of digits, each an ASCII character (e.g. the number 123 is sent as the three characters '1', '2', '3'). - - -#### Syntax - -``` -server.println() -server.println(data) -server.println(data, BASE) - -``` - -#### Parameters -- data (optional): the data to print (char, byte, int, long, or string) - -- BASE (optional): the base in which to print numbers: BIN for binary (base 2), DEC for decimal (base 10), OCT for octal (base 8), HEX for hexadecimal (base 16). - -#### Returns -- byte - -- println() will return the number of bytes written, though reading that number is optional - -## Client Class - -### `Client` - -#### Description -Client is the base class for all Ethernet client based calls. It is not called directly, but invoked whenever you use a function that relies on it. - -### `EthernetClient()` - -#### Description - -Creates a client which can connect to a specified internet IP address and port (defined in the client.connect() function). - - -#### Syntax - -``` -EthernetClient() - -``` - -#### Parameters -None - -#### Example - -``` -#include -#include - - -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -byte ip[] = { 10, 0, 0, 177 }; -byte server[] = { 64, 233, 187, 99 }; // Google - -EthernetClient client; - -void setup() -{ - Ethernet.begin(mac, ip); - Serial.begin(9600); - - delay(1000); - - Serial.println("connecting..."); - - if (client.connect(server, 80)) { - Serial.println("connected"); - client.println("GET /search?q=arduino HTTP/1.0"); - client.println(); - } else { - Serial.println("connection failed"); - } -} - -void loop() -{ - if (client.available()) { - char c = client.read(); - Serial.print(c); - } - - if (!client.connected()) { - Serial.println(); - Serial.println("disconnecting."); - client.stop(); - for(;;) - ; - } -} -``` - -### `if (EthernetClient)` - -#### Description -Indicates if the specified Ethernet client is ready. - - -#### Syntax - -``` -if (client) - -``` - -#### Parameters -none - -#### Returns -- boolean : returns true if the specified client is available. - -#### Example - -``` -#include -#include - - -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -byte ip[] = { 10, 0, 0, 177 }; -byte server[] = { 64, 233, 187, 99 }; // Google - -EthernetClient client; - -void setup() -{ - Ethernet.begin(mac, ip); - Serial.begin(9600); - - delay(1000); - - Serial.println("connecting..."); - while(!client){ - ; // wait until there is a client connected to proceed - } - if (client.connect(server, 80)) { - Serial.println("connected"); - client.println("GET /search?q=arduino HTTP/1.0"); - client.println(); - } else { - Serial.println("connection failed"); - } -} - -void loop() -{ - if (client.available()) { - char c = client.read(); - Serial.print(c); - } - - if (!client.connected()) { - Serial.println(); - Serial.println("disconnecting."); - client.stop(); - for(;;) - ; - } -} - -``` - -### `client.connected()` - -#### Description - -Whether or not the client is connected. Note that a client is considered connected if the connection has been closed but there is still unread data. - - -#### Syntax - -``` -client.connected() - -``` - -#### Parameters -none - -#### Returns -- Returns true if the client is connected, false if not. - -#### Example - -``` -#include -#include - -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -byte ip[] = { 10, 0, 0, 177 }; -byte server[] = { 64, 233, 187, 99 }; // Google - -EthernetClient client; - -void setup() -{ - Ethernet.begin(mac, ip); - Serial.begin(9600); - client.connect(server, 80); - delay(1000); - - Serial.println("connecting..."); - - if (client.connected()) { - Serial.println("connected"); - client.println("GET /search?q=arduino HTTP/1.0"); - client.println(); - } else { - Serial.println("connection failed"); - } -} - -void loop() -{ - if (client.available()) { - char c = client.read(); - Serial.print(c); - } - - if (!client.connected()) { - Serial.println(); - Serial.println("disconnecting."); - client.stop(); - for(;;) - ; - } -} -``` - -### `client.connect()` - -#### Description - -Connects to a specified IP address and port. The return value indicates success or failure. Also supports DNS lookups when using a domain name. - - -#### Syntax - -``` -client.connect() -client.connect(ip, port) -client.connect(URL, port) - -``` - -#### Parameters -- ip: the IP address that the client will connect to (array of 4 bytes) - -- URL: the domain name the client will connect to (string, ex.:"arduino.cc") - -- port: the port that the client will connect to (int) - -#### Returns -- Returns an int (1,-1,-2,-3,-4) indicating connection status : - -- SUCCESS 1 -- TIMED_OUT -1 -- INVALID_SERVER -2 -- TRUNCATED -3 -- INVALID_RESPONSE -4 -#### Example - -``` -#include -#include - -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -byte ip[] = { 10, 0, 0, 177 }; -byte server[] = { 64, 233, 187, 99 }; // Google - -EthernetClient client; - -void setup() -{ - Ethernet.begin(mac, ip); - Serial.begin(9600); - - delay(1000); - - Serial.println("connecting..."); - - if (client.connect(server, 80)) { - Serial.println("connected"); - client.println("GET /search?q=arduino HTTP/1.0"); - client.println(); - } else { - Serial.println("connection failed"); - } -} - -void loop() -{ - if (client.available()) { - char c = client.read(); - Serial.print(c); - } - - if (!client.connected()) { - Serial.println(); - Serial.println("disconnecting."); - client.stop(); - for(;;) - ; - } -} -``` - -### `client.localPort()` - -#### Description - -Returns the local port number the client is connected to. - - -#### Syntax - -``` -client.localPort - -``` - -#### Parameters -none - -#### Returns -- the local port number the client is connected to (uint16_t). - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -// telnet defaults to port 23 -EthernetServer server = EthernetServer(23); - -void setup() { - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // initialize the Ethernet device - Ethernet.begin(mac, ip); - - // start listening for clients - server.begin(); -} - -void loop() { - // if an incoming client connects, there will be bytes available to read: - EthernetClient client = server.available(); - if (client) { - Serial.print("Client is connected on port: "); - Serial.println(client.localPort()); - client.stop(); - } -} -``` - -### `client.remoteIP()` - -#### Description - -Returns the IP address of the client. - - -#### Syntax - -``` -client.remoteIP() - -``` - -#### Parameters -none - -#### Returns -- the client's IP address (IPAddress). - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -// telnet defaults to port 23 -EthernetServer server = EthernetServer(23); - -void setup() { - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // initialize the Ethernet device - Ethernet.begin(mac, ip); - - // start listening for clients - server.begin(); -} - -void loop() { - // if an incoming client connects, there will be bytes available to read: - EthernetClient client = server.available(); - if (client) { - Serial.print("Remote IP address: "); - Serial.println(client.remoteIP()); - client.stop(); - } -} -``` - -### `client.remotePort()` - -#### Description - -Returns the port of the host that sent the current incoming packet. - - -#### Syntax - -``` -client.remotePort() - -``` - -#### Parameters -none - -#### Returns -- the port of the host that sent the current incoming packet (uint16_t). - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -// telnet defaults to port 23 -EthernetServer server = EthernetServer(23); - -void setup() { - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // initialize the Ethernet device - Ethernet.begin(mac, ip); - - // start listening for clients - server.begin(); -} - -void loop() { - // if an incoming client connects, there will be bytes available to read: - EthernetClient client = server.available(); - if (client) { - Serial.print("Remote port: "); - Serial.println(client.remotePort()); - client.stop(); - } -} -``` - -### `client.setConnectionTimeout()` - -#### Description - -Set the timeout for client.connect() and client.stop(). The initial value is 1000 ms. You might prefer to set a lower timeout value to make your program more responsive in the event something goes wrong. - - -#### Syntax - -``` -client.setConnectionTimeout(milliseconds) - -``` - -#### Parameters -- milliseconds: the timeout duration for client.connect() and client.stop() (uint16_t) - -#### Returns -Nothing - -#### Example - -``` -#include -#include - -byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; -IPAddress ip(10, 0, 0, 177); - -// telnet defaults to port 23 -EthernetServer server = EthernetServer(23); - -void setup() { - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // initialize the Ethernet device - Ethernet.begin(mac, ip); - - // start listening for clients - server.begin(); -} - -void loop() { - // if an incoming client connects, there will be bytes available to read: - EthernetClient client = server.available(); - if (client) { - client.setConnectionTimeout(100); // set the timeout duration for client.connect() and client.stop() - } -} -``` - -### `client.write()` - -#### Description - -Write data to the server the client is connected to. This data is sent as a byte or series of bytes. - - -#### Syntax - -``` -client.write(val) -client.write(buf, len) - -``` - -#### Parameters -- val: a value to send as a single byte (byte or char) - -- buf: an array to send as a series of bytes (byte or char) - -- len: the length of the buffer - -#### Returns -byte: -- write() returns the number of bytes written. It is not necessary to read this value. - -### `print()` - -#### Description - -Print data to the server that a client is connected to. Prints numbers as a sequence of digits, each an ASCII character (e.g. the number 123 is sent as the three characters '1', '2', '3'). - - -#### Syntax - -``` -client.print(data) -client.print(data, BASE) - -``` - -#### Parameters -- data: the data to print (char, byte, int, long, or string) - -- BASE (optional): the base in which to print numbers: DEC for decimal (base 10), OCT for octal (base 8), HEX for hexadecimal (base 16). -#### Returns -- byte: returns the number of bytes written, though reading that number is optional - -### `client.println()` - -#### Description - -Print data, followed by a carriage return and newline, to the server a client is connected to. Prints numbers as a sequence of digits, each an ASCII character (e.g. the number 123 is sent as the three characters '1', '2', '3'). - - -#### Syntax - -``` -client.println() -client.println(data) -client.print(data, BASE) - -``` - -#### Parameters -- data (optional): the data to print (char, byte, int, long, or string) - -- BASE (optional): the base in which to print numbers: DEC for decimal (base 10), OCT for octal (base 8), HEX for hexadecimal (base 16). - -#### Returns -- byte: return the number of bytes written, though reading that number is optional - - -### `client.available()` - -#### Description - -Returns the number of bytes available for reading (that is, the amount of data that has been written to the client by the server it is connected to). - -available() inherits from the Stream utility class. - - -#### Syntax - -``` -client.available() - -``` - -#### Parameters -none - -#### Returns -- The number of bytes available. - -#### Example - -``` -#include -#include - -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -byte ip[] = { 10, 0, 0, 177 }; -byte server[] = { 64, 233, 187, 99 }; // Google - -EthernetClient client; - -void setup() -{ - Ethernet.begin(mac, ip); - Serial.begin(9600); - - delay(1000); - - Serial.println("connecting..."); - - if (client.connect(server, 80)) { - Serial.println("connected"); - client.println("GET /search?q=arduino HTTP/1.0"); - client.println(); - } else { - Serial.println("connection failed"); - } -} - -void loop() -{ - if (client.available()) { - char c = client.read(); - Serial.print(c); - } - - if (!client.connected()) { - Serial.println(); - Serial.println("disconnecting."); - client.stop(); - for(;;) - ; - } -} -``` - -### `client.read()` - -#### Description - -Read the next byte received from the server the client is connected to (after the last call to read()). - -read() inherits from the Stream utility class. - - -#### Syntax - -``` -client.read() - -``` - -#### Parameters -none - -#### Returns -- The next byte (or character), or -1 if none is available. - -### `client.flush()` -Waits until all outgoing characters in buffer have been sent. - -flush() inherits from the Stream utility class. - - -#### Syntax - -``` -client.flush() - -``` - -#### Parameters -none - -#### Returns -none - -### `client.stop()` - -#### Description - -Disconnect from the server. - - -#### Syntax - -``` -client.stop() - -``` - -#### Parameters -none - -#### Returns -none - -## EthernetUDP Class - -### `EthernetUDP.begin()` - -#### Description -Initializes the ethernet UDP library and network settings. - - -#### Syntax - -``` -EthernetUDP.begin(localPort); -``` - -#### Parameters -- localPort: the local port to listen on (int) - -#### Returns -- 1 if successful, 0 if there are no sockets available to use. - -#### Example - -``` - -#include - -#include - -#include - - - -// Enter a MAC address and IP address for your controller below. - -// The IP address will be dependent on your local network: - -byte mac[] = { - - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; - -IPAddress ip(192, 168, 1, 177); - - - -unsigned int localPort = 8888; // local port to listen on - - - -// An EthernetUDP instance to let us send and receive packets over UDP - -EthernetUDP Udp; - - - -void setup() { - - // start the Ethernet and UDP: - - Ethernet.begin(mac,ip); - - Udp.begin(localPort); - - - -} - - - -void loop() { -} - - -``` - -### `EthernetUDP.read()` - -#### Description -Reads UDP data from the specified buffer. If no arguments are given, it will return the next character in the buffer. - -This function can only be successfully called after UDP.parsePacket(). - - -#### Syntax - -``` -EthernetUDP.read(); -EthernetUDP.read(packetBuffer, MaxSize); -``` - -#### Parameters -- packetBuffer: buffer to hold incoming packets (char) -- MaxSize: maximum size of the buffer (int) - -#### Returns -- char : returns the characters in the buffer - -#### Example - -``` -#include -#include -#include - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -IPAddress ip(192, 168, 1, 177); - -unsigned int localPort = 8888; // local port to listen on - -// An EthernetUDP instance to let us send and receive packets over UDP -EthernetUDP Udp; - -char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet, - -void setup() { - // start the Ethernet and UDP: - Ethernet.begin(mac,ip); - Udp.begin(localPort); - -} - -void loop() { - - int packetSize = Udp.parsePacket(); - if(packetSize) - { - Serial.print("Received packet of size "); - Serial.println(packetSize); - Serial.print("From "); - IPAddress remote = Udp.remoteIP(); - for (int i =0; i < 4; i++) - { - Serial.print(remote[i], DEC); - if (i < 3) - { - Serial.print("."); - } - } - Serial.print(", port "); - Serial.println(Udp.remotePort()); - - // read the packet into packetBuffer - Udp.read(packetBuffer,UDP_TX_PACKET_MAX_SIZE); - Serial.println("Contents:"); - Serial.println(packetBuffer); -} -} -``` - -### `EthernetUDP.write()` - -#### Description -Writes UDP data to the remote connection. Must be wrapped between beginPacket() and endPacket(). beginPacket() initializes the packet of data, it is not sent until endPacket() is called. - - -#### Syntax - -``` -EthernetUDP.write(message); -EthernetUDP.write(buffer, size); - -``` - -#### Parameters - -- message: the outgoing message (char) - -- buffer: an array to send as a series of bytes (byte or char) - -- size: the length of the buffer - -#### Returns -- byte : returns the number of characters sent. This does not have to be read - -#### Example - -``` - - - -#include - -#include - -#include - - - -// Enter a MAC address and IP address for your controller below. - -// The IP address will be dependent on your local network: - -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; - -IPAddress ip(192, 168, 1, 177); - - - -unsigned int localPort = 8888; // local port to listen on - - - -// An EthernetUDP instance to let us send and receive packets over UDP - -EthernetUDP Udp; - - - -void setup() { - - // start the Ethernet and UDP: - - Ethernet.begin(mac,ip); - - Udp.begin(localPort); - -} - - - -void loop() { - - Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); - - Udp.write("hello"); - - Udp.endPacket(); - -} - - -``` - -### `EthernetUDP.beginPacket()` - -#### Description -Starts a connection to write UDP data to the remote connection - - -#### Syntax - -``` -EthernetUDP.beginPacket(remoteIP, remotePort); -``` - -#### Parameters -- remoteIP: the IP address of the remote connection (4 bytes) -- remotePort: the port of the remote connection (int) -#### Returns -- Returns an int: 1 if successful, 0 if there was a problem resolving the hostname or port. - -#### Example - -``` -#include -#include -#include - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -IPAddress ip(192, 168, 1, 177); - -unsigned int localPort = 8888; // local port to listen on - -// An EthernetUDP instance to let us send and receive packets over UDP -EthernetUDP Udp; - -void setup() { - // start the Ethernet and UDP: - Ethernet.begin(mac,ip); - Udp.begin(localPort); - -} - -void loop() { - - Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); - Udp.write("hello"); - Udp.endPacket(); - -} -``` - -### `EthernetUDP.endPacket()` - -#### Description -Called after writing UDP data to the remote connection. - - -#### Syntax - -``` -EthernetUDP.endPacket(); -``` - -#### Parameters -None - -#### Returns -- Returns an int: 1 if the packet was sent successfully, 0 if there was an error - -#### Example - -``` -#include -#include -#include - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -IPAddress ip(192, 168, 1, 177); - -unsigned int localPort = 8888; // local port to listen on - -// An EthernetUDP instance to let us send and receive packets over UDP -EthernetUDP Udp; - -void setup() { - // start the Ethernet and UDP: - Ethernet.begin(mac,ip); - Udp.begin(localPort); - -} - -void loop() { - - Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); - Udp.write("hello"); - Udp.endPacket(); - -} -``` - -### `EthernetUDP.parsePacket()` - -#### Description -Checks for the presence of a UDP packet, and reports the size. parsePacket() must be called before reading the buffer with UDP.read(). - - -#### Syntax - -``` -EthernetUDP.parsePacket(); -``` - -#### Parameters -None - -#### Returns -- int: the size of a received UDP packet - -#### Example - -``` - -#include // needed for Arduino versions later than 0018 -#include -#include // UDP library from: bjoern@cs.stanford.edu 12/30/2008 - - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -IPAddress ip(192, 168, 1, 177); - -unsigned int localPort = 8888; // local port to listen on - -// An EthernetUDP instance to let us send and receive packets over UDP -EthernetUDP Udp; - -void setup() { - // start the Ethernet and UDP: - Ethernet.begin(mac,ip); - Udp.begin(localPort); - - Serial.begin(9600); -} - -void loop() { - // if there's data available, read a packet - int packetSize = Udp.parsePacket(); - if(packetSize) - { - Serial.print("Received packet of size "); - Serial.println(packetSize); - } - delay(10); -} -``` - -### `EthernetUDP.available()` - -#### Description - -Get the number of bytes (characters) available for reading from the buffer. This is data that's already arrived. - -This function can only be successfully called after UDP.parsePacket(). - -available() inherits from the Stream utility class. - - -#### Syntax - -``` -EthernetUDP.available() - -``` - -#### Parameters -None - -#### Returns -- the number of bytes available to read - -#### Example - -``` -#include -#include -#include - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -IPAddress ip(192, 168, 1, 177); - -unsigned int localPort = 8888; // local port to listen on - -// An EthernetUDP instance to let us send and receive packets over UDP -EthernetUDP Udp; - -char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet, - -void setup() { - // start the Ethernet and UDP: - Ethernet.begin(mac,ip); - Udp.begin(localPort); - -} - -void loop() { - - int packetSize = Udp.parsePacket(); - if(Udp.available()) - { - Serial.print("Received packet of size "); - Serial.println(packetSize); - Serial.print("From "); - IPAddress remote = Udp.remoteIP(); - for (int i =0; i < 4; i++) - { - Serial.print(remote[i], DEC); - if (i < 3) - { - Serial.print("."); - } - } - Serial.print(", port "); - Serial.println(Udp.remotePort()); - - // read the packet into packetBuffer - Udp.read(packetBuffer,UDP_TX_PACKET_MAX_SIZE); - Serial.println("Contents:"); - Serial.println(packetBuffer); - } -} -``` - -## UDP class - -### `UDP.stop()` - -#### Description - -Disconnect from the server. Release any resource being used during the UDP session. - - -#### Syntax - -``` -UDP.stop() - -``` - -#### Parameters -none - -#### Returns -none - -### `UDP.remoteIP()` - -#### Description -Gets the IP address of the remote connection. - -This function must be called after UDP.parsePacket(). - - -#### Syntax - -``` -UDP.remoteIP(); -``` - -#### Parameters -None - -#### Returns -- 4 bytes : the IP address of the remote connection - -#### Example - -``` - -#include -#include -#include - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -IPAddress ip(192, 168, 1, 177); - -unsigned int localPort = 8888; // local port to listen on - -// An EthernetUDP instance to let us send and receive packets over UDP -EthernetUDP Udp; - -void setup() { - // start the Ethernet and UDP: - Ethernet.begin(mac,ip); - Udp.begin(localPort); -} - -void loop() { - - int packetSize = Udp.parsePacket(); - if(packetSize) - { - Serial.print("Received packet of size "); - Serial.println(packetSize); - Serial.print("From IP : "); - - IPAddress remote = Udp.remoteIP(); - //print out the remote connection's IP address - Serial.print(remote); - - Serial.print(" on port : "); - //print out the remote connection's port - Serial.println(Udp.remotePort()); - } - -} - - -``` - -### `UDP.remotePort()` - -#### Description -Gets the port of the remote UDP connection. - -This function must be called after UDP.parsePacket(). - - -#### Syntax - -``` -UDP.remotePort(); -``` - -#### Parameters -None - -#### Returns -- int : the port of the UDP connection to a remote host - -#### Example - -``` -#include -#include -#include - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -IPAddress ip(192, 168, 1, 177); - -unsigned int localPort = 8888; // local port to listen on - -// An EthernetUDP instance to let us send and receive packets over UDP -EthernetUDP Udp; - -char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet, - -void setup() { - // start the Ethernet and UDP: - Ethernet.begin(mac,ip); - Udp.begin(localPort); - -} - -void loop() { - - int packetSize = Udp.parsePacket(); - if(packetSize) - { - Serial.print("Received packet of size "); - Serial.println(packetSize); - Serial.print("From "); - IPAddress remote = Udp.remoteIP(); - for (int i =0; i < 4; i++) - { - Serial.print(remote[i], DEC); - if (i < 3) - { - Serial.print("."); - } - } - Serial.print(", port "); - Serial.println(Udp.remotePort()); - - // read the packet into packetBuffer - Udp.read(packetBuffer,UDP_TX_PACKET_MAX_SIZE); - Serial.println("Contents:"); - Serial.println(packetBuffer); - } -} -``` \ No newline at end of file diff --git a/lib/Ethernet/docs/arduino_mega_ethernet_pins.png b/lib/Ethernet/docs/arduino_mega_ethernet_pins.png deleted file mode 100644 index e35edc2c8db2ab7ed4aa17b94854931f86ca525d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19276 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 i&D^-vU=QsfK>j}?Eii=>Xe(9#0000E3^jRp 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 -#include -``` \ No newline at end of file diff --git a/lib/Ethernet/examples/AdvancedChatServer/AdvancedChatServer.ino b/lib/Ethernet/examples/AdvancedChatServer/AdvancedChatServer.ino deleted file mode 100644 index c97a958..0000000 --- a/lib/Ethernet/examples/AdvancedChatServer/AdvancedChatServer.ino +++ /dev/null @@ -1,119 +0,0 @@ -/* - Advanced Chat Server - - A more advanced server that distributes any incoming messages - to all connected clients but the client the message comes from. - To use, telnet to your device's IP address and type. - You can see the client's input in the serial monitor as well. - Using an Arduino WIZnet Ethernet shield. - - Circuit: - * Ethernet shield attached to pins 10, 11, 12, 13 - - created 18 Dec 2009 - by David A. Mellis - modified 9 Apr 2012 - by Tom Igoe - redesigned to make use of operator== 25 Nov 2013 - by Norbert Truchsess - - */ - -#include -#include - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network. -// gateway and subnet are optional: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED -}; -IPAddress ip(192, 168, 1, 177); -IPAddress myDns(192, 168, 1, 1); -IPAddress gateway(192, 168, 1, 1); -IPAddress subnet(255, 255, 0, 0); - - -// telnet defaults to port 23 -EthernetServer server(23); - -EthernetClient clients[8]; - -void setup() { - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - //Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - // initialize the Ethernet device - Ethernet.begin(mac, ip, myDns, gateway, subnet); - - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - while (true) { - delay(1); // do nothing, no point running without Ethernet hardware - } - } - if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - } - - // start listening for clients - server.begin(); - - Serial.print("Chat server address:"); - Serial.println(Ethernet.localIP()); -} - -void loop() { - // check for any new client connecting, and say hello (before any incoming data) - EthernetClient newClient = server.accept(); - if (newClient) { - for (byte i=0; i < 8; i++) { - if (!clients[i]) { - Serial.print("We have a new client #"); - Serial.println(i); - newClient.print("Hello, client number: "); - newClient.println(i); - // Once we "accept", the client is no longer tracked by EthernetServer - // so we must store it into our list of clients - clients[i] = newClient; - break; - } - } - } - - // check for incoming data from all clients - for (byte i=0; i < 8; i++) { - if (clients[i] && clients[i].available() > 0) { - // read bytes from a client - byte buffer[80]; - int count = clients[i].read(buffer, 80); - // write the bytes to all other connected clients - for (byte j=0; j < 8; j++) { - if (j != i && clients[j].connected()) { - clients[j].write(buffer, count); - } - } - } - } - - // stop any clients which disconnect - for (byte i=0; i < 8; i++) { - if (clients[i] && !clients[i].connected()) { - Serial.print("disconnect client #"); - Serial.println(i); - clients[i].stop(); - } - } -} diff --git a/lib/Ethernet/examples/BarometricPressureWebServer/BarometricPressureWebServer.ino b/lib/Ethernet/examples/BarometricPressureWebServer/BarometricPressureWebServer.ino deleted file mode 100644 index 831f17f..0000000 --- a/lib/Ethernet/examples/BarometricPressureWebServer/BarometricPressureWebServer.ino +++ /dev/null @@ -1,247 +0,0 @@ -/* - SCP1000 Barometric Pressure Sensor Display - - Serves the output of a Barometric Pressure Sensor as a web page. - Uses the SPI library. For details on the sensor, see: - http://www.sparkfun.com/commerce/product_info.php?products_id=8161 - - This sketch adapted from Nathan Seidle's SCP1000 example for PIC: - http://www.sparkfun.com/datasheets/Sensors/SCP1000-Testing.zip - - TODO: this hardware is long obsolete. This example program should - be rewritten to use https://www.sparkfun.com/products/9721 - - Circuit: - SCP1000 sensor attached to pins 6,7, and 11 - 13: - DRDY: pin 6 - CSB: pin 7 - MOSI: pin 11 - MISO: pin 12 - SCK: pin 13 - - created 31 July 2010 - by Tom Igoe - */ - -#include -// the sensor communicates using SPI, so include the library: -#include - - -// assign a MAC address for the Ethernet controller. -// fill in your address here: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED -}; -// assign an IP address for the controller: -IPAddress ip(192, 168, 1, 20); - - -// Initialize the Ethernet server library -// with the IP address and port you want to use -// (port 80 is default for HTTP): -EthernetServer server(80); - - -//Sensor's memory register addresses: -const int PRESSURE = 0x1F; //3 most significant bits of pressure -const int PRESSURE_LSB = 0x20; //16 least significant bits of pressure -const int TEMPERATURE = 0x21; //16 bit temperature reading - -// pins used for the connection with the sensor -// the others you need are controlled by the SPI library): -const int dataReadyPin = 6; -const int chipSelectPin = 7; - -float temperature = 0.0; -long pressure = 0; -long lastReadingTime = 0; - -void setup() { - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - //Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - // start the SPI library: - SPI.begin(); - - // start the Ethernet connection - Ethernet.begin(mac, ip); - - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - while (true) { - delay(1); // do nothing, no point running without Ethernet hardware - } - } - if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - } - - // start listening for clients - server.begin(); - - // initialize the data ready and chip select pins: - pinMode(dataReadyPin, INPUT); - pinMode(chipSelectPin, OUTPUT); - - //Configure SCP1000 for low noise configuration: - writeRegister(0x02, 0x2D); - writeRegister(0x01, 0x03); - writeRegister(0x03, 0x02); - - // give the sensor and Ethernet shield time to set up: - delay(1000); - - //Set the sensor to high resolution mode to start readings: - writeRegister(0x03, 0x0A); - -} - -void loop() { - // check for a reading no more than once a second. - if (millis() - lastReadingTime > 1000) { - // if there's a reading ready, read it: - // don't do anything until the data ready pin is high: - if (digitalRead(dataReadyPin) == HIGH) { - getData(); - // timestamp the last time you got a reading: - lastReadingTime = millis(); - } - } - - // listen for incoming Ethernet connections: - listenForEthernetClients(); -} - - -void getData() { - Serial.println("Getting reading"); - //Read the temperature data - int tempData = readRegister(0x21, 2); - - // convert the temperature to Celsius and display it: - temperature = (float)tempData / 20.0; - - //Read the pressure data highest 3 bits: - byte pressureDataHigh = readRegister(0x1F, 1); - pressureDataHigh &= 0b00000111; //you only needs bits 2 to 0 - - //Read the pressure data lower 16 bits: - unsigned int pressureDataLow = readRegister(0x20, 2); - //combine the two parts into one 19-bit number: - pressure = ((pressureDataHigh << 16) | pressureDataLow) / 4; - - Serial.print("Temperature: "); - Serial.print(temperature); - Serial.println(" degrees C"); - Serial.print("Pressure: " + String(pressure)); - Serial.println(" Pa"); -} - -void listenForEthernetClients() { - // listen for incoming clients - EthernetClient client = server.available(); - if (client) { - Serial.println("Got a client"); - // an HTTP request ends with a blank line - bool currentLineIsBlank = true; - while (client.connected()) { - if (client.available()) { - char c = client.read(); - // if you've gotten to the end of the line (received a newline - // character) and the line is blank, the HTTP request has ended, - // so you can send a reply - if (c == '\n' && currentLineIsBlank) { - // send a standard HTTP response header - client.println("HTTP/1.1 200 OK"); - client.println("Content-Type: text/html"); - client.println(); - // print the current readings, in HTML format: - client.print("Temperature: "); - client.print(temperature); - client.print(" degrees C"); - client.println("
"); - client.print("Pressure: " + String(pressure)); - client.print(" Pa"); - client.println("
"); - break; - } - if (c == '\n') { - // you're starting a new line - currentLineIsBlank = true; - } else if (c != '\r') { - // you've gotten a character on the current line - currentLineIsBlank = false; - } - } - } - // give the web browser time to receive the data - delay(1); - // close the connection: - client.stop(); - } -} - - -//Send a write command to SCP1000 -void writeRegister(byte registerName, byte registerValue) { - // SCP1000 expects the register name in the upper 6 bits - // of the byte: - registerName <<= 2; - // command (read or write) goes in the lower two bits: - registerName |= 0b00000010; //Write command - - // take the chip select low to select the device: - digitalWrite(chipSelectPin, LOW); - - SPI.transfer(registerName); //Send register location - SPI.transfer(registerValue); //Send value to record into register - - // take the chip select high to de-select: - digitalWrite(chipSelectPin, HIGH); -} - - -//Read register from the SCP1000: -unsigned int readRegister(byte registerName, int numBytes) { - byte inByte = 0; // incoming from the SPI read - unsigned int result = 0; // result to return - - // SCP1000 expects the register name in the upper 6 bits - // of the byte: - registerName <<= 2; - // command (read or write) goes in the lower two bits: - registerName &= 0b11111100; //Read command - - // take the chip select low to select the device: - digitalWrite(chipSelectPin, LOW); - // send the device the register you want to read: - SPI.transfer(registerName); - // send a value of 0 to read the first byte returned: - inByte = SPI.transfer(0x00); - - result = inByte; - // if there's more than one byte returned, - // shift the first byte then get the second byte: - if (numBytes > 1) { - result = inByte << 8; - inByte = SPI.transfer(0x00); - result = result | inByte; - } - // take the chip select high to de-select: - digitalWrite(chipSelectPin, HIGH); - // return the result: - return (result); -} diff --git a/lib/Ethernet/examples/ChatServer/ChatServer.ino b/lib/Ethernet/examples/ChatServer/ChatServer.ino deleted file mode 100644 index 3e28060..0000000 --- a/lib/Ethernet/examples/ChatServer/ChatServer.ino +++ /dev/null @@ -1,96 +0,0 @@ -/* - Chat Server - - A simple server that distributes any incoming messages to all - connected clients. To use, telnet to your device's IP address and type. - You can see the client's input in the serial monitor as well. - Using an Arduino WIZnet Ethernet shield. - - Circuit: - * Ethernet shield attached to pins 10, 11, 12, 13 - - created 18 Dec 2009 - by David A. Mellis - modified 9 Apr 2012 - by Tom Igoe - - */ - -#include -#include - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network. -// gateway and subnet are optional: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; -IPAddress ip(192, 168, 1, 177); -IPAddress myDns(192, 168, 1, 1); -IPAddress gateway(192, 168, 1, 1); -IPAddress subnet(255, 255, 0, 0); - - -// telnet defaults to port 23 -EthernetServer server(23); -bool alreadyConnected = false; // whether or not the client was connected previously - -void setup() { - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - //Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - // initialize the Ethernet device - Ethernet.begin(mac, ip, myDns, gateway, subnet); - - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - while (true) { - delay(1); // do nothing, no point running without Ethernet hardware - } - } - if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - } - - // start listening for clients - server.begin(); - - Serial.print("Chat server address:"); - Serial.println(Ethernet.localIP()); -} - -void loop() { - // wait for a new client: - EthernetClient client = server.available(); - - // when the client sends the first byte, say hello: - if (client) { - if (!alreadyConnected) { - // clear out the input buffer: - client.flush(); - Serial.println("We have a new client"); - client.println("Hello, client!"); - alreadyConnected = true; - } - - if (client.available() > 0) { - // read the bytes incoming from the client: - char thisChar = client.read(); - // echo the bytes back to the client: - server.write(thisChar); - // echo the bytes to the server as well: - Serial.write(thisChar); - } - } -} diff --git a/lib/Ethernet/examples/DhcpAddressPrinter/DhcpAddressPrinter.ino b/lib/Ethernet/examples/DhcpAddressPrinter/DhcpAddressPrinter.ino deleted file mode 100644 index 612106f..0000000 --- a/lib/Ethernet/examples/DhcpAddressPrinter/DhcpAddressPrinter.ino +++ /dev/null @@ -1,94 +0,0 @@ -/* - DHCP-based IP printer - - This sketch uses the DHCP extensions to the Ethernet library - to get an IP address via DHCP and print the address obtained. - using an Arduino WIZnet Ethernet shield. - - Circuit: - Ethernet shield attached to pins 10, 11, 12, 13 - - created 12 April 2011 - modified 9 Apr 2012 - by Tom Igoe - modified 02 Sept 2015 - by Arturo Guadalupi - - */ - -#include -#include - -// Enter a MAC address for your controller below. -// Newer Ethernet shields have a MAC address printed on a sticker on the shield -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED -}; - -void setup() { - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - //Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // start the Ethernet connection: - Serial.println("Initialize Ethernet with DHCP:"); - if (Ethernet.begin(mac) == 0) { - Serial.println("Failed to configure Ethernet using DHCP"); - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - } else if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - } - // no point in carrying on, so do nothing forevermore: - while (true) { - delay(1); - } - } - // print your local IP address: - Serial.print("My IP address: "); - Serial.println(Ethernet.localIP()); -} - -void loop() { - switch (Ethernet.maintain()) { - case 1: - //renewed fail - Serial.println("Error: renewed fail"); - break; - - case 2: - //renewed success - Serial.println("Renewed success"); - //print your local IP address: - Serial.print("My IP address: "); - Serial.println(Ethernet.localIP()); - break; - - case 3: - //rebind fail - Serial.println("Error: rebind fail"); - break; - - case 4: - //rebind success - Serial.println("Rebind success"); - //print your local IP address: - Serial.print("My IP address: "); - Serial.println(Ethernet.localIP()); - break; - - default: - //nothing happened - break; - } -} diff --git a/lib/Ethernet/examples/DhcpChatServer/DhcpChatServer.ino b/lib/Ethernet/examples/DhcpChatServer/DhcpChatServer.ino deleted file mode 100644 index 01cd814..0000000 --- a/lib/Ethernet/examples/DhcpChatServer/DhcpChatServer.ino +++ /dev/null @@ -1,101 +0,0 @@ -/* - DHCP Chat Server - - A simple server that distributes any incoming messages to all - connected clients. To use, telnet to your device's IP address and type. - You can see the client's input in the serial monitor as well. - Using an Arduino WIZnet Ethernet shield. - - THis version attempts to get an IP address using DHCP - - Circuit: - * Ethernet shield attached to pins 10, 11, 12, 13 - - created 21 May 2011 - modified 9 Apr 2012 - by Tom Igoe - modified 02 Sept 2015 - by Arturo Guadalupi - Based on ChatServer example by David A. Mellis - - */ - -#include -#include - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network. -// gateway and subnet are optional: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED -}; -IPAddress ip(192, 168, 1, 177); -IPAddress myDns(192, 168, 1, 1); -IPAddress gateway(192, 168, 1, 1); -IPAddress subnet(255, 255, 0, 0); - -// telnet defaults to port 23 -EthernetServer server(23); -bool gotAMessage = false; // whether or not you got a message from the client yet - -void setup() { - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - //Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // start the Ethernet connection: - Serial.println("Trying to get an IP address using DHCP"); - if (Ethernet.begin(mac) == 0) { - Serial.println("Failed to configure Ethernet using DHCP"); - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - while (true) { - delay(1); // do nothing, no point running without Ethernet hardware - } - } - if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - } - // initialize the Ethernet device not using DHCP: - Ethernet.begin(mac, ip, myDns, gateway, subnet); - } - // print your local IP address: - Serial.print("My IP address: "); - Serial.println(Ethernet.localIP()); - - // start listening for clients - server.begin(); -} - -void loop() { - // wait for a new client: - EthernetClient client = server.available(); - - // when the client sends the first byte, say hello: - if (client) { - if (!gotAMessage) { - Serial.println("We have a new client"); - client.println("Hello, client!"); - gotAMessage = true; - } - - // read the bytes incoming from the client: - char thisChar = client.read(); - // echo the bytes back to the client: - server.write(thisChar); - // echo the bytes to the server as well: - Serial.print(thisChar); - Ethernet.maintain(); - } -} diff --git a/lib/Ethernet/examples/LinkStatus/LinkStatus.ino b/lib/Ethernet/examples/LinkStatus/LinkStatus.ino deleted file mode 100644 index 84651d0..0000000 --- a/lib/Ethernet/examples/LinkStatus/LinkStatus.ino +++ /dev/null @@ -1,44 +0,0 @@ -/* - Link Status - - This sketch prints the Ethernet link status. When the - Ethernet cable is connected the link status should go to "ON". - NOTE: Only WIZnet W5200 and W5500 are capable of reporting - the link status. W5100 will report "Unknown". - Hardware: - - Ethernet shield or equivalent board/shield with WIZnet W5200/W5500 - Written by Cristian Maglie - This example is public domain. -*/ - -#include -#include - -void setup() { - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - //Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - Serial.begin(9600); -} - -void loop() { - auto link = Ethernet.linkStatus(); - Serial.print("Link status: "); - switch (link) { - case Unknown: - Serial.println("Unknown"); - break; - case LinkON: - Serial.println("ON"); - break; - case LinkOFF: - Serial.println("OFF"); - break; - } - delay(1000); -} diff --git a/lib/Ethernet/examples/PagerServer/PagerServer.ino b/lib/Ethernet/examples/PagerServer/PagerServer.ino deleted file mode 100644 index e17ae6e..0000000 --- a/lib/Ethernet/examples/PagerServer/PagerServer.ino +++ /dev/null @@ -1,71 +0,0 @@ -/* - Pager Server - - A simple server that echoes any incoming messages to all - connected clients. Connect two or more telnet sessions - to see how server.available() and server.print() works. - - created in September 2020 for the Ethernet library - by Juraj Andrassy https://github.com/jandrassy - -*/ -#include - -// Enter a MAC address for your controller below. -// Newer Ethernet shields have a MAC address printed on a sticker on the shield -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; - -// Set the static IP address to use if the DHCP fails to assign -IPAddress ip(192, 168, 0, 177); - -EthernetServer server(2323); - -void setup() { - - Serial.begin(9600); - while (!Serial); - - // start the Ethernet connection: - Serial.println("Initialize Ethernet with DHCP:"); - if (Ethernet.begin(mac) == 0) { - Serial.println("Failed to configure Ethernet using DHCP"); - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - while (true) { - delay(1); // do nothing, no point running without Ethernet hardware - } - } - if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - } - // try to configure using IP address instead of DHCP: - Ethernet.begin(mac, ip); - } else { - Serial.print(" DHCP assigned IP "); - Serial.println(Ethernet.localIP()); - } - - server.begin(); - - IPAddress ip = Ethernet.localIP(); - Serial.println(); - Serial.print("To access the server, connect with Telnet client to "); - Serial.print(ip); - Serial.println(" 2323"); -} - -void loop() { - - EthernetClient client = server.available(); // returns first client which has data to read or a 'false' client - if (client) { // client is true only if it is connected and has data to read - String s = client.readStringUntil('\n'); // read the message incoming from one of the clients - s.trim(); // trim eventual \r - Serial.println(s); // print the message to Serial Monitor - client.print("echo: "); // this is only for the sending client - server.println(s); // send the message to all connected clients -#ifndef ARDUINO_ARCH_SAM - server.flush(); // flush the buffers -#endif /* !defined(ARDUINO_ARCH_SAM) */ - } -} diff --git a/lib/Ethernet/examples/TelnetClient/TelnetClient.ino b/lib/Ethernet/examples/TelnetClient/TelnetClient.ino deleted file mode 100644 index ff554a5..0000000 --- a/lib/Ethernet/examples/TelnetClient/TelnetClient.ino +++ /dev/null @@ -1,109 +0,0 @@ -/* - Telnet client - - This sketch connects to a telnet server (http://www.google.com) - using an Arduino WIZnet Ethernet shield. You'll need a telnet server - to test this with. - Processing's ChatServer example (part of the Network library) works well, - running on port 10002. It can be found as part of the examples - in the Processing application, available at - https://processing.org/ - - Circuit: - * Ethernet shield attached to pins 10, 11, 12, 13 - - created 14 Sep 2010 - modified 9 Apr 2012 - by Tom Igoe - */ - -#include -#include - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED -}; -IPAddress ip(192, 168, 1, 177); - -// Enter the IP address of the server you're connecting to: -IPAddress server(1, 1, 1, 1); - -// Initialize the Ethernet client library -// with the IP address and port of the server -// that you want to connect to (port 23 is default for telnet; -// if you're using Processing's ChatServer, use port 10002): -EthernetClient client; - -void setup() { - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - //Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - // start the Ethernet connection: - Ethernet.begin(mac, ip); - - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - while (true) { - delay(1); // do nothing, no point running without Ethernet hardware - } - } - while (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - delay(500); - } - - // give the Ethernet shield a second to initialize: - delay(1000); - Serial.println("connecting..."); - - // if you get a connection, report back via serial: - if (client.connect(server, 10002)) { - Serial.println("connected"); - } else { - // if you didn't get a connection to the server: - Serial.println("connection failed"); - } -} - -void loop() { - // if there are incoming bytes available - // from the server, read them and print them: - if (client.available()) { - char c = client.read(); - Serial.print(c); - } - - // as long as there are bytes in the serial queue, - // read them and send them out the socket if it's open: - while (Serial.available() > 0) { - char inChar = Serial.read(); - if (client.connected()) { - client.print(inChar); - } - } - - // if the server's disconnected, stop the client: - if (!client.connected()) { - Serial.println(); - Serial.println("disconnecting."); - client.stop(); - // do nothing: - while (true) { - delay(1); - } - } -} diff --git a/lib/Ethernet/examples/UDPSendReceiveString/UDPSendReceiveString.ino b/lib/Ethernet/examples/UDPSendReceiveString/UDPSendReceiveString.ino deleted file mode 100644 index 3995b33..0000000 --- a/lib/Ethernet/examples/UDPSendReceiveString/UDPSendReceiveString.ino +++ /dev/null @@ -1,138 +0,0 @@ -/* - UDPSendReceiveString - - This sketch receives UDP message strings, prints them to the serial port - and sends an "acknowledge" string back to the sender - - A Processing sketch is included at the end of file that can be used to send - and receive messages for testing with a computer. - - created 21 Aug 2010 - by Michael Margolis - - This code is in the public domain. - */ - - -#include -#include - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED -}; -IPAddress ip(192, 168, 1, 177); - -unsigned int localPort = 8888; // local port to listen on - -// buffers for receiving and sending data -char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; // buffer to hold incoming packet, -char ReplyBuffer[] = "acknowledged"; // a string to send back - -// An EthernetUDP instance to let us send and receive packets over UDP -EthernetUDP Udp; - -void setup() { - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - //Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - // start the Ethernet - Ethernet.begin(mac, ip); - - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - while (true) { - delay(1); // do nothing, no point running without Ethernet hardware - } - } - if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - } - - // start UDP - Udp.begin(localPort); -} - -void loop() { - // if there's data available, read a packet - int packetSize = Udp.parsePacket(); - if (packetSize) { - Serial.print("Received packet of size "); - Serial.println(packetSize); - Serial.print("From "); - IPAddress remote = Udp.remoteIP(); - for (int i=0; i < 4; i++) { - Serial.print(remote[i], DEC); - if (i < 3) { - Serial.print("."); - } - } - Serial.print(", port "); - Serial.println(Udp.remotePort()); - - // read the packet into packetBuffer - Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); - Serial.println("Contents:"); - Serial.println(packetBuffer); - - // send a reply to the IP address and port that sent us the packet we received - Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); - Udp.write(ReplyBuffer); - Udp.endPacket(); - } - delay(10); -} - - -/* - Processing sketch to run with this example - ===================================================== - - // Processing UDP example to send and receive string data from Arduino - // press any key to send the "Hello Arduino" message - - - import hypermedia.net.*; - - UDP udp; // define the UDP object - - - void setup() { - udp = new UDP( this, 6000 ); // create a new datagram connection on port 6000 - //udp.log( true ); // <-- printout the connection activity - udp.listen( true ); // and wait for incoming message - } - - void draw() - { - } - - void keyPressed() { - String ip = "192.168.1.177"; // the remote IP address - int port = 8888; // the destination port - - udp.send("Hello World", ip, port ); // the message to send - - } - - void receive( byte[] data ) { // <-- default handler - //void receive( byte[] data, String ip, int port ) { // <-- extended handler - - for(int i=0; i < data.length; i++) - print(char(data[i])); - println(); - } - */ diff --git a/lib/Ethernet/examples/UdpNtpClient/UdpNtpClient.ino b/lib/Ethernet/examples/UdpNtpClient/UdpNtpClient.ino deleted file mode 100644 index 1455b40..0000000 --- a/lib/Ethernet/examples/UdpNtpClient/UdpNtpClient.ino +++ /dev/null @@ -1,145 +0,0 @@ -/* - Udp NTP Client - - Get the time from a Network Time Protocol (NTP) time server - Demonstrates use of UDP sendPacket and ReceivePacket - For more on NTP time servers and the messages needed to communicate with them, - see https://en.wikipedia.org/wiki/Network_Time_Protocol - - created 4 Sep 2010 - by Michael Margolis - modified 9 Apr 2012 - by Tom Igoe - modified 02 Sept 2015 - by Arturo Guadalupi - - This code is in the public domain. - - */ - -#include -#include -#include - -// Enter a MAC address for your controller below. -// Newer Ethernet shields have a MAC address printed on a sticker on the shield -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED -}; - -unsigned int localPort = 8888; // local port to listen for UDP packets - -const char timeServer[] = "time.nist.gov"; // time.nist.gov NTP server - -const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message - -byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets - -// A UDP instance to let us send and receive packets over UDP -EthernetUDP Udp; - -void setup() { - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - //Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // start Ethernet and UDP - if (Ethernet.begin(mac) == 0) { - Serial.println("Failed to configure Ethernet using DHCP"); - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - } else if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - } - // no point in carrying on, so do nothing forevermore: - while (true) { - delay(1); - } - } - Udp.begin(localPort); -} - -void loop() { - sendNTPpacket(timeServer); // send an NTP packet to a time server - - // wait to see if a reply is available - delay(1000); - if (Udp.parsePacket()) { - // We've received a packet, read the data from it - Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer - - // the timestamp starts at byte 40 of the received packet and is four bytes, - // or two words, long. First, extract the two words: - - unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); - unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); - // combine the four bytes (two words) into a long integer - // this is NTP time (seconds since Jan 1 1900): - unsigned long secsSince1900 = highWord << 16 | lowWord; - Serial.print("Seconds since Jan 1 1900 = "); - Serial.println(secsSince1900); - - // now convert NTP time into everyday time: - Serial.print("Unix time = "); - // Unix time starts on Jan 1 1970. In seconds, that's 2208988800: - const unsigned long seventyYears = 2208988800UL; - // subtract seventy years: - unsigned long epoch = secsSince1900 - seventyYears; - // print Unix time: - Serial.println(epoch); - - - // print the hour, minute and second: - Serial.print("The UTC time is "); // UTC is the time at Greenwich Meridian (GMT) - Serial.print((epoch % 86400L) / 3600); // print the hour (86400 equals secs per day) - Serial.print(':'); - if (((epoch % 3600) / 60) < 10) { - // In the first 10 minutes of each hour, we'll want a leading '0' - Serial.print('0'); - } - Serial.print((epoch % 3600) / 60); // print the minute (3600 equals secs per minute) - Serial.print(':'); - if ((epoch % 60) < 10) { - // In the first 10 seconds of each minute, we'll want a leading '0' - Serial.print('0'); - } - Serial.println(epoch % 60); // print the second - } - // wait ten seconds before asking for the time again - delay(10000); - Ethernet.maintain(); -} - -// send an NTP request to the time server at the given address -void sendNTPpacket(const char * address) { - // set all bytes in the buffer to 0 - memset(packetBuffer, 0, NTP_PACKET_SIZE); - // Initialize values needed to form NTP request - // (see URL above for details on the packets) - packetBuffer[0] = 0b11100011; // LI, Version, Mode - packetBuffer[1] = 0; // Stratum, or type of clock - packetBuffer[2] = 6; // Polling Interval - packetBuffer[3] = 0xEC; // Peer Clock Precision - // 8 bytes of zero for Root Delay & Root Dispersion - packetBuffer[12] = 49; - packetBuffer[13] = 0x4E; - packetBuffer[14] = 49; - packetBuffer[15] = 52; - - // all NTP fields have been given values, now - // you can send a packet requesting a timestamp: - Udp.beginPacket(address, 123); // NTP requests are to port 123 - Udp.write(packetBuffer, NTP_PACKET_SIZE); - Udp.endPacket(); -} diff --git a/lib/Ethernet/examples/WebClient/WebClient.ino b/lib/Ethernet/examples/WebClient/WebClient.ino deleted file mode 100644 index f4a5d02..0000000 --- a/lib/Ethernet/examples/WebClient/WebClient.ino +++ /dev/null @@ -1,136 +0,0 @@ -/* - Web client - - This sketch connects to a website (http://www.google.com) - using an Arduino WIZnet Ethernet shield. - - Circuit: - * Ethernet shield attached to pins 10, 11, 12, 13 - - created 18 Dec 2009 - by David A. Mellis - modified 9 Apr 2012 - by Tom Igoe, based on work by Adrian McEwen - - */ - -#include -#include - -// Enter a MAC address for your controller below. -// Newer Ethernet shields have a MAC address printed on a sticker on the shield -byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; - -// if you don't want to use DNS (and reduce your sketch size) -// use the numeric IP instead of the name for the server: -//IPAddress server(74,125,232,128); // numeric IP for Google (no DNS) -char server[] = "www.google.com"; // name address for Google (using DNS) - -// Set the static IP address to use if the DHCP fails to assign -IPAddress ip(192, 168, 0, 177); -IPAddress myDns(192, 168, 0, 1); - -// Initialize the Ethernet client library -// with the IP address and port of the server -// that you want to connect to (port 80 is default for HTTP): -EthernetClient client; - -// Variables to measure the speed -unsigned long beginMicros, endMicros; -unsigned long byteCount = 0; -bool printWebData = true; // set to false for better speed measurement - -void setup() { - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - //Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // start the Ethernet connection: - Serial.println("Initialize Ethernet with DHCP:"); - if (Ethernet.begin(mac) == 0) { - Serial.println("Failed to configure Ethernet using DHCP"); - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - while (true) { - delay(1); // do nothing, no point running without Ethernet hardware - } - } - if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - } - // try to configure using IP address instead of DHCP: - Ethernet.begin(mac, ip, myDns); - } else { - Serial.print(" DHCP assigned IP "); - Serial.println(Ethernet.localIP()); - } - // give the Ethernet shield a second to initialize: - delay(1000); - Serial.print("connecting to "); - Serial.print(server); - Serial.println("..."); - - // if you get a connection, report back via serial: - if (client.connect(server, 80)) { - Serial.print("connected to "); - Serial.println(client.remoteIP()); - // Make a HTTP request: - client.println("GET /search?q=arduino HTTP/1.1"); - client.println("Host: www.google.com"); - client.println("Connection: close"); - client.println(); - } else { - // if you didn't get a connection to the server: - Serial.println("connection failed"); - } - beginMicros = micros(); -} - -void loop() { - // if there are incoming bytes available - // from the server, read them and print them: - int len = client.available(); - if (len > 0) { - byte buffer[80]; - if (len > 80) len = 80; - client.read(buffer, len); - if (printWebData) { - Serial.write(buffer, len); // show in the serial monitor (slows some boards) - } - byteCount = byteCount + len; - } - - // if the server's disconnected, stop the client: - if (!client.connected()) { - endMicros = micros(); - Serial.println(); - Serial.println("disconnecting."); - client.stop(); - Serial.print("Received "); - Serial.print(byteCount); - Serial.print(" bytes in "); - float seconds = (float)(endMicros - beginMicros) / 1000000.0; - Serial.print(seconds, 4); - float rate = (float)byteCount / seconds / 1000.0; - Serial.print(", rate = "); - Serial.print(rate); - Serial.print(" kbytes/second"); - Serial.println(); - - // do nothing forevermore: - while (true) { - delay(1); - } - } -} diff --git a/lib/Ethernet/examples/WebClientRepeating/WebClientRepeating.ino b/lib/Ethernet/examples/WebClientRepeating/WebClientRepeating.ino deleted file mode 100644 index 1fb11e1..0000000 --- a/lib/Ethernet/examples/WebClientRepeating/WebClientRepeating.ino +++ /dev/null @@ -1,126 +0,0 @@ -/* - Repeating Web client - - This sketch connects to a web server and makes a request - using a WIZnet Ethernet shield. You can use the Arduino Ethernet Shield, or - the Adafruit Ethernet shield, either one will work, as long as it's got - a WIZnet Ethernet module on board. - - This example uses DNS, by assigning the Ethernet client with a MAC address, - IP address, and DNS address. - - Circuit: - * Ethernet shield attached to pins 10, 11, 12, 13 - - created 19 Apr 2012 - by Tom Igoe - modified 21 Jan 2014 - by Federico Vanzati - - https://www.arduino.cc/en/Tutorial/WebClientRepeating - This code is in the public domain. - - */ - -#include -#include - -// assign a MAC address for the Ethernet controller. -// fill in your address here: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED -}; -// Set the static IP address to use if the DHCP fails to assign -IPAddress ip(192, 168, 0, 177); -IPAddress myDns(192, 168, 0, 1); - -// initialize the library instance: -EthernetClient client; - -char server[] = "www.arduino.cc"; // also change the Host line in httpRequest() -//IPAddress server(64,131,82,241); - -unsigned long lastConnectionTime = 0; // last time you connected to the server, in milliseconds -const unsigned long postingInterval = 10*1000; // delay between updates, in milliseconds - -void setup() { - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - //Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - // start serial port: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - // start the Ethernet connection: - Serial.println("Initialize Ethernet with DHCP:"); - if (Ethernet.begin(mac) == 0) { - Serial.println("Failed to configure Ethernet using DHCP"); - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - while (true) { - delay(1); // do nothing, no point running without Ethernet hardware - } - } - if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - } - // try to configure using IP address instead of DHCP: - Ethernet.begin(mac, ip, myDns); - Serial.print("My IP address: "); - Serial.println(Ethernet.localIP()); - } else { - Serial.print(" DHCP assigned IP "); - Serial.println(Ethernet.localIP()); - } - // give the Ethernet shield a second to initialize: - delay(1000); -} - -void loop() { - // if there's incoming data from the net connection. - // send it out the serial port. This is for debugging - // purposes only: - if (client.available()) { - char c = client.read(); - Serial.write(c); - } - - // if ten seconds have passed since your last connection, - // then connect again and send data: - if (millis() - lastConnectionTime > postingInterval) { - httpRequest(); - } - -} - -// this method makes a HTTP connection to the server: -void httpRequest() { - // close any connection before send a new request. - // This will free the socket on the Ethernet shield - client.stop(); - - // if there's a successful connection: - if (client.connect(server, 80)) { - Serial.println("connecting..."); - // send the HTTP GET request: - client.println("GET /latest.txt HTTP/1.1"); - client.println("Host: www.arduino.cc"); - client.println("User-Agent: arduino-ethernet"); - client.println("Connection: close"); - client.println(); - - // note the time that the connection was made: - lastConnectionTime = millis(); - } else { - // if you couldn't make a connection: - Serial.println("connection failed"); - } -} diff --git a/lib/Ethernet/examples/WebServer/WebServer.ino b/lib/Ethernet/examples/WebServer/WebServer.ino deleted file mode 100644 index f3929d2..0000000 --- a/lib/Ethernet/examples/WebServer/WebServer.ino +++ /dev/null @@ -1,122 +0,0 @@ -/* - Web Server - - A simple web server that shows the value of the analog input pins. - using an Arduino WIZnet Ethernet shield. - - Circuit: - * Ethernet shield attached to pins 10, 11, 12, 13 - * Analog inputs attached to pins A0 through A5 (optional) - - created 18 Dec 2009 - by David A. Mellis - modified 9 Apr 2012 - by Tom Igoe - modified 02 Sept 2015 - by Arturo Guadalupi - - */ - -#include -#include - -// Enter a MAC address and IP address for your controller below. -// The IP address will be dependent on your local network: -byte mac[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED -}; -IPAddress ip(192, 168, 1, 177); - -// Initialize the Ethernet server library -// with the IP address and port you want to use -// (port 80 is default for HTTP): -EthernetServer server(80); - -void setup() { - // You can use Ethernet.init(pin) to configure the CS pin - //Ethernet.init(10); // Most Arduino shields - //Ethernet.init(5); // MKR ETH Shield - //Ethernet.init(0); // Teensy 2.0 - //Ethernet.init(20); // Teensy++ 2.0 - //Ethernet.init(15); // ESP8266 with Adafruit FeatherWing Ethernet - //Ethernet.init(33); // ESP32 with Adafruit FeatherWing Ethernet - - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - Serial.println("Ethernet WebServer Example"); - - // start the Ethernet connection and the server: - Ethernet.begin(mac, ip); - - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) { - Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :("); - while (true) { - delay(1); // do nothing, no point running without Ethernet hardware - } - } - if (Ethernet.linkStatus() == LinkOFF) { - Serial.println("Ethernet cable is not connected."); - } - - // start the server - server.begin(); - Serial.print("server is at "); - Serial.println(Ethernet.localIP()); -} - - -void loop() { - // listen for incoming clients - EthernetClient client = server.available(); - if (client) { - Serial.println("new client"); - // an HTTP request ends with a blank line - bool currentLineIsBlank = true; - while (client.connected()) { - if (client.available()) { - char c = client.read(); - Serial.write(c); - // if you've gotten to the end of the line (received a newline - // character) and the line is blank, the HTTP request has ended, - // so you can send a reply - if (c == '\n' && currentLineIsBlank) { - // send a standard HTTP response header - client.println("HTTP/1.1 200 OK"); - client.println("Content-Type: text/html"); - client.println("Connection: close"); // the connection will be closed after completion of the response - client.println("Refresh: 5"); // refresh the page automatically every 5 sec - client.println(); - client.println(""); - client.println(""); - // output the value of each analog input pin - for (int analogChannel = 0; analogChannel < 6; analogChannel++) { - int sensorReading = analogRead(analogChannel); - client.print("analog input "); - client.print(analogChannel); - client.print(" is "); - client.print(sensorReading); - client.println("
"); - } - client.println(""); - break; - } - if (c == '\n') { - // you're starting a new line - currentLineIsBlank = true; - } else if (c != '\r') { - // you've gotten a character on the current line - currentLineIsBlank = false; - } - } - } - // give the web browser time to receive the data - delay(1); - // close the connection: - client.stop(); - Serial.println("client disconnected"); - } -} diff --git a/lib/Ethernet/keywords.txt b/lib/Ethernet/keywords.txt deleted file mode 100644 index 9fd2541..0000000 --- a/lib/Ethernet/keywords.txt +++ /dev/null @@ -1,67 +0,0 @@ -####################################### -# Syntax Coloring Map For Ethernet -####################################### - -####################################### -# Datatypes (KEYWORD1) -####################################### - -Ethernet KEYWORD1 Ethernet -EthernetClient KEYWORD1 EthernetClient -EthernetServer KEYWORD1 EthernetServer -IPAddress KEYWORD1 EthernetIPAddress - -####################################### -# Methods and Functions (KEYWORD2) -####################################### - -status KEYWORD2 -connect KEYWORD2 -write KEYWORD2 -available KEYWORD2 -availableForWrite KEYWORD2 -read KEYWORD2 -peek KEYWORD2 -flush KEYWORD2 -stop KEYWORD2 -connected KEYWORD2 -accept KEYWORD2 -begin KEYWORD2 -beginMulticast KEYWORD2 -beginPacket KEYWORD2 -endPacket KEYWORD2 -parsePacket KEYWORD2 -remoteIP KEYWORD2 -remotePort KEYWORD2 -getSocketNumber KEYWORD2 -localIP KEYWORD2 -localPort KEYWORD2 -maintain KEYWORD2 -linkStatus KEYWORD2 -hardwareStatus KEYWORD2 -MACAddress KEYWORD2 -subnetMask KEYWORD2 -gatewayIP KEYWORD2 -dnsServerIP KEYWORD2 -setMACAddress KEYWORD2 -setLocalIP KEYWORD2 -setSubnetMask KEYWORD2 -setGatewayIP KEYWORD2 -setDnsServerIP KEYWORD2 -setRetransmissionTimeout KEYWORD2 -setRetransmissionCount KEYWORD2 -setConnectionTimeout KEYWORD2 - -####################################### -# Constants (LITERAL1) -####################################### - -EthernetLinkStatus LITERAL1 -Unknown LITERAL1 -LinkON LITERAL1 -LinkOFF LITERAL1 -EthernetHardwareStatus LITERAL1 -EthernetNoHardware LITERAL1 -EthernetW5100 LITERAL1 -EthernetW5200 LITERAL1 -EthernetW5500 LITERAL1 diff --git a/lib/Ethernet/library.properties b/lib/Ethernet/library.properties deleted file mode 100644 index 046e6c2..0000000 --- a/lib/Ethernet/library.properties +++ /dev/null @@ -1,10 +0,0 @@ -name=Ethernet -version=2.0.2 -author=Various (see AUTHORS file for details) -maintainer=Arduino -sentence=Enables network connection (local and Internet) using the Arduino Ethernet Board or Shield. -paragraph=With this library you can use the Arduino Ethernet (shield or board) to connect to Internet. The library provides both client and server functionalities. The library permits you to connect to a local network also with DHCP and to resolve DNS. -category=Communication -url=https://www.arduino.cc/en/Reference/Ethernet -architectures=* -includes=Ethernet.h \ No newline at end of file diff --git a/lib/Ethernet/src/Dhcp.cpp b/lib/Ethernet/src/Dhcp.cpp deleted file mode 100644 index 28df310..0000000 --- a/lib/Ethernet/src/Dhcp.cpp +++ /dev/null @@ -1,437 +0,0 @@ -// DHCP Library v0.3 - April 25, 2009 -// Author: Jordan Terrell - blog.jordanterrell.com - -#include -#include "Ethernet.h" -#include "Dhcp.h" -#include "utility/w5100.h" - -int DhcpClass::beginWithDHCP(uint8_t *mac, unsigned long timeout, unsigned long responseTimeout) -{ - _dhcpLeaseTime=0; - _dhcpT1=0; - _dhcpT2=0; - _timeout = timeout; - _responseTimeout = responseTimeout; - - // zero out _dhcpMacAddr - memset(_dhcpMacAddr, 0, 6); - reset_DHCP_lease(); - - memcpy((void*)_dhcpMacAddr, (void*)mac, 6); - _dhcp_state = STATE_DHCP_START; - return request_DHCP_lease(); -} - -void DhcpClass::reset_DHCP_lease() -{ - // zero out _dhcpSubnetMask, _dhcpGatewayIp, _dhcpLocalIp, _dhcpDhcpServerIp, _dhcpDnsServerIp - memset(_dhcpLocalIp, 0, sizeof(_dhcpLocalIp)); - memset(_dhcpSubnetMask, 0, sizeof(_dhcpSubnetMask)); - memset(_dhcpGatewayIp, 0, sizeof(_dhcpGatewayIp)); - memset(_dhcpDhcpServerIp, 0, sizeof(_dhcpDhcpServerIp)); - memset(_dhcpDnsServerIp, 0, sizeof(_dhcpDnsServerIp)); -} - - //return:0 on error, 1 if request is sent and response is received -int DhcpClass::request_DHCP_lease() -{ - uint8_t messageType = 0; - - // Pick an initial transaction ID - _dhcpTransactionId = random(1UL, 2000UL); - _dhcpInitialTransactionId = _dhcpTransactionId; - - _dhcpUdpSocket.stop(); - if (_dhcpUdpSocket.begin(DHCP_CLIENT_PORT) == 0) { - // Couldn't get a socket - return 0; - } - - presend_DHCP(); - - int result = 0; - - unsigned long startTime = millis(); - - while (_dhcp_state != STATE_DHCP_LEASED) { - if (_dhcp_state == STATE_DHCP_START) { - _dhcpTransactionId++; - send_DHCP_MESSAGE(DHCP_DISCOVER, ((millis() - startTime) / 1000)); - _dhcp_state = STATE_DHCP_DISCOVER; - } else if (_dhcp_state == STATE_DHCP_REREQUEST) { - _dhcpTransactionId++; - send_DHCP_MESSAGE(DHCP_REQUEST, ((millis() - startTime)/1000)); - _dhcp_state = STATE_DHCP_REQUEST; - } else if (_dhcp_state == STATE_DHCP_DISCOVER) { - uint32_t respId; - messageType = parseDHCPResponse(_responseTimeout, respId); - if (messageType == DHCP_OFFER) { - // We'll use the transaction ID that the offer came with, - // rather than the one we were up to - _dhcpTransactionId = respId; - send_DHCP_MESSAGE(DHCP_REQUEST, ((millis() - startTime) / 1000)); - _dhcp_state = STATE_DHCP_REQUEST; - } - } else if (_dhcp_state == STATE_DHCP_REQUEST) { - uint32_t respId; - messageType = parseDHCPResponse(_responseTimeout, respId); - if (messageType == DHCP_ACK) { - _dhcp_state = STATE_DHCP_LEASED; - result = 1; - //use default lease time if we didn't get it - if (_dhcpLeaseTime == 0) { - _dhcpLeaseTime = DEFAULT_LEASE; - } - // Calculate T1 & T2 if we didn't get it - if (_dhcpT1 == 0) { - // T1 should be 50% of _dhcpLeaseTime - _dhcpT1 = _dhcpLeaseTime >> 1; - } - if (_dhcpT2 == 0) { - // T2 should be 87.5% (7/8ths) of _dhcpLeaseTime - _dhcpT2 = _dhcpLeaseTime - (_dhcpLeaseTime >> 3); - } - _renewInSec = _dhcpT1; - _rebindInSec = _dhcpT2; - } else if (messageType == DHCP_NAK) { - _dhcp_state = STATE_DHCP_START; - } - } - - if (messageType == 255) { - messageType = 0; - _dhcp_state = STATE_DHCP_START; - } - - if (result != 1 && ((millis() - startTime) > _timeout)) - break; - } - - // We're done with the socket now - _dhcpUdpSocket.stop(); - _dhcpTransactionId++; - - _lastCheckLeaseMillis = millis(); - return result; -} - -void DhcpClass::presend_DHCP() -{ -} - -void DhcpClass::send_DHCP_MESSAGE(uint8_t messageType, uint16_t secondsElapsed) -{ - uint8_t buffer[32]; - memset(buffer, 0, 32); - IPAddress dest_addr(255, 255, 255, 255); // Broadcast address - - if (_dhcpUdpSocket.beginPacket(dest_addr, DHCP_SERVER_PORT) == -1) { - //Serial.printf("DHCP transmit error\n"); - // FIXME Need to return errors - return; - } - - buffer[0] = DHCP_BOOTREQUEST; // op - buffer[1] = DHCP_HTYPE10MB; // htype - buffer[2] = DHCP_HLENETHERNET; // hlen - buffer[3] = DHCP_HOPS; // hops - - // xid - unsigned long xid = htonl(_dhcpTransactionId); - memcpy(buffer + 4, &(xid), 4); - - // 8, 9 - seconds elapsed - buffer[8] = ((secondsElapsed & 0xff00) >> 8); - buffer[9] = (secondsElapsed & 0x00ff); - - // flags - unsigned short flags = htons(DHCP_FLAGSBROADCAST); - memcpy(buffer + 10, &(flags), 2); - - // ciaddr: already zeroed - // yiaddr: already zeroed - // siaddr: already zeroed - // giaddr: already zeroed - - //put data in W5100 transmit buffer - _dhcpUdpSocket.write(buffer, 28); - - memset(buffer, 0, 32); // clear local buffer - - memcpy(buffer, _dhcpMacAddr, 6); // chaddr - - //put data in W5100 transmit buffer - _dhcpUdpSocket.write(buffer, 16); - - memset(buffer, 0, 32); // clear local buffer - - // leave zeroed out for sname && file - // put in W5100 transmit buffer x 6 (192 bytes) - - for(int i = 0; i < 6; i++) { - _dhcpUdpSocket.write(buffer, 32); - } - - // OPT - Magic Cookie - buffer[0] = (uint8_t)((MAGIC_COOKIE >> 24)& 0xFF); - buffer[1] = (uint8_t)((MAGIC_COOKIE >> 16)& 0xFF); - buffer[2] = (uint8_t)((MAGIC_COOKIE >> 8)& 0xFF); - buffer[3] = (uint8_t)(MAGIC_COOKIE& 0xFF); - - // OPT - message type - buffer[4] = dhcpMessageType; - buffer[5] = 0x01; - buffer[6] = messageType; //DHCP_REQUEST; - - // OPT - client identifier - buffer[7] = dhcpClientIdentifier; - buffer[8] = 0x07; - buffer[9] = 0x01; - memcpy(buffer + 10, _dhcpMacAddr, 6); - - // OPT - host name - buffer[16] = hostName; - buffer[17] = strlen(HOST_NAME) + 6; // length of hostname + last 3 bytes of mac address - strcpy((char*)&(buffer[18]), HOST_NAME); - - printByte((char*)&(buffer[24]), _dhcpMacAddr[3]); - printByte((char*)&(buffer[26]), _dhcpMacAddr[4]); - printByte((char*)&(buffer[28]), _dhcpMacAddr[5]); - - //put data in W5100 transmit buffer - _dhcpUdpSocket.write(buffer, 30); - - if (messageType == DHCP_REQUEST) { - buffer[0] = dhcpRequestedIPaddr; - buffer[1] = 0x04; - buffer[2] = _dhcpLocalIp[0]; - buffer[3] = _dhcpLocalIp[1]; - buffer[4] = _dhcpLocalIp[2]; - buffer[5] = _dhcpLocalIp[3]; - - buffer[6] = dhcpServerIdentifier; - buffer[7] = 0x04; - buffer[8] = _dhcpDhcpServerIp[0]; - buffer[9] = _dhcpDhcpServerIp[1]; - buffer[10] = _dhcpDhcpServerIp[2]; - buffer[11] = _dhcpDhcpServerIp[3]; - - //put data in W5100 transmit buffer - _dhcpUdpSocket.write(buffer, 12); - } - - buffer[0] = dhcpParamRequest; - buffer[1] = 0x06; - buffer[2] = subnetMask; - buffer[3] = routersOnSubnet; - buffer[4] = dns; - buffer[5] = domainName; - buffer[6] = dhcpT1value; - buffer[7] = dhcpT2value; - buffer[8] = endOption; - - //put data in W5100 transmit buffer - _dhcpUdpSocket.write(buffer, 9); - - _dhcpUdpSocket.endPacket(); -} - -uint8_t DhcpClass::parseDHCPResponse(unsigned long responseTimeout, uint32_t& transactionId) -{ - uint8_t type = 0; - uint8_t opt_len = 0; - - unsigned long startTime = millis(); - - while (_dhcpUdpSocket.parsePacket() <= 0) { - if ((millis() - startTime) > responseTimeout) { - return 255; - } - delay(50); - } - // start reading in the packet - RIP_MSG_FIXED fixedMsg; - _dhcpUdpSocket.read((uint8_t*)&fixedMsg, sizeof(RIP_MSG_FIXED)); - - if (fixedMsg.op == DHCP_BOOTREPLY && _dhcpUdpSocket.remotePort() == DHCP_SERVER_PORT) { - transactionId = ntohl(fixedMsg.xid); - if (memcmp(fixedMsg.chaddr, _dhcpMacAddr, 6) != 0 || - (transactionId < _dhcpInitialTransactionId) || - (transactionId > _dhcpTransactionId)) { - // Need to read the rest of the packet here regardless - _dhcpUdpSocket.flush(); // FIXME - return 0; - } - - memcpy(_dhcpLocalIp, fixedMsg.yiaddr, 4); - - // Skip to the option part - _dhcpUdpSocket.read((uint8_t *)NULL, 240 - (int)sizeof(RIP_MSG_FIXED)); - - while (_dhcpUdpSocket.available() > 0) { - switch (_dhcpUdpSocket.read()) { - case endOption : - break; - - case padOption : - break; - - case dhcpMessageType : - opt_len = _dhcpUdpSocket.read(); - type = _dhcpUdpSocket.read(); - break; - - case subnetMask : - opt_len = _dhcpUdpSocket.read(); - _dhcpUdpSocket.read(_dhcpSubnetMask, 4); - break; - - case routersOnSubnet : - opt_len = _dhcpUdpSocket.read(); - _dhcpUdpSocket.read(_dhcpGatewayIp, 4); - _dhcpUdpSocket.read((uint8_t *)NULL, opt_len - 4); - break; - - case dns : - opt_len = _dhcpUdpSocket.read(); - _dhcpUdpSocket.read(_dhcpDnsServerIp, 4); - _dhcpUdpSocket.read((uint8_t *)NULL, opt_len - 4); - break; - - case dhcpServerIdentifier : - opt_len = _dhcpUdpSocket.read(); - if ( IPAddress(_dhcpDhcpServerIp) == IPAddress((uint32_t)0) || - IPAddress(_dhcpDhcpServerIp) == _dhcpUdpSocket.remoteIP() ) { - _dhcpUdpSocket.read(_dhcpDhcpServerIp, sizeof(_dhcpDhcpServerIp)); - } else { - // Skip over the rest of this option - _dhcpUdpSocket.read((uint8_t *)NULL, opt_len); - } - break; - - case dhcpT1value : - opt_len = _dhcpUdpSocket.read(); - _dhcpUdpSocket.read((uint8_t*)&_dhcpT1, sizeof(_dhcpT1)); - _dhcpT1 = ntohl(_dhcpT1); - break; - - case dhcpT2value : - opt_len = _dhcpUdpSocket.read(); - _dhcpUdpSocket.read((uint8_t*)&_dhcpT2, sizeof(_dhcpT2)); - _dhcpT2 = ntohl(_dhcpT2); - break; - - case dhcpIPaddrLeaseTime : - opt_len = _dhcpUdpSocket.read(); - _dhcpUdpSocket.read((uint8_t*)&_dhcpLeaseTime, sizeof(_dhcpLeaseTime)); - _dhcpLeaseTime = ntohl(_dhcpLeaseTime); - _renewInSec = _dhcpLeaseTime; - break; - - default : - opt_len = _dhcpUdpSocket.read(); - // Skip over the rest of this option - _dhcpUdpSocket.read((uint8_t *)NULL, opt_len); - break; - } - } - } - - // Need to skip to end of the packet regardless here - _dhcpUdpSocket.flush(); // FIXME - - return type; -} - - -/* - returns: - 0/DHCP_CHECK_NONE: nothing happened - 1/DHCP_CHECK_RENEW_FAIL: renew failed - 2/DHCP_CHECK_RENEW_OK: renew success - 3/DHCP_CHECK_REBIND_FAIL: rebind fail - 4/DHCP_CHECK_REBIND_OK: rebind success -*/ -int DhcpClass::checkLease() -{ - int rc = DHCP_CHECK_NONE; - - unsigned long now = millis(); - unsigned long elapsed = now - _lastCheckLeaseMillis; - - // if more then one sec passed, reduce the counters accordingly - if (elapsed >= 1000) { - // set the new timestamps - _lastCheckLeaseMillis = now - (elapsed % 1000); - elapsed = elapsed / 1000; - - // decrease the counters by elapsed seconds - // we assume that the cycle time (elapsed) is fairly constant - // if the remainder is less than cycle time * 2 - // do it early instead of late - if (_renewInSec < elapsed * 2) { - _renewInSec = 0; - } else { - _renewInSec -= elapsed; - } - if (_rebindInSec < elapsed * 2) { - _rebindInSec = 0; - } else { - _rebindInSec -= elapsed; - } - } - - // if we have a lease but should renew, do it - if (_renewInSec == 0 &&_dhcp_state == STATE_DHCP_LEASED) { - _dhcp_state = STATE_DHCP_REREQUEST; - rc = 1 + request_DHCP_lease(); - } - - // if we have a lease or is renewing but should bind, do it - if (_rebindInSec == 0 && (_dhcp_state == STATE_DHCP_LEASED || - _dhcp_state == STATE_DHCP_START)) { - // this should basically restart completely - _dhcp_state = STATE_DHCP_START; - reset_DHCP_lease(); - rc = 3 + request_DHCP_lease(); - } - return rc; -} - -IPAddress DhcpClass::getLocalIp() -{ - return IPAddress(_dhcpLocalIp); -} - -IPAddress DhcpClass::getSubnetMask() -{ - return IPAddress(_dhcpSubnetMask); -} - -IPAddress DhcpClass::getGatewayIp() -{ - return IPAddress(_dhcpGatewayIp); -} - -IPAddress DhcpClass::getDhcpServerIp() -{ - return IPAddress(_dhcpDhcpServerIp); -} - -IPAddress DhcpClass::getDnsServerIp() -{ - return IPAddress(_dhcpDnsServerIp); -} - -void DhcpClass::printByte(char * buf, uint8_t n ) -{ - char *str = &buf[1]; - buf[0]='0'; - do { - unsigned long m = n; - n /= 16; - char c = m - 16 * n; - *str-- = c < 10 ? c + '0' : c + 'A' - 10; - } while(n); -} \ No newline at end of file diff --git a/lib/Ethernet/src/Dhcp.h b/lib/Ethernet/src/Dhcp.h deleted file mode 100644 index 43ec4f8..0000000 --- a/lib/Ethernet/src/Dhcp.h +++ /dev/null @@ -1,137 +0,0 @@ -// DHCP Library v0.3 - April 25, 2009 -// Author: Jordan Terrell - blog.jordanterrell.com - -#ifndef Dhcp_h -#define Dhcp_h - -/* DHCP state machine. */ -#define STATE_DHCP_START 0 -#define STATE_DHCP_DISCOVER 1 -#define STATE_DHCP_REQUEST 2 -#define STATE_DHCP_LEASED 3 -#define STATE_DHCP_REREQUEST 4 -#define STATE_DHCP_RELEASE 5 - -#define DHCP_FLAGSBROADCAST 0x8000 - -/* UDP port numbers for DHCP */ -#define DHCP_SERVER_PORT 67 /* from server to client */ -#define DHCP_CLIENT_PORT 68 /* from client to server */ - -/* DHCP message OP code */ -#define DHCP_BOOTREQUEST 1 -#define DHCP_BOOTREPLY 2 - -/* DHCP message type */ -#define DHCP_DISCOVER 1 -#define DHCP_OFFER 2 -#define DHCP_REQUEST 3 -#define DHCP_DECLINE 4 -#define DHCP_ACK 5 -#define DHCP_NAK 6 -#define DHCP_RELEASE 7 -#define DHCP_INFORM 8 - -#define DHCP_HTYPE10MB 1 -#define DHCP_HTYPE100MB 2 - -#define DHCP_HLENETHERNET 6 -#define DHCP_HOPS 0 -#define DHCP_SECS 0 - -#define MAGIC_COOKIE 0x63825363 -#define MAX_DHCP_OPT 16 - -#define HOST_NAME "WIZnet" -#define DEFAULT_LEASE (900) //default lease time in seconds - -#define DHCP_CHECK_NONE (0) -#define DHCP_CHECK_RENEW_FAIL (1) -#define DHCP_CHECK_RENEW_OK (2) -#define DHCP_CHECK_REBIND_FAIL (3) -#define DHCP_CHECK_REBIND_OK (4) - -enum -{ - padOption = 0, - subnetMask = 1, - timerOffset = 2, - routersOnSubnet = 3, - /* timeServer = 4, - nameServer = 5,*/ - dns = 6, - /*logServer = 7, - cookieServer = 8, - lprServer = 9, - impressServer = 10, - resourceLocationServer = 11,*/ - hostName = 12, - /*bootFileSize = 13, - meritDumpFile = 14,*/ - domainName = 15, - /*swapServer = 16, - rootPath = 17, - extentionsPath = 18, - IPforwarding = 19, - nonLocalSourceRouting = 20, - policyFilter = 21, - maxDgramReasmSize = 22, - defaultIPTTL = 23, - pathMTUagingTimeout = 24, - pathMTUplateauTable = 25, - ifMTU = 26, - allSubnetsLocal = 27, - broadcastAddr = 28, - performMaskDiscovery = 29, - maskSupplier = 30, - performRouterDiscovery = 31, - routerSolicitationAddr = 32, - staticRoute = 33, - trailerEncapsulation = 34, - arpCacheTimeout = 35, - ethernetEncapsulation = 36, - tcpDefaultTTL = 37, - tcpKeepaliveInterval = 38, - tcpKeepaliveGarbage = 39, - nisDomainName = 40, - nisServers = 41, - ntpServers = 42, - vendorSpecificInfo = 43, - netBIOSnameServer = 44, - netBIOSdgramDistServer = 45, - netBIOSnodeType = 46, - netBIOSscope = 47, - xFontServer = 48, - xDisplayManager = 49,*/ - dhcpRequestedIPaddr = 50, - dhcpIPaddrLeaseTime = 51, - /*dhcpOptionOverload = 52,*/ - dhcpMessageType = 53, - dhcpServerIdentifier = 54, - dhcpParamRequest = 55, - /*dhcpMsg = 56, - dhcpMaxMsgSize = 57,*/ - dhcpT1value = 58, - dhcpT2value = 59, - /*dhcpClassIdentifier = 60,*/ - dhcpClientIdentifier = 61, - endOption = 255 -}; - -typedef struct _RIP_MSG_FIXED -{ - uint8_t op; - uint8_t htype; - uint8_t hlen; - uint8_t hops; - uint32_t xid; - uint16_t secs; - uint16_t flags; - uint8_t ciaddr[4]; - uint8_t yiaddr[4]; - uint8_t siaddr[4]; - uint8_t giaddr[4]; - uint8_t chaddr[6]; -} RIP_MSG_FIXED; - -#endif diff --git a/lib/Ethernet/src/Dns.cpp b/lib/Ethernet/src/Dns.cpp deleted file mode 100644 index dca7ce4..0000000 --- a/lib/Ethernet/src/Dns.cpp +++ /dev/null @@ -1,353 +0,0 @@ -// Arduino DNS client for WIZnet W5100-based Ethernet shield -// (c) Copyright 2009-2010 MCQN Ltd. -// Released under Apache License, version 2.0 - -#include -#include "Ethernet.h" -#include "Dns.h" -#include "utility/w5100.h" - - -#define SOCKET_NONE 255 -// Various flags and header field values for a DNS message -#define UDP_HEADER_SIZE 8 -#define DNS_HEADER_SIZE 12 -#define TTL_SIZE 4 -#define QUERY_FLAG (0) -#define RESPONSE_FLAG (1<<15) -#define QUERY_RESPONSE_MASK (1<<15) -#define OPCODE_STANDARD_QUERY (0) -#define OPCODE_INVERSE_QUERY (1<<11) -#define OPCODE_STATUS_REQUEST (2<<11) -#define OPCODE_MASK (15<<11) -#define AUTHORITATIVE_FLAG (1<<10) -#define TRUNCATION_FLAG (1<<9) -#define RECURSION_DESIRED_FLAG (1<<8) -#define RECURSION_AVAILABLE_FLAG (1<<7) -#define RESP_NO_ERROR (0) -#define RESP_FORMAT_ERROR (1) -#define RESP_SERVER_FAILURE (2) -#define RESP_NAME_ERROR (3) -#define RESP_NOT_IMPLEMENTED (4) -#define RESP_REFUSED (5) -#define RESP_MASK (15) -#define TYPE_A (0x0001) -#define CLASS_IN (0x0001) -#define LABEL_COMPRESSION_MASK (0xC0) -// Port number that DNS servers listen on -#define DNS_PORT 53 - -// Possible return codes from ProcessResponse -#define SUCCESS 1 -#define TIMED_OUT -1 -#define INVALID_SERVER -2 -#define TRUNCATED -3 -#define INVALID_RESPONSE -4 - -void DNSClient::begin(const IPAddress& aDNSServer) -{ - iDNSServer = aDNSServer; - iRequestId = 0; -} - - -int DNSClient::inet_aton(const char* address, IPAddress& result) -{ - uint16_t acc = 0; // Accumulator - uint8_t dots = 0; - - while (*address) { - char c = *address++; - if (c >= '0' && c <= '9') { - acc = acc * 10 + (c - '0'); - if (acc > 255) { - // Value out of [0..255] range - return 0; - } - } else if (c == '.') { - if (dots == 3) { - // Too much dots (there must be 3 dots) - return 0; - } - result[dots++] = acc; - acc = 0; - } else { - // Invalid char - return 0; - } - } - - if (dots != 3) { - // Too few dots (there must be 3 dots) - return 0; - } - result[3] = acc; - return 1; -} - -int DNSClient::getHostByName(const char* aHostname, IPAddress& aResult, uint16_t timeout) -{ - int ret = 0; - - // See if it's a numeric IP address - if (inet_aton(aHostname, aResult)) { - // It is, our work here is done - return 1; - } - - // Check we've got a valid DNS server to use - if (iDNSServer == INADDR_NONE) { - return INVALID_SERVER; - } - - // Find a socket to use - if (iUdp.begin(1024+(millis() & 0xF)) == 1) { - // Try up to three times - int retries = 0; - // while ((retries < 3) && (ret <= 0)) { - // Send DNS request - ret = iUdp.beginPacket(iDNSServer, DNS_PORT); - if (ret != 0) { - // Now output the request data - ret = BuildRequest(aHostname); - if (ret != 0) { - // And finally send the request - ret = iUdp.endPacket(); - if (ret != 0) { - // Now wait for a response - int wait_retries = 0; - ret = TIMED_OUT; - while ((wait_retries < 3) && (ret == TIMED_OUT)) { - ret = ProcessResponse(timeout, aResult); - wait_retries++; - } - } - } - } - retries++; - //} - - // We're done with the socket now - iUdp.stop(); - } - - return ret; -} - -uint16_t DNSClient::BuildRequest(const char* aName) -{ - // Build header - // 1 1 1 1 1 1 - // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // | ID | - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // |QR| Opcode |AA|TC|RD|RA| Z | RCODE | - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // | QDCOUNT | - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // | ANCOUNT | - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // | NSCOUNT | - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // | ARCOUNT | - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // As we only support one request at a time at present, we can simplify - // some of this header - iRequestId = millis(); // generate a random ID - uint16_t twoByteBuffer; - - // FIXME We should also check that there's enough space available to write to, rather - // FIXME than assume there's enough space (as the code does at present) - iUdp.write((uint8_t*)&iRequestId, sizeof(iRequestId)); - - twoByteBuffer = htons(QUERY_FLAG | OPCODE_STANDARD_QUERY | RECURSION_DESIRED_FLAG); - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - - twoByteBuffer = htons(1); // One question record - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - - twoByteBuffer = 0; // Zero answer records - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - // and zero additional records - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - - // Build question - const char* start =aName; - const char* end =start; - uint8_t len; - // Run through the name being requested - while (*end) { - // Find out how long this section of the name is - end = start; - while (*end && (*end != '.') ) { - end++; - } - - if (end-start > 0) { - // Write out the size of this section - len = end-start; - iUdp.write(&len, sizeof(len)); - // And then write out the section - iUdp.write((uint8_t*)start, end-start); - } - start = end+1; - } - - // We've got to the end of the question name, so - // terminate it with a zero-length section - len = 0; - iUdp.write(&len, sizeof(len)); - // Finally the type and class of question - twoByteBuffer = htons(TYPE_A); - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - - twoByteBuffer = htons(CLASS_IN); // Internet class of question - iUdp.write((uint8_t*)&twoByteBuffer, sizeof(twoByteBuffer)); - // Success! Everything buffered okay - return 1; -} - - -uint16_t DNSClient::ProcessResponse(uint16_t aTimeout, IPAddress& aAddress) -{ - uint32_t startTime = millis(); - - // Wait for a response packet - while (iUdp.parsePacket() <= 0) { - if ((millis() - startTime) > aTimeout) { - return TIMED_OUT; - } - delay(50); - } - - // We've had a reply! - // Read the UDP header - //uint8_t header[DNS_HEADER_SIZE]; // Enough space to reuse for the DNS header - union { - uint8_t byte[DNS_HEADER_SIZE]; // Enough space to reuse for the DNS header - uint16_t word[DNS_HEADER_SIZE/2]; - } header; - - // Check that it's a response from the right server and the right port - if ( (iDNSServer != iUdp.remoteIP()) || (iUdp.remotePort() != DNS_PORT) ) { - // It's not from who we expected - return INVALID_SERVER; - } - - // Read through the rest of the response - if (iUdp.available() < DNS_HEADER_SIZE) { - return TRUNCATED; - } - iUdp.read(header.byte, DNS_HEADER_SIZE); - - uint16_t header_flags = htons(header.word[1]); - // Check that it's a response to this request - if ((iRequestId != (header.word[0])) || - ((header_flags & QUERY_RESPONSE_MASK) != (uint16_t)RESPONSE_FLAG) ) { - // Mark the entire packet as read - iUdp.flush(); // FIXME - return INVALID_RESPONSE; - } - // Check for any errors in the response (or in our request) - // although we don't do anything to get round these - if ( (header_flags & TRUNCATION_FLAG) || (header_flags & RESP_MASK) ) { - // Mark the entire packet as read - iUdp.flush(); // FIXME - return -5; //INVALID_RESPONSE; - } - - // And make sure we've got (at least) one answer - uint16_t answerCount = htons(header.word[3]); - if (answerCount == 0) { - // Mark the entire packet as read - iUdp.flush(); // FIXME - return -6; //INVALID_RESPONSE; - } - - // Skip over any questions - for (uint16_t i=0; i < htons(header.word[2]); i++) { - // Skip over the name - uint8_t len; - do { - iUdp.read(&len, sizeof(len)); - if (len > 0) { - // Don't need to actually read the data out for the string, just - // advance ptr to beyond it - iUdp.read((uint8_t *)NULL, (size_t)len); - } - } while (len != 0); - - // Now jump over the type and class - iUdp.read((uint8_t *)NULL, 4); - } - - // Now we're up to the bit we're interested in, the answer - // There might be more than one answer (although we'll just use the first - // type A answer) and some authority and additional resource records but - // we're going to ignore all of them. - - for (uint16_t i=0; i < answerCount; i++) { - // Skip the name - uint8_t len; - do { - iUdp.read(&len, sizeof(len)); - if ((len & LABEL_COMPRESSION_MASK) == 0) { - // It's just a normal label - if (len > 0) { - // And it's got a length - // Don't need to actually read the data out for the string, - // just advance ptr to beyond it - iUdp.read((uint8_t *)NULL, len); - } - } else { - // This is a pointer to a somewhere else in the message for the - // rest of the name. We don't care about the name, and RFC1035 - // says that a name is either a sequence of labels ended with a - // 0 length octet or a pointer or a sequence of labels ending in - // a pointer. Either way, when we get here we're at the end of - // the name - // Skip over the pointer - iUdp.read((uint8_t *)NULL, 1); // we don't care about the byte - // And set len so that we drop out of the name loop - len = 0; - } - } while (len != 0); - - // Check the type and class - uint16_t answerType; - uint16_t answerClass; - iUdp.read((uint8_t*)&answerType, sizeof(answerType)); - iUdp.read((uint8_t*)&answerClass, sizeof(answerClass)); - - // Ignore the Time-To-Live as we don't do any caching - iUdp.read((uint8_t *)NULL, TTL_SIZE); // don't care about the returned bytes - - // And read out the length of this answer - // Don't need header_flags anymore, so we can reuse it here - iUdp.read((uint8_t*)&header_flags, sizeof(header_flags)); - - if ( (htons(answerType) == TYPE_A) && (htons(answerClass) == CLASS_IN) ) { - if (htons(header_flags) != 4) { - // It's a weird size - // Mark the entire packet as read - iUdp.flush(); // FIXME - return -9;//INVALID_RESPONSE; - } - // FIXME: seems to lock up here on ESP8266, but why?? - iUdp.read(aAddress.raw_address(), 4); - return SUCCESS; - } else { - // This isn't an answer type we're after, move onto the next one - iUdp.read((uint8_t *)NULL, htons(header_flags)); - } - } - - // Mark the entire packet as read - iUdp.flush(); // FIXME - - // If we get here then we haven't found an answer - return -10; //INVALID_RESPONSE; -} diff --git a/lib/Ethernet/src/Dns.h b/lib/Ethernet/src/Dns.h deleted file mode 100644 index 58f9d2c..0000000 --- a/lib/Ethernet/src/Dns.h +++ /dev/null @@ -1,40 +0,0 @@ -// Arduino DNS client for WIZnet W5100-based Ethernet shield -// (c) Copyright 2009-2010 MCQN Ltd. -// Released under Apache License, version 2.0 - -#ifndef DNSClient_h -#define DNSClient_h - -#include "Ethernet.h" - -class DNSClient -{ -public: - void begin(const IPAddress& aDNSServer); - - /** Convert a numeric IP address string into a four-byte IP address. - @param aIPAddrString IP address to convert - @param aResult IPAddress structure to store the returned IP address - @result 1 if aIPAddrString was successfully converted to an IP address, - else error code - */ - int inet_aton(const char *aIPAddrString, IPAddress& aResult); - - /** Resolve the given hostname to an IP address. - @param aHostname Name to be resolved - @param aResult IPAddress structure to store the returned IP address - @result 1 if aIPAddrString was successfully converted to an IP address, - else error code - */ - int getHostByName(const char* aHostname, IPAddress& aResult, uint16_t timeout=5000); - -protected: - uint16_t BuildRequest(const char* aName); - uint16_t ProcessResponse(uint16_t aTimeout, IPAddress& aAddress); - - IPAddress iDNSServer; - uint16_t iRequestId; - EthernetUDP iUdp; -}; - -#endif diff --git a/lib/Ethernet/src/Ethernet.cpp b/lib/Ethernet/src/Ethernet.cpp deleted file mode 100644 index 8f6f76a..0000000 --- a/lib/Ethernet/src/Ethernet.cpp +++ /dev/null @@ -1,243 +0,0 @@ -/* Copyright 2018 Paul Stoffregen - * - * 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. - */ - -#include -#include "Ethernet.h" -#include "utility/w5100.h" -#include "Dhcp.h" - -IPAddress EthernetClass::_dnsServerAddress; -DhcpClass* EthernetClass::_dhcp = NULL; - -int EthernetClass::begin(uint8_t *mac, unsigned long timeout, unsigned long responseTimeout) -{ - static DhcpClass s_dhcp; - _dhcp = &s_dhcp; - - // Initialise the basic info - if (W5100.init() == 0) return 0; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.setMACAddress(mac); - W5100.setIPAddress(IPAddress(0,0,0,0).raw_address()); - SPI.endTransaction(); - - // Now try to get our config info from a DHCP server - int ret = _dhcp->beginWithDHCP(mac, timeout, responseTimeout); - if (ret == 1) { - // We've successfully found a DHCP server and got our configuration - // info, so set things accordingly - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.setIPAddress(_dhcp->getLocalIp().raw_address()); - W5100.setGatewayIp(_dhcp->getGatewayIp().raw_address()); - W5100.setSubnetMask(_dhcp->getSubnetMask().raw_address()); - SPI.endTransaction(); - _dnsServerAddress = _dhcp->getDnsServerIp(); - socketPortRand(micros()); - } - return ret; -} - -void EthernetClass::begin(uint8_t *mac, IPAddress ip) -{ - // Assume the DNS server will be the machine on the same network as the local IP - // but with last octet being '1' - IPAddress dns = ip; - dns[3] = 1; - begin(mac, ip, dns); -} - -void EthernetClass::begin(uint8_t *mac, IPAddress ip, IPAddress dns) -{ - // Assume the gateway will be the machine on the same network as the local IP - // but with last octet being '1' - IPAddress gateway = ip; - gateway[3] = 1; - begin(mac, ip, dns, gateway); -} - -void EthernetClass::begin(uint8_t *mac, IPAddress ip, IPAddress dns, IPAddress gateway) -{ - IPAddress subnet(255, 255, 255, 0); - begin(mac, ip, dns, gateway, subnet); -} - -void EthernetClass::begin(uint8_t *mac, IPAddress ip, IPAddress dns, IPAddress gateway, IPAddress subnet) -{ - if (W5100.init() == 0) return; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.setMACAddress(mac); -#ifdef ESP8266 - W5100.setIPAddress(&ip[0]); - W5100.setGatewayIp(&gateway[0]); - W5100.setSubnetMask(&subnet[0]); -#else - W5100.setIPAddress(ip.raw_address()); - W5100.setGatewayIp(gateway.raw_address()); - W5100.setSubnetMask(subnet.raw_address()); -#endif - SPI.endTransaction(); - _dnsServerAddress = dns; -} - -void EthernetClass::init(uint8_t sspin, uint8_t sckpin, uint8_t misopin, uint8_t mosipin) -{ - W5100.setSS(sspin); - W5100.setSPI(sckpin, misopin, mosipin); -} - -EthernetLinkStatus EthernetClass::linkStatus() -{ - switch (W5100.getLinkStatus()) { - case UNKNOWN: return Unknown; - case LINK_ON: return LinkON; - case LINK_OFF: return LinkOFF; - default: return Unknown; - } -} - -EthernetHardwareStatus EthernetClass::hardwareStatus() -{ - switch (W5100.getChip()) { - case 51: return EthernetW5100; - case 52: return EthernetW5200; - case 55: return EthernetW5500; - default: return EthernetNoHardware; - } -} - -int EthernetClass::maintain() -{ - int rc = DHCP_CHECK_NONE; - if (_dhcp != NULL) { - // we have a pointer to dhcp, use it - rc = _dhcp->checkLease(); - switch (rc) { - case DHCP_CHECK_NONE: - //nothing done - break; - case DHCP_CHECK_RENEW_OK: - case DHCP_CHECK_REBIND_OK: - //we might have got a new IP. - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.setIPAddress(_dhcp->getLocalIp().raw_address()); - W5100.setGatewayIp(_dhcp->getGatewayIp().raw_address()); - W5100.setSubnetMask(_dhcp->getSubnetMask().raw_address()); - SPI.endTransaction(); - _dnsServerAddress = _dhcp->getDnsServerIp(); - break; - default: - //this is actually an error, it will retry though - break; - } - } - return rc; -} - - -void EthernetClass::MACAddress(uint8_t *mac_address) -{ - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.getMACAddress(mac_address); - SPI.endTransaction(); -} - -IPAddress EthernetClass::localIP() -{ - IPAddress ret; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.getIPAddress(ret.raw_address()); - SPI.endTransaction(); - return ret; -} - -IPAddress EthernetClass::subnetMask() -{ - IPAddress ret; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.getSubnetMask(ret.raw_address()); - SPI.endTransaction(); - return ret; -} - -IPAddress EthernetClass::gatewayIP() -{ - IPAddress ret; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.getGatewayIp(ret.raw_address()); - SPI.endTransaction(); - return ret; -} - -void EthernetClass::setMACAddress(const uint8_t *mac_address) -{ - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.setMACAddress(mac_address); - SPI.endTransaction(); -} - -void EthernetClass::setLocalIP(const IPAddress local_ip) -{ - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - IPAddress ip = local_ip; - W5100.setIPAddress(ip.raw_address()); - SPI.endTransaction(); -} - -void EthernetClass::setSubnetMask(const IPAddress subnet) -{ - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - IPAddress ip = subnet; - W5100.setSubnetMask(ip.raw_address()); - SPI.endTransaction(); -} - -void EthernetClass::setGatewayIP(const IPAddress gateway) -{ - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - IPAddress ip = gateway; - W5100.setGatewayIp(ip.raw_address()); - SPI.endTransaction(); -} - -void EthernetClass::setRetransmissionTimeout(uint16_t milliseconds) -{ - if (milliseconds > 6553) milliseconds = 6553; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.setRetransmissionTime(milliseconds * 10); - SPI.endTransaction(); -} - -void EthernetClass::setRetransmissionCount(uint8_t num) -{ - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.setRetransmissionCount(num); - SPI.endTransaction(); -} - - - - - - - - - - -EthernetClass Ethernet; \ No newline at end of file diff --git a/lib/Ethernet/src/Ethernet.h b/lib/Ethernet/src/Ethernet.h deleted file mode 100644 index 5a1e780..0000000 --- a/lib/Ethernet/src/Ethernet.h +++ /dev/null @@ -1,323 +0,0 @@ -/* Copyright 2018 Paul Stoffregen - * - * 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. - */ - -#ifndef ethernet_h_ -#define ethernet_h_ - -// All symbols exposed to Arduino sketches are contained in this header file -// -// Older versions had much of this stuff in EthernetClient.h, EthernetServer.h, -// and socket.h. Including headers in different order could cause trouble, so -// these "friend" classes are now defined in the same header file. socket.h -// was removed to avoid possible conflict with the C library header files. - - -// Configure the maximum number of sockets to support. W5100 chips can have -// up to 4 sockets. W5200 & W5500 can have up to 8 sockets. Several bytes -// of RAM are used for each socket. Reducing the maximum can save RAM, but -// you are limited to fewer simultaneous connections. -#if defined(RAMEND) && defined(RAMSTART) && ((RAMEND - RAMSTART) <= 2048) -#define MAX_SOCK_NUM 4 -#else -#define MAX_SOCK_NUM 8 -#endif - -// By default, each socket uses 2K buffers inside the WIZnet chip. If -// MAX_SOCK_NUM is set to fewer than the chip's maximum, uncommenting -// this will use larger buffers within the WIZnet chip. Large buffers -// can really help with UDP protocols like Artnet. In theory larger -// buffers should allow faster TCP over high-latency links, but this -// does not always seem to work in practice (maybe WIZnet bugs?) -//#define ETHERNET_LARGE_BUFFERS - - -#include -#include "Client.h" -#include "Server.h" -#include "Udp.h" - -enum EthernetLinkStatus { - Unknown, - LinkON, - LinkOFF -}; - -enum EthernetHardwareStatus { - EthernetNoHardware, - EthernetW5100, - EthernetW5200, - EthernetW5500 -}; - -class EthernetUDP; -class EthernetClient; -class EthernetServer; -class DhcpClass; - -class EthernetClass { -private: - static IPAddress _dnsServerAddress; - static DhcpClass* _dhcp; -public: - // Initialise the Ethernet shield to use the provided MAC address and - // gain the rest of the configuration through DHCP. - // Returns 0 if the DHCP configuration failed, and 1 if it succeeded - static int begin(uint8_t *mac, unsigned long timeout = 60000, unsigned long responseTimeout = 4000); - static int maintain(); - static EthernetLinkStatus linkStatus(); - static EthernetHardwareStatus hardwareStatus(); - - // Manual configuration - static void begin(uint8_t *mac, IPAddress ip); - static void begin(uint8_t *mac, IPAddress ip, IPAddress dns); - static void begin(uint8_t *mac, IPAddress ip, IPAddress dns, IPAddress gateway); - static void begin(uint8_t *mac, IPAddress ip, IPAddress dns, IPAddress gateway, IPAddress subnet); - static void init(uint8_t sspin = 10, uint8_t sckpin = 255, uint8_t misopin = 255, uint8_t mosipin = 255); - - static void MACAddress(uint8_t *mac_address); - static IPAddress localIP(); - static IPAddress subnetMask(); - static IPAddress gatewayIP(); - static IPAddress dnsServerIP() { return _dnsServerAddress; } - - void setMACAddress(const uint8_t *mac_address); - void setLocalIP(const IPAddress local_ip); - void setSubnetMask(const IPAddress subnet); - void setGatewayIP(const IPAddress gateway); - void setDnsServerIP(const IPAddress dns_server) { _dnsServerAddress = dns_server; } - void setRetransmissionTimeout(uint16_t milliseconds); - void setRetransmissionCount(uint8_t num); - - friend class EthernetClient; - friend class EthernetServer; - friend class EthernetUDP; -private: - // Opens a socket(TCP or UDP or IP_RAW mode) - static uint8_t socketBegin(uint8_t protocol, uint16_t port); - static uint8_t socketBeginMulticast(uint8_t protocol, IPAddress ip,uint16_t port); - static uint8_t socketStatus(uint8_t s); - // Close socket - static void socketClose(uint8_t s); - // Establish TCP connection (Active connection) - static void socketConnect(uint8_t s, uint8_t * addr, uint16_t port); - // disconnect the connection - static void socketDisconnect(uint8_t s); - // Establish TCP connection (Passive connection) - static uint8_t socketListen(uint8_t s); - // Send data (TCP) - static uint16_t socketSend(uint8_t s, const uint8_t * buf, uint16_t len); - static uint16_t socketSendAvailable(uint8_t s); - // Receive data (TCP) - static int socketRecv(uint8_t s, uint8_t * buf, int16_t len); - static uint16_t socketRecvAvailable(uint8_t s); - static uint8_t socketPeek(uint8_t s); - // sets up a UDP datagram, the data for which will be provided by one - // or more calls to bufferData and then finally sent with sendUDP. - // return true if the datagram was successfully set up, or false if there was an error - static bool socketStartUDP(uint8_t s, uint8_t* addr, uint16_t port); - // copy up to len bytes of data from buf into a UDP datagram to be - // sent later by sendUDP. Allows datagrams to be built up from a series of bufferData calls. - // return Number of bytes successfully buffered - static uint16_t socketBufferData(uint8_t s, uint16_t offset, const uint8_t* buf, uint16_t len); - // Send a UDP datagram built up from a sequence of startUDP followed by one or more - // calls to bufferData. - // return true if the datagram was successfully sent, or false if there was an error - static bool socketSendUDP(uint8_t s); - // Initialize the "random" source port number - static void socketPortRand(uint16_t n); -}; - -extern EthernetClass Ethernet; - - -#define UDP_TX_PACKET_MAX_SIZE 24 - -class EthernetUDP : public UDP { -private: - uint16_t _port; // local port to listen on - IPAddress _remoteIP; // remote IP address for the incoming packet whilst it's being processed - uint16_t _remotePort; // remote port for the incoming packet whilst it's being processed - uint16_t _offset; // offset into the packet being sent - -protected: - uint8_t sockindex; - uint16_t _remaining; // remaining bytes of incoming packet yet to be processed - -public: - EthernetUDP() : sockindex(MAX_SOCK_NUM) {} // Constructor - virtual uint8_t begin(uint16_t); // initialize, start listening on specified port. Returns 1 if successful, 0 if there are no sockets available to use - virtual uint8_t beginMulticast(IPAddress, uint16_t); // initialize, start listening on specified port. Returns 1 if successful, 0 if there are no sockets available to use - virtual void stop(); // Finish with the UDP socket - - // Sending UDP packets - - // Start building up a packet to send to the remote host specific in ip and port - // Returns 1 if successful, 0 if there was a problem with the supplied IP address or port - virtual int beginPacket(IPAddress ip, uint16_t port); - // Start building up a packet to send to the remote host specific in host and port - // Returns 1 if successful, 0 if there was a problem resolving the hostname or port - virtual int beginPacket(const char *host, uint16_t port); - // Finish off this packet and send it - // Returns 1 if the packet was sent successfully, 0 if there was an error - virtual int endPacket(); - // Write a single byte into the packet - virtual size_t write(uint8_t); - // Write size bytes from buffer into the packet - virtual size_t write(const uint8_t *buffer, size_t size); - - using Print::write; - - // Start processing the next available incoming packet - // Returns the size of the packet in bytes, or 0 if no packets are available - virtual int parsePacket(); - // Number of bytes remaining in the current packet - virtual int available(); - // Read a single byte from the current packet - virtual int read(); - // Read up to len bytes from the current packet and place them into buffer - // Returns the number of bytes read, or 0 if none are available - virtual int read(unsigned char* buffer, size_t len); - // Read up to len characters from the current packet and place them into buffer - // Returns the number of characters read, or 0 if none are available - virtual int read(char* buffer, size_t len) { return read((unsigned char*)buffer, len); }; - // Return the next byte from the current packet without moving on to the next byte - virtual int peek(); - virtual void flush(); // Finish reading the current packet - - // Return the IP address of the host who sent the current incoming packet - virtual IPAddress remoteIP() { return _remoteIP; }; - // Return the port of the host who sent the current incoming packet - virtual uint16_t remotePort() { return _remotePort; }; - virtual uint16_t localPort() { return _port; } -}; - - - - -class EthernetClient : public Client { -public: - EthernetClient() : _sockindex(MAX_SOCK_NUM), _timeout(1000) { } - EthernetClient(uint8_t s) : _sockindex(s), _timeout(1000) { } - virtual ~EthernetClient() {}; - - uint8_t status(); - virtual int connect(IPAddress ip, uint16_t port); - virtual int connect(const char *host, uint16_t port); - virtual int availableForWrite(void); - virtual size_t write(uint8_t); - virtual size_t write(const uint8_t *buf, size_t size); - virtual int available(); - virtual int read(); - virtual int read(uint8_t *buf, size_t size); - virtual int peek(); - virtual void flush(); - virtual void stop(); - virtual uint8_t connected(); - virtual operator bool() { return _sockindex < MAX_SOCK_NUM; } - virtual bool operator==(const bool value) { return bool() == value; } - virtual bool operator!=(const bool value) { return bool() != value; } - virtual bool operator==(const EthernetClient&); - virtual bool operator!=(const EthernetClient& rhs) { return !this->operator==(rhs); } - uint8_t getSocketNumber() const { return _sockindex; } - virtual uint16_t localPort(); - virtual IPAddress remoteIP(); - virtual uint16_t remotePort(); - virtual void setConnectionTimeout(uint16_t timeout) { _timeout = timeout; } - - friend class EthernetServer; - - using Print::write; - -private: - uint8_t _sockindex; // MAX_SOCK_NUM means client not in use - uint16_t _timeout; -}; - - -class EthernetServer : public Server { -private: - uint16_t _port; -public: - EthernetServer(uint16_t port) : _port(port) { } - EthernetClient available(); - EthernetClient accept(); - virtual void begin(); - virtual size_t write(uint8_t); - virtual size_t write(const uint8_t *buf, size_t size); - virtual operator bool(); - using Print::write; - //void statusreport(); - - // TODO: make private when socket allocation moves to EthernetClass - static uint16_t server_port[MAX_SOCK_NUM]; -}; - - -class DhcpClass { -private: - uint32_t _dhcpInitialTransactionId; - uint32_t _dhcpTransactionId; - uint8_t _dhcpMacAddr[6]; -#ifdef __arm__ - uint8_t _dhcpLocalIp[4] __attribute__((aligned(4))); - uint8_t _dhcpSubnetMask[4] __attribute__((aligned(4))); - uint8_t _dhcpGatewayIp[4] __attribute__((aligned(4))); - uint8_t _dhcpDhcpServerIp[4] __attribute__((aligned(4))); - uint8_t _dhcpDnsServerIp[4] __attribute__((aligned(4))); -#else - uint8_t _dhcpLocalIp[4]; - uint8_t _dhcpSubnetMask[4]; - uint8_t _dhcpGatewayIp[4]; - uint8_t _dhcpDhcpServerIp[4]; - uint8_t _dhcpDnsServerIp[4]; -#endif - uint32_t _dhcpLeaseTime; - uint32_t _dhcpT1, _dhcpT2; - uint32_t _renewInSec; - uint32_t _rebindInSec; - unsigned long _timeout; - unsigned long _responseTimeout; - unsigned long _lastCheckLeaseMillis; - uint8_t _dhcp_state; - EthernetUDP _dhcpUdpSocket; - - int request_DHCP_lease(); - void reset_DHCP_lease(); - void presend_DHCP(); - void send_DHCP_MESSAGE(uint8_t, uint16_t); - void printByte(char *, uint8_t); - - uint8_t parseDHCPResponse(unsigned long responseTimeout, uint32_t& transactionId); -public: - IPAddress getLocalIp(); - IPAddress getSubnetMask(); - IPAddress getGatewayIp(); - IPAddress getDhcpServerIp(); - IPAddress getDnsServerIp(); - - int beginWithDHCP(uint8_t *, unsigned long timeout = 60000, unsigned long responseTimeout = 4000); - int checkLease(); -}; - - - - - -#endif diff --git a/lib/Ethernet/src/EthernetClient.cpp b/lib/Ethernet/src/EthernetClient.cpp deleted file mode 100644 index 5a20c74..0000000 --- a/lib/Ethernet/src/EthernetClient.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/* Copyright 2018 Paul Stoffregen - * - * 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. - */ - -#include -#include "Ethernet.h" -#include "Dns.h" -#include "utility/w5100.h" - -int EthernetClient::connect(const char * host, uint16_t port) -{ - DNSClient dns; // Look up the host first - IPAddress remote_addr; - - if (_sockindex < MAX_SOCK_NUM) { - if (Ethernet.socketStatus(_sockindex) != SnSR::CLOSED) { - Ethernet.socketDisconnect(_sockindex); // TODO: should we call stop()? - } - _sockindex = MAX_SOCK_NUM; - } - dns.begin(Ethernet.dnsServerIP()); - if (!dns.getHostByName(host, remote_addr)) return 0; // TODO: use _timeout - return connect(remote_addr, port); -} - -int EthernetClient::connect(IPAddress ip, uint16_t port) -{ - if (_sockindex < MAX_SOCK_NUM) { - if (Ethernet.socketStatus(_sockindex) != SnSR::CLOSED) { - Ethernet.socketDisconnect(_sockindex); // TODO: should we call stop()? - } - _sockindex = MAX_SOCK_NUM; - } -#if defined(ESP8266) || defined(ESP32) - if (ip == IPAddress((uint32_t)0) || ip == IPAddress(0xFFFFFFFFul)) return 0; -#else - if (ip == IPAddress(0ul) || ip == IPAddress(0xFFFFFFFFul)) return 0; -#endif - _sockindex = Ethernet.socketBegin(SnMR::TCP, 0); - if (_sockindex >= MAX_SOCK_NUM) return 0; - Ethernet.socketConnect(_sockindex, rawIPAddress(ip), port); - uint32_t start = millis(); - while (1) { - uint8_t stat = Ethernet.socketStatus(_sockindex); - if (stat == SnSR::ESTABLISHED) return 1; - if (stat == SnSR::CLOSE_WAIT) return 1; - if (stat == SnSR::CLOSED) return 0; - if (millis() - start > _timeout) break; - delay(1); - } - Ethernet.socketClose(_sockindex); - _sockindex = MAX_SOCK_NUM; - return 0; -} - -int EthernetClient::availableForWrite(void) -{ - if (_sockindex >= MAX_SOCK_NUM) return 0; - return Ethernet.socketSendAvailable(_sockindex); -} - -size_t EthernetClient::write(uint8_t b) -{ - return write(&b, 1); -} - -size_t EthernetClient::write(const uint8_t *buf, size_t size) -{ - if (_sockindex >= MAX_SOCK_NUM) return 0; - if (Ethernet.socketSend(_sockindex, buf, size)) return size; - setWriteError(); - return 0; -} - -int EthernetClient::available() -{ - if (_sockindex >= MAX_SOCK_NUM) return 0; - return Ethernet.socketRecvAvailable(_sockindex); - // TODO: do the WIZnet chips automatically retransmit TCP ACK - // packets if they are lost by the network? Someday this should - // be checked by a man-in-the-middle test which discards certain - // packets. If ACKs aren't resent, we would need to check for - // returning 0 here and after a timeout do another Sock_RECV - // command to cause the WIZnet chip to resend the ACK packet. -} - -int EthernetClient::read(uint8_t *buf, size_t size) -{ - if (_sockindex >= MAX_SOCK_NUM) return 0; - return Ethernet.socketRecv(_sockindex, buf, size); -} - -int EthernetClient::peek() -{ - if (_sockindex >= MAX_SOCK_NUM) return -1; - if (!available()) return -1; - return Ethernet.socketPeek(_sockindex); -} - -int EthernetClient::read() -{ - uint8_t b; - if (Ethernet.socketRecv(_sockindex, &b, 1) > 0) return b; - return -1; -} - -void EthernetClient::flush() -{ - while (_sockindex < MAX_SOCK_NUM) { - uint8_t stat = Ethernet.socketStatus(_sockindex); - if (stat != SnSR::ESTABLISHED && stat != SnSR::CLOSE_WAIT) return; - if (Ethernet.socketSendAvailable(_sockindex) >= W5100.SSIZE) return; - } -} - -void EthernetClient::stop() -{ - if (_sockindex >= MAX_SOCK_NUM) return; - - // attempt to close the connection gracefully (send a FIN to other side) - Ethernet.socketDisconnect(_sockindex); - unsigned long start = millis(); - - // wait up to a second for the connection to close - do { - if (Ethernet.socketStatus(_sockindex) == SnSR::CLOSED) { - _sockindex = MAX_SOCK_NUM; - return; // exit the loop - } - delay(1); - } while (millis() - start < _timeout); - - // if it hasn't closed, close it forcefully - Ethernet.socketClose(_sockindex); - _sockindex = MAX_SOCK_NUM; -} - -uint8_t EthernetClient::connected() -{ - if (_sockindex >= MAX_SOCK_NUM) return 0; - - uint8_t s = Ethernet.socketStatus(_sockindex); - return !(s == SnSR::LISTEN || s == SnSR::CLOSED || s == SnSR::FIN_WAIT || - (s == SnSR::CLOSE_WAIT && !available())); -} - -uint8_t EthernetClient::status() -{ - if (_sockindex >= MAX_SOCK_NUM) return SnSR::CLOSED; - return Ethernet.socketStatus(_sockindex); -} - -// the next function allows us to use the client returned by -// EthernetServer::available() as the condition in an if-statement. -bool EthernetClient::operator==(const EthernetClient& rhs) -{ - if (_sockindex != rhs._sockindex) return false; - if (_sockindex >= MAX_SOCK_NUM) return false; - if (rhs._sockindex >= MAX_SOCK_NUM) return false; - return true; -} - -// https://github.com/per1234/EthernetMod -// from: https://github.com/ntruchsess/Arduino-1/commit/937bce1a0bb2567f6d03b15df79525569377dabd -uint16_t EthernetClient::localPort() -{ - if (_sockindex >= MAX_SOCK_NUM) return 0; - uint16_t port; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - port = W5100.readSnPORT(_sockindex); - SPI.endTransaction(); - return port; -} - -// https://github.com/per1234/EthernetMod -// returns the remote IP address: https://forum.arduino.cc/index.php?topic=82416.0 -IPAddress EthernetClient::remoteIP() -{ - if (_sockindex >= MAX_SOCK_NUM) return IPAddress((uint32_t)0); - uint8_t remoteIParray[4]; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.readSnDIPR(_sockindex, remoteIParray); - SPI.endTransaction(); - return IPAddress(remoteIParray); -} - -// https://github.com/per1234/EthernetMod -// from: https://github.com/ntruchsess/Arduino-1/commit/ca37de4ba4ecbdb941f14ac1fe7dd40f3008af75 -uint16_t EthernetClient::remotePort() -{ - if (_sockindex >= MAX_SOCK_NUM) return 0; - uint16_t port; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - port = W5100.readSnDPORT(_sockindex); - SPI.endTransaction(); - return port; -} diff --git a/lib/Ethernet/src/EthernetClient.h b/lib/Ethernet/src/EthernetClient.h deleted file mode 100644 index b5aef96..0000000 --- a/lib/Ethernet/src/EthernetClient.h +++ /dev/null @@ -1,3 +0,0 @@ -// This file is in the public domain. No copyright is claimed. - -#include "Ethernet.h" diff --git a/lib/Ethernet/src/EthernetServer.cpp b/lib/Ethernet/src/EthernetServer.cpp deleted file mode 100644 index ddebd15..0000000 --- a/lib/Ethernet/src/EthernetServer.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* Copyright 2018 Paul Stoffregen - * - * 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. - */ - -#include -#include "Ethernet.h" -#include "utility/w5100.h" - -uint16_t EthernetServer::server_port[MAX_SOCK_NUM]; - - -void EthernetServer::begin() -{ - uint8_t sockindex = Ethernet.socketBegin(SnMR::TCP, _port); - if (sockindex < MAX_SOCK_NUM) { - if (Ethernet.socketListen(sockindex)) { - server_port[sockindex] = _port; - } else { - Ethernet.socketDisconnect(sockindex); - } - } -} - -EthernetClient EthernetServer::available() -{ - bool listening = false; - uint8_t sockindex = MAX_SOCK_NUM; - uint8_t chip, maxindex=MAX_SOCK_NUM; - - chip = W5100.getChip(); - if (!chip) return EthernetClient(MAX_SOCK_NUM); -#if MAX_SOCK_NUM > 4 - if (chip == 51) maxindex = 4; // W5100 chip never supports more than 4 sockets -#endif - for (uint8_t i=0; i < maxindex; i++) { - if (server_port[i] == _port) { - uint8_t stat = Ethernet.socketStatus(i); - if (stat == SnSR::ESTABLISHED || stat == SnSR::CLOSE_WAIT) { - if (Ethernet.socketRecvAvailable(i) > 0) { - sockindex = i; - } else { - // remote host closed connection, our end still open - if (stat == SnSR::CLOSE_WAIT) { - Ethernet.socketDisconnect(i); - // status becomes LAST_ACK for short time - } - } - } else if (stat == SnSR::LISTEN) { - listening = true; - } else if (stat == SnSR::CLOSED) { - server_port[i] = 0; - } - } - } - if (!listening) begin(); - return EthernetClient(sockindex); -} - -EthernetClient EthernetServer::accept() -{ - bool listening = false; - uint8_t sockindex = MAX_SOCK_NUM; - uint8_t chip, maxindex=MAX_SOCK_NUM; - - chip = W5100.getChip(); - if (!chip) return EthernetClient(MAX_SOCK_NUM); -#if MAX_SOCK_NUM > 4 - if (chip == 51) maxindex = 4; // W5100 chip never supports more than 4 sockets -#endif - for (uint8_t i=0; i < maxindex; i++) { - if (server_port[i] == _port) { - uint8_t stat = Ethernet.socketStatus(i); - if (sockindex == MAX_SOCK_NUM && - (stat == SnSR::ESTABLISHED || stat == SnSR::CLOSE_WAIT)) { - // Return the connected client even if no data received. - // Some protocols like FTP expect the server to send the - // first data. - sockindex = i; - server_port[i] = 0; // only return the client once - } else if (stat == SnSR::LISTEN) { - listening = true; - } else if (stat == SnSR::CLOSED) { - server_port[i] = 0; - } - } - } - if (!listening) begin(); - return EthernetClient(sockindex); -} - -EthernetServer::operator bool() -{ - uint8_t maxindex=MAX_SOCK_NUM; -#if MAX_SOCK_NUM > 4 - if (W5100.getChip() == 51) maxindex = 4; // W5100 chip never supports more than 4 sockets -#endif - for (uint8_t i=0; i < maxindex; i++) { - if (server_port[i] == _port) { - if (Ethernet.socketStatus(i) == SnSR::LISTEN) { - return true; // server is listening for incoming clients - } - } - } - return false; -} - -#if 0 -void EthernetServer::statusreport() -{ - Serial.printf("EthernetServer, port=%d\n", _port); - for (uint8_t i=0; i < MAX_SOCK_NUM; i++) { - uint16_t port = server_port[i]; - uint8_t stat = Ethernet.socketStatus(i); - const char *name; - switch (stat) { - case 0x00: name = "CLOSED"; break; - case 0x13: name = "INIT"; break; - case 0x14: name = "LISTEN"; break; - case 0x15: name = "SYNSENT"; break; - case 0x16: name = "SYNRECV"; break; - case 0x17: name = "ESTABLISHED"; break; - case 0x18: name = "FIN_WAIT"; break; - case 0x1A: name = "CLOSING"; break; - case 0x1B: name = "TIME_WAIT"; break; - case 0x1C: name = "CLOSE_WAIT"; break; - case 0x1D: name = "LAST_ACK"; break; - case 0x22: name = "UDP"; break; - case 0x32: name = "IPRAW"; break; - case 0x42: name = "MACRAW"; break; - case 0x5F: name = "PPPOE"; break; - default: name = "???"; - } - int avail = Ethernet.socketRecvAvailable(i); - Serial.printf(" %d: port=%d, status=%s (0x%02X), avail=%d\n", - i, port, name, stat, avail); - } -} -#endif - -size_t EthernetServer::write(uint8_t b) -{ - return write(&b, 1); -} - -size_t EthernetServer::write(const uint8_t *buffer, size_t size) -{ - uint8_t chip, maxindex=MAX_SOCK_NUM; - - chip = W5100.getChip(); - if (!chip) return 0; -#if MAX_SOCK_NUM > 4 - if (chip == 51) maxindex = 4; // W5100 chip never supports more than 4 sockets -#endif - available(); - for (uint8_t i=0; i < maxindex; i++) { - if (server_port[i] == _port) { - if (Ethernet.socketStatus(i) == SnSR::ESTABLISHED) { - Ethernet.socketSend(i, buffer, size); - } - } - } - return size; -} diff --git a/lib/Ethernet/src/EthernetServer.h b/lib/Ethernet/src/EthernetServer.h deleted file mode 100644 index b5aef96..0000000 --- a/lib/Ethernet/src/EthernetServer.h +++ /dev/null @@ -1,3 +0,0 @@ -// This file is in the public domain. No copyright is claimed. - -#include "Ethernet.h" diff --git a/lib/Ethernet/src/EthernetUdp.cpp b/lib/Ethernet/src/EthernetUdp.cpp deleted file mode 100644 index e28791f..0000000 --- a/lib/Ethernet/src/EthernetUdp.cpp +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Udp.cpp: Library to send/receive UDP packets with the Arduino Ethernet Shield. - * This version only offers minimal wrapping of socket.cpp - * Drop Udp.h/.cpp into the Ethernet library directory at hardware/libraries/Ethernet/ - * - * MIT License: - * Copyright (c) 2008 Bjoern Hartmann - * 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. - * - * bjoern@cs.stanford.edu 12/30/2008 - */ - -#include -#include "Ethernet.h" -#include "Dns.h" -#include "utility/w5100.h" - -/* Start EthernetUDP socket, listening at local port PORT */ -uint8_t EthernetUDP::begin(uint16_t port) -{ - if (sockindex < MAX_SOCK_NUM) Ethernet.socketClose(sockindex); - sockindex = Ethernet.socketBegin(SnMR::UDP, port); - if (sockindex >= MAX_SOCK_NUM) return 0; - _port = port; - _remaining = 0; - return 1; -} - -/* return number of bytes available in the current packet, - will return zero if parsePacket hasn't been called yet */ -int EthernetUDP::available() -{ - return _remaining; -} - -/* Release any resources being used by this EthernetUDP instance */ -void EthernetUDP::stop() -{ - if (sockindex < MAX_SOCK_NUM) { - Ethernet.socketClose(sockindex); - sockindex = MAX_SOCK_NUM; - } -} - -int EthernetUDP::beginPacket(const char *host, uint16_t port) -{ - // Look up the host first - int ret = 0; - DNSClient dns; - IPAddress remote_addr; - - dns.begin(Ethernet.dnsServerIP()); - ret = dns.getHostByName(host, remote_addr); - if (ret != 1) return ret; - return beginPacket(remote_addr, port); -} - -int EthernetUDP::beginPacket(IPAddress ip, uint16_t port) -{ - _offset = 0; - //Serial.printf("UDP beginPacket\n"); - return Ethernet.socketStartUDP(sockindex, rawIPAddress(ip), port); -} - -int EthernetUDP::endPacket() -{ - return Ethernet.socketSendUDP(sockindex); -} - -size_t EthernetUDP::write(uint8_t byte) -{ - return write(&byte, 1); -} - -size_t EthernetUDP::write(const uint8_t *buffer, size_t size) -{ - //Serial.printf("UDP write %d\n", size); - uint16_t bytes_written = Ethernet.socketBufferData(sockindex, _offset, buffer, size); - _offset += bytes_written; - return bytes_written; -} - -int EthernetUDP::parsePacket() -{ - // discard any remaining bytes in the last packet - while (_remaining) { - // could this fail (loop endlessly) if _remaining > 0 and recv in read fails? - // should only occur if recv fails after telling us the data is there, lets - // hope the w5100 always behaves :) - read((uint8_t *)NULL, _remaining); - } - - if (Ethernet.socketRecvAvailable(sockindex) > 0) { - //HACK - hand-parse the UDP packet using TCP recv method - uint8_t tmpBuf[8]; - int ret=0; - //read 8 header bytes and get IP and port from it - ret = Ethernet.socketRecv(sockindex, tmpBuf, 8); - if (ret > 0) { - _remoteIP = tmpBuf; - _remotePort = tmpBuf[4]; - _remotePort = (_remotePort << 8) + tmpBuf[5]; - _remaining = tmpBuf[6]; - _remaining = (_remaining << 8) + tmpBuf[7]; - - // When we get here, any remaining bytes are the data - ret = _remaining; - } - return ret; - } - // There aren't any packets available - return 0; -} - -int EthernetUDP::read() -{ - uint8_t byte; - - if ((_remaining > 0) && (Ethernet.socketRecv(sockindex, &byte, 1) > 0)) { - // We read things without any problems - _remaining--; - return byte; - } - - // If we get here, there's no data available - return -1; -} - -int EthernetUDP::read(unsigned char *buffer, size_t len) -{ - if (_remaining > 0) { - int got; - if (_remaining <= len) { - // data should fit in the buffer - got = Ethernet.socketRecv(sockindex, buffer, _remaining); - } else { - // too much data for the buffer, - // grab as much as will fit - got = Ethernet.socketRecv(sockindex, buffer, len); - } - if (got > 0) { - _remaining -= got; - //Serial.printf("UDP read %d\n", got); - return got; - } - } - // If we get here, there's no data available or recv failed - return -1; -} - -int EthernetUDP::peek() -{ - // Unlike recv, peek doesn't check to see if there's any data available, so we must. - // If the user hasn't called parsePacket yet then return nothing otherwise they - // may get the UDP header - if (sockindex >= MAX_SOCK_NUM || _remaining == 0) return -1; - return Ethernet.socketPeek(sockindex); -} - -void EthernetUDP::flush() -{ - // TODO: we should wait for TX buffer to be emptied -} - -/* Start EthernetUDP socket, listening at local port PORT */ -uint8_t EthernetUDP::beginMulticast(IPAddress ip, uint16_t port) -{ - if (sockindex < MAX_SOCK_NUM) Ethernet.socketClose(sockindex); - sockindex = Ethernet.socketBeginMulticast(SnMR::UDP | SnMR::MULTI, ip, port); - if (sockindex >= MAX_SOCK_NUM) return 0; - _port = port; - _remaining = 0; - return 1; -} diff --git a/lib/Ethernet/src/EthernetUdp.h b/lib/Ethernet/src/EthernetUdp.h deleted file mode 100644 index 16bb062..0000000 --- a/lib/Ethernet/src/EthernetUdp.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Udp.cpp: Library to send/receive UDP packets with the Arduino Ethernet Shield. - * This version only offers minimal wrapping of socket.cpp - * Drop Udp.h/.cpp into the Ethernet library directory at hardware/libraries/Ethernet/ - * - * NOTE: UDP is fast, but has some important limitations (thanks to Warren Gray for mentioning these) - * 1) UDP does not guarantee the order in which assembled UDP packets are received. This - * might not happen often in practice, but in larger network topologies, a UDP - * packet can be received out of sequence. - * 2) UDP does not guard against lost packets - so packets *can* disappear without the sender being - * aware of it. Again, this may not be a concern in practice on small local networks. - * For more information, see http://www.cafeaulait.org/course/week12/35.html - * - * MIT License: - * Copyright (c) 2008 Bjoern Hartmann - * 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. - * - * bjoern@cs.stanford.edu 12/30/2008 - */ - -#include "Ethernet.h" - diff --git a/lib/Ethernet/src/socket.cpp b/lib/Ethernet/src/socket.cpp deleted file mode 100644 index 7dc83fe..0000000 --- a/lib/Ethernet/src/socket.cpp +++ /dev/null @@ -1,538 +0,0 @@ -/* Copyright 2018 Paul Stoffregen - * - * 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. - */ - -#include -#include "Ethernet.h" -#include "utility/w5100.h" - -#if ARDUINO >= 156 && !defined(ARDUINO_ARCH_PIC32) -extern void yield(void); -#else -#define yield() -#endif - -// TODO: randomize this when not using DHCP, but how? -static uint16_t local_port = 49152; // 49152 to 65535 - -typedef struct { - uint16_t RX_RSR; // Number of bytes received - uint16_t RX_RD; // Address to read - uint16_t TX_FSR; // Free space ready for transmit - uint8_t RX_inc; // how much have we advanced RX_RD -} socketstate_t; - -static socketstate_t state[MAX_SOCK_NUM]; - - -static uint16_t getSnTX_FSR(uint8_t s); -static uint16_t getSnRX_RSR(uint8_t s); -static void write_data(uint8_t s, uint16_t offset, const uint8_t *data, uint16_t len); -static void read_data(uint8_t s, uint16_t src, uint8_t *dst, uint16_t len); - - - -/*****************************************/ -/* Socket management */ -/*****************************************/ - - -void EthernetClass::socketPortRand(uint16_t n) -{ - n &= 0x3FFF; - local_port ^= n; - //Serial.printf("socketPortRand %d, srcport=%d\n", n, local_port); -} - -uint8_t EthernetClass::socketBegin(uint8_t protocol, uint16_t port) -{ - uint8_t s, status[MAX_SOCK_NUM], chip, maxindex=MAX_SOCK_NUM; - - // first check hardware compatibility - chip = W5100.getChip(); - if (!chip) return MAX_SOCK_NUM; // immediate error if no hardware detected -#if MAX_SOCK_NUM > 4 - if (chip == 51) maxindex = 4; // W5100 chip never supports more than 4 sockets -#endif - //Serial.printf("W5000socket begin, protocol=%d, port=%d\n", protocol, port); - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - // look at all the hardware sockets, use any that are closed (unused) - for (s=0; s < maxindex; s++) { - status[s] = W5100.readSnSR(s); - if (status[s] == SnSR::CLOSED) goto makesocket; - } - //Serial.printf("W5000socket step2\n"); - // as a last resort, forcibly close any already closing - for (s=0; s < maxindex; s++) { - uint8_t stat = status[s]; - if (stat == SnSR::LAST_ACK) goto closemakesocket; - if (stat == SnSR::TIME_WAIT) goto closemakesocket; - if (stat == SnSR::FIN_WAIT) goto closemakesocket; - if (stat == SnSR::CLOSING) goto closemakesocket; - } -#if 0 - Serial.printf("W5000socket step3\n"); - // next, use any that are effectively closed - for (s=0; s < MAX_SOCK_NUM; s++) { - uint8_t stat = status[s]; - // TODO: this also needs to check if no more data - if (stat == SnSR::CLOSE_WAIT) goto closemakesocket; - } -#endif - SPI.endTransaction(); - return MAX_SOCK_NUM; // all sockets are in use -closemakesocket: - //Serial.printf("W5000socket close\n"); - W5100.execCmdSn(s, Sock_CLOSE); -makesocket: - //Serial.printf("W5000socket %d\n", s); - EthernetServer::server_port[s] = 0; - delayMicroseconds(250); // TODO: is this needed?? - W5100.writeSnMR(s, protocol); - W5100.writeSnIR(s, 0xFF); - if (port > 0) { - W5100.writeSnPORT(s, port); - } else { - // if don't set the source port, set local_port number. - if (++local_port < 49152) local_port = 49152; - W5100.writeSnPORT(s, local_port); - } - W5100.execCmdSn(s, Sock_OPEN); - state[s].RX_RSR = 0; - state[s].RX_RD = W5100.readSnRX_RD(s); // always zero? - state[s].RX_inc = 0; - state[s].TX_FSR = 0; - //Serial.printf("W5000socket prot=%d, RX_RD=%d\n", W5100.readSnMR(s), state[s].RX_RD); - SPI.endTransaction(); - return s; -} - -// multicast version to set fields before open thd -uint8_t EthernetClass::socketBeginMulticast(uint8_t protocol, IPAddress ip, uint16_t port) -{ - uint8_t s, status[MAX_SOCK_NUM], chip, maxindex=MAX_SOCK_NUM; - - // first check hardware compatibility - chip = W5100.getChip(); - if (!chip) return MAX_SOCK_NUM; // immediate error if no hardware detected -#if MAX_SOCK_NUM > 4 - if (chip == 51) maxindex = 4; // W5100 chip never supports more than 4 sockets -#endif - //Serial.printf("W5000socket begin, protocol=%d, port=%d\n", protocol, port); - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - // look at all the hardware sockets, use any that are closed (unused) - for (s=0; s < maxindex; s++) { - status[s] = W5100.readSnSR(s); - if (status[s] == SnSR::CLOSED) goto makesocket; - } - //Serial.printf("W5000socket step2\n"); - // as a last resort, forcibly close any already closing - for (s=0; s < maxindex; s++) { - uint8_t stat = status[s]; - if (stat == SnSR::LAST_ACK) goto closemakesocket; - if (stat == SnSR::TIME_WAIT) goto closemakesocket; - if (stat == SnSR::FIN_WAIT) goto closemakesocket; - if (stat == SnSR::CLOSING) goto closemakesocket; - } -#if 0 - Serial.printf("W5000socket step3\n"); - // next, use any that are effectively closed - for (s=0; s < MAX_SOCK_NUM; s++) { - uint8_t stat = status[s]; - // TODO: this also needs to check if no more data - if (stat == SnSR::CLOSE_WAIT) goto closemakesocket; - } -#endif - SPI.endTransaction(); - return MAX_SOCK_NUM; // all sockets are in use -closemakesocket: - //Serial.printf("W5000socket close\n"); - W5100.execCmdSn(s, Sock_CLOSE); -makesocket: - //Serial.printf("W5000socket %d\n", s); - EthernetServer::server_port[s] = 0; - delayMicroseconds(250); // TODO: is this needed?? - W5100.writeSnMR(s, protocol); - W5100.writeSnIR(s, 0xFF); - if (port > 0) { - W5100.writeSnPORT(s, port); - } else { - // if don't set the source port, set local_port number. - if (++local_port < 49152) local_port = 49152; - W5100.writeSnPORT(s, local_port); - } - // Calculate MAC address from Multicast IP Address - byte mac[] = { 0x01, 0x00, 0x5E, 0x00, 0x00, 0x00 }; - mac[3] = ip[1] & 0x7F; - mac[4] = ip[2]; - mac[5] = ip[3]; - W5100.writeSnDIPR(s, ip.raw_address()); //239.255.0.1 - W5100.writeSnDPORT(s, port); - W5100.writeSnDHAR(s, mac); - W5100.execCmdSn(s, Sock_OPEN); - state[s].RX_RSR = 0; - state[s].RX_RD = W5100.readSnRX_RD(s); // always zero? - state[s].RX_inc = 0; - state[s].TX_FSR = 0; - //Serial.printf("W5000socket prot=%d, RX_RD=%d\n", W5100.readSnMR(s), state[s].RX_RD); - SPI.endTransaction(); - return s; -} -// Return the socket's status -// -uint8_t EthernetClass::socketStatus(uint8_t s) -{ - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - uint8_t status = W5100.readSnSR(s); - SPI.endTransaction(); - return status; -} - -// Immediately close. If a TCP connection is established, the -// remote host is left unaware we closed. -// -void EthernetClass::socketClose(uint8_t s) -{ - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.execCmdSn(s, Sock_CLOSE); - SPI.endTransaction(); -} - - -// Place the socket in listening (server) mode -// -uint8_t EthernetClass::socketListen(uint8_t s) -{ - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - if (W5100.readSnSR(s) != SnSR::INIT) { - SPI.endTransaction(); - return 0; - } - W5100.execCmdSn(s, Sock_LISTEN); - SPI.endTransaction(); - return 1; -} - - -// establish a TCP connection in Active (client) mode. -// -void EthernetClass::socketConnect(uint8_t s, uint8_t * addr, uint16_t port) -{ - // set destination IP - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.writeSnDIPR(s, addr); - W5100.writeSnDPORT(s, port); - W5100.execCmdSn(s, Sock_CONNECT); - SPI.endTransaction(); -} - - - -// Gracefully disconnect a TCP connection. -// -void EthernetClass::socketDisconnect(uint8_t s) -{ - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.execCmdSn(s, Sock_DISCON); - SPI.endTransaction(); -} - - - -/*****************************************/ -/* Socket Data Receive Functions */ -/*****************************************/ - - -static uint16_t getSnRX_RSR(uint8_t s) -{ -#if 1 - uint16_t val, prev; - - prev = W5100.readSnRX_RSR(s); - while (1) { - val = W5100.readSnRX_RSR(s); - if (val == prev) { - return val; - } - prev = val; - } -#else - uint16_t val = W5100.readSnRX_RSR(s); - return val; -#endif -} - -static void read_data(uint8_t s, uint16_t src, uint8_t *dst, uint16_t len) -{ - uint16_t size; - uint16_t src_mask; - uint16_t src_ptr; - - //Serial.printf("read_data, len=%d, at:%d\n", len, src); - src_mask = (uint16_t)src & W5100.SMASK; - src_ptr = W5100.RBASE(s) + src_mask; - - if (W5100.hasOffsetAddressMapping() || src_mask + len <= W5100.SSIZE) { - W5100.read(src_ptr, dst, len); - } else { - size = W5100.SSIZE - src_mask; - W5100.read(src_ptr, dst, size); - dst += size; - W5100.read(W5100.RBASE(s), dst, len - size); - } -} - -// Receive data. Returns size, or -1 for no data, or 0 if connection closed -// -int EthernetClass::socketRecv(uint8_t s, uint8_t *buf, int16_t len) -{ - // Check how much data is available - int ret = state[s].RX_RSR; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - if (ret < len) { - uint16_t rsr = getSnRX_RSR(s); - ret = rsr - state[s].RX_inc; - state[s].RX_RSR = ret; - //Serial.printf("Sock_RECV, RX_RSR=%d, RX_inc=%d\n", ret, state[s].RX_inc); - } - if (ret == 0) { - // No data available. - uint8_t status = W5100.readSnSR(s); - if ( status == SnSR::LISTEN || status == SnSR::CLOSED || - status == SnSR::CLOSE_WAIT ) { - // The remote end has closed its side of the connection, - // so this is the eof state - ret = 0; - } else { - // The connection is still up, but there's no data waiting to be read - ret = -1; - } - } else { - if (ret > len) ret = len; // more data available than buffer length - uint16_t ptr = state[s].RX_RD; - if (buf) read_data(s, ptr, buf, ret); - ptr += ret; - state[s].RX_RD = ptr; - state[s].RX_RSR -= ret; - uint16_t inc = state[s].RX_inc + ret; - if (inc >= 250 || state[s].RX_RSR == 0) { - state[s].RX_inc = 0; - W5100.writeSnRX_RD(s, ptr); - W5100.execCmdSn(s, Sock_RECV); - //Serial.printf("Sock_RECV cmd, RX_RD=%d, RX_RSR=%d\n", - // state[s].RX_RD, state[s].RX_RSR); - } else { - state[s].RX_inc = inc; - } - } - SPI.endTransaction(); - //Serial.printf("socketRecv, ret=%d\n", ret); - return ret; -} - -uint16_t EthernetClass::socketRecvAvailable(uint8_t s) -{ - uint16_t ret = state[s].RX_RSR; - if (ret == 0) { - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - uint16_t rsr = getSnRX_RSR(s); - SPI.endTransaction(); - ret = rsr - state[s].RX_inc; - state[s].RX_RSR = ret; - //Serial.printf("sockRecvAvailable s=%d, RX_RSR=%d\n", s, ret); - } - return ret; -} - -// get the first byte in the receive queue (no checking) -// -uint8_t EthernetClass::socketPeek(uint8_t s) -{ - uint8_t b; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - uint16_t ptr = state[s].RX_RD; - W5100.read((ptr & W5100.SMASK) + W5100.RBASE(s), &b, 1); - SPI.endTransaction(); - return b; -} - - - -/*****************************************/ -/* Socket Data Transmit Functions */ -/*****************************************/ - -static uint16_t getSnTX_FSR(uint8_t s) -{ - uint16_t val, prev; - - prev = W5100.readSnTX_FSR(s); - while (1) { - val = W5100.readSnTX_FSR(s); - if (val == prev) { - state[s].TX_FSR = val; - return val; - } - prev = val; - } -} - - -static void write_data(uint8_t s, uint16_t data_offset, const uint8_t *data, uint16_t len) -{ - uint16_t ptr = W5100.readSnTX_WR(s); - ptr += data_offset; - uint16_t offset = ptr & W5100.SMASK; - uint16_t dstAddr = offset + W5100.SBASE(s); - - if (W5100.hasOffsetAddressMapping() || offset + len <= W5100.SSIZE) { - W5100.write(dstAddr, data, len); - } else { - // Wrap around circular buffer - uint16_t size = W5100.SSIZE - offset; - W5100.write(dstAddr, data, size); - W5100.write(W5100.SBASE(s), data + size, len - size); - } - ptr += len; - W5100.writeSnTX_WR(s, ptr); -} - - -/** - * @brief This function used to send the data in TCP mode - * @return 1 for success else 0. - */ -uint16_t EthernetClass::socketSend(uint8_t s, const uint8_t * buf, uint16_t len) -{ - uint8_t status=0; - uint16_t ret=0; - uint16_t freesize=0; - - if (len > W5100.SSIZE) { - ret = W5100.SSIZE; // check size not to exceed MAX size. - } else { - ret = len; - } - - // if freebuf is available, start. - do { - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - freesize = getSnTX_FSR(s); - status = W5100.readSnSR(s); - SPI.endTransaction(); - if ((status != SnSR::ESTABLISHED) && (status != SnSR::CLOSE_WAIT)) { - ret = 0; - break; - } - yield(); - } while (freesize < ret); - - // copy data - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - write_data(s, 0, (uint8_t *)buf, ret); - W5100.execCmdSn(s, Sock_SEND); - - /* +2008.01 bj */ - while ( (W5100.readSnIR(s) & SnIR::SEND_OK) != SnIR::SEND_OK ) { - /* m2008.01 [bj] : reduce code */ - if ( W5100.readSnSR(s) == SnSR::CLOSED ) { - SPI.endTransaction(); - return 0; - } - SPI.endTransaction(); - yield(); - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - } - /* +2008.01 bj */ - W5100.writeSnIR(s, SnIR::SEND_OK); - SPI.endTransaction(); - return ret; -} - -uint16_t EthernetClass::socketSendAvailable(uint8_t s) -{ - uint8_t status=0; - uint16_t freesize=0; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - freesize = getSnTX_FSR(s); - status = W5100.readSnSR(s); - SPI.endTransaction(); - if ((status == SnSR::ESTABLISHED) || (status == SnSR::CLOSE_WAIT)) { - return freesize; - } - return 0; -} - -uint16_t EthernetClass::socketBufferData(uint8_t s, uint16_t offset, const uint8_t* buf, uint16_t len) -{ - //Serial.printf(" bufferData, offset=%d, len=%d\n", offset, len); - uint16_t ret =0; - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - uint16_t txfree = getSnTX_FSR(s); - if (len > txfree) { - ret = txfree; // check size not to exceed MAX size. - } else { - ret = len; - } - write_data(s, offset, buf, ret); - SPI.endTransaction(); - return ret; -} - -bool EthernetClass::socketStartUDP(uint8_t s, uint8_t* addr, uint16_t port) -{ - if ( ((addr[0] == 0x00) && (addr[1] == 0x00) && (addr[2] == 0x00) && (addr[3] == 0x00)) || - ((port == 0x00)) ) { - return false; - } - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.writeSnDIPR(s, addr); - W5100.writeSnDPORT(s, port); - SPI.endTransaction(); - return true; -} - -bool EthernetClass::socketSendUDP(uint8_t s) -{ - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - W5100.execCmdSn(s, Sock_SEND); - - /* +2008.01 bj */ - while ( (W5100.readSnIR(s) & SnIR::SEND_OK) != SnIR::SEND_OK ) { - if (W5100.readSnIR(s) & SnIR::TIMEOUT) { - /* +2008.01 [bj]: clear interrupt */ - W5100.writeSnIR(s, (SnIR::SEND_OK|SnIR::TIMEOUT)); - SPI.endTransaction(); - //Serial.printf("sendUDP timeout\n"); - return false; - } - SPI.endTransaction(); - yield(); - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - } - - /* +2008.01 bj */ - W5100.writeSnIR(s, SnIR::SEND_OK); - SPI.endTransaction(); - - //Serial.printf("sendUDP ok\n"); - /* Sent ok */ - return true; -} diff --git a/lib/Ethernet/src/utility/w5100.cpp b/lib/Ethernet/src/utility/w5100.cpp deleted file mode 100644 index e7ec24d..0000000 --- a/lib/Ethernet/src/utility/w5100.cpp +++ /dev/null @@ -1,481 +0,0 @@ -/* - * Copyright 2018 Paul Stoffregen - * Copyright (c) 2010 by Cristian Maglie - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of either the GNU General Public License version 2 - * or the GNU Lesser General Public License version 2.1, both as - * published by the Free Software Foundation. - */ - -#include -#include "Ethernet.h" -#include "w5100.h" - - -/***************************************************/ -/** Default SS pin setting **/ -/***************************************************/ - -// If variant.h or other headers specifically define the -// default SS pin for Ethernet, use it. -#if defined(PIN_SPI_SS_ETHERNET_LIB) -#define SS_PIN_DEFAULT PIN_SPI_SS_ETHERNET_LIB - -// MKR boards default to pin 5 for MKR ETH -// Pins 8-10 are MOSI/SCK/MISO on MRK, so don't use pin 10 -#elif defined(USE_ARDUINO_MKR_PIN_LAYOUT) || defined(ARDUINO_SAMD_MKRZERO) || defined(ARDUINO_SAMD_MKR1000) || defined(ARDUINO_SAMD_MKRFox1200) || defined(ARDUINO_SAMD_MKRGSM1400) || defined(ARDUINO_SAMD_MKRWAN1300) || defined(ARDUINO_SAMD_MKRVIDOR4000) -#define SS_PIN_DEFAULT 5 - -// For boards using AVR, assume shields with SS on pin 10 -// will be used. This allows for Arduino Mega (where -// SS is pin 53) and Arduino Leonardo (where SS is pin 17) -// to work by default with Arduino Ethernet Shield R2 & R3. -#elif defined(__AVR__) -#define SS_PIN_DEFAULT 10 - -// If variant.h or other headers define these names -// use them if none of the other cases match -#elif defined(PIN_SPI_SS) -#define SS_PIN_DEFAULT PIN_SPI_SS -#elif defined(CORE_SS0_PIN) -#define SS_PIN_DEFAULT CORE_SS0_PIN - -// As a final fallback, use pin 10 -#else -#define SS_PIN_DEFAULT 10 -#endif - - - - -// W5100 controller instance -uint8_t W5100Class::chip = 0; -uint8_t W5100Class::CH_BASE_MSB; -uint8_t W5100Class::ss_pin = SS_PIN_DEFAULT; -uint8_t W5100Class::sck_pin = 255; -uint8_t W5100Class::miso_pin = 255; -uint8_t W5100Class::mosi_pin = 255; -#ifdef ETHERNET_LARGE_BUFFERS -uint16_t W5100Class::SSIZE = 2048; -uint16_t W5100Class::SMASK = 0x07FF; -#endif -W5100Class W5100; - -// pointers and bitmasks for optimized SS pin -#if defined(__AVR__) - volatile uint8_t * W5100Class::ss_pin_reg; - uint8_t W5100Class::ss_pin_mask; -#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) - volatile uint8_t * W5100Class::ss_pin_reg; -#elif defined(__MKL26Z64__) - volatile uint8_t * W5100Class::ss_pin_reg; - uint8_t W5100Class::ss_pin_mask; -#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__) - volatile uint32_t * W5100Class::ss_pin_reg; - uint32_t W5100Class::ss_pin_mask; -#elif defined(__PIC32MX__) - volatile uint32_t * W5100Class::ss_pin_reg; - uint32_t W5100Class::ss_pin_mask; -#elif defined(ARDUINO_ARCH_ESP8266) - volatile uint32_t * W5100Class::ss_pin_reg; - uint32_t W5100Class::ss_pin_mask; -#elif defined(__SAMD21G18A__) - volatile uint32_t * W5100Class::ss_pin_reg; - uint32_t W5100Class::ss_pin_mask; -#endif - - -uint8_t W5100Class::init(void) -{ - static bool initialized = false; - uint8_t i; - - if (initialized) return 1; - - // Many Ethernet shields have a CAT811 or similar reset chip - // connected to W5100 or W5200 chips. The W5200 will not work at - // all, and may even drive its MISO pin, until given an active low - // reset pulse! The CAT811 has a 240 ms typical pulse length, and - // a 400 ms worst case maximum pulse length. MAX811 has a worst - // case maximum 560 ms pulse length. This delay is meant to wait - // until the reset pulse is ended. If your hardware has a shorter - // reset time, this can be edited or removed. - delay(560); - //Serial.println("w5100 init"); - - if(sck_pin != 255 && miso_pin != 255 && mosi_pin != 255) { - SPI.begin(sck_pin, miso_pin, mosi_pin, -1); - } else { - SPI.begin(); - } - initSS(); - resetSS(); - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - - // Attempt W5200 detection first, because W5200 does not properly - // reset its SPI state when CS goes high (inactive). Communication - // from detecting the other chips can leave the W5200 in a state - // where it won't recover, unless given a reset pulse. - if (isW5200()) { - CH_BASE_MSB = 0x40; -#ifdef ETHERNET_LARGE_BUFFERS -#if MAX_SOCK_NUM <= 1 - SSIZE = 16384; -#elif MAX_SOCK_NUM <= 2 - SSIZE = 8192; -#elif MAX_SOCK_NUM <= 4 - SSIZE = 4096; -#else - SSIZE = 2048; -#endif - SMASK = SSIZE - 1; -#endif - for (i=0; i> 10); - writeSnTX_SIZE(i, SSIZE >> 10); - } - for (; i<8; i++) { - writeSnRX_SIZE(i, 0); - writeSnTX_SIZE(i, 0); - } - // Try W5500 next. WIZnet finally seems to have implemented - // SPI well with this chip. It appears to be very resilient, - // so try it after the fragile W5200 - } else if (isW5500()) { - CH_BASE_MSB = 0x10; -#ifdef ETHERNET_LARGE_BUFFERS -#if MAX_SOCK_NUM <= 1 - SSIZE = 16384; -#elif MAX_SOCK_NUM <= 2 - SSIZE = 8192; -#elif MAX_SOCK_NUM <= 4 - SSIZE = 4096; -#else - SSIZE = 2048; -#endif - SMASK = SSIZE - 1; - for (i=0; i> 10); - writeSnTX_SIZE(i, SSIZE >> 10); - } - for (; i<8; i++) { - writeSnRX_SIZE(i, 0); - writeSnTX_SIZE(i, 0); - } -#endif - // Try W5100 last. This simple chip uses fixed 4 byte frames - // for every 8 bit access. Terribly inefficient, but so simple - // it recovers from "hearing" unsuccessful W5100 or W5200 - // communication. W5100 is also the only chip without a VERSIONR - // register for identification, so we check this last. - } else if (isW5100()) { - CH_BASE_MSB = 0x04; -#ifdef ETHERNET_LARGE_BUFFERS -#if MAX_SOCK_NUM <= 1 - SSIZE = 8192; - writeTMSR(0x03); - writeRMSR(0x03); -#elif MAX_SOCK_NUM <= 2 - SSIZE = 4096; - writeTMSR(0x0A); - writeRMSR(0x0A); -#else - SSIZE = 2048; - writeTMSR(0x55); - writeRMSR(0x55); -#endif - SMASK = SSIZE - 1; -#else - writeTMSR(0x55); - writeRMSR(0x55); -#endif - // No hardware seems to be present. Or it could be a W5200 - // that's heard other SPI communication if its chip select - // pin wasn't high when a SD card or other SPI chip was used. - } else { - //Serial.println("no chip :-("); - chip = 0; - SPI.endTransaction(); - return 0; // no known chip is responding :-( - } - SPI.endTransaction(); - initialized = true; - return 1; // successful init -} - -// Soft reset the WIZnet chip, by writing to its MR register reset bit -uint8_t W5100Class::softReset(void) -{ - uint16_t count=0; - - //Serial.println("WIZnet soft reset"); - // write to reset bit - writeMR(0x80); - // then wait for soft reset to complete - do { - uint8_t mr = readMR(); - //Serial.print("mr="); - //Serial.println(mr, HEX); - if (mr == 0) return 1; - delay(1); - } while (++count < 20); - return 0; -} - -uint8_t W5100Class::isW5100(void) -{ - chip = 51; - //Serial.println("w5100.cpp: detect W5100 chip"); - if (!softReset()) return 0; - writeMR(0x10); - if (readMR() != 0x10) return 0; - writeMR(0x12); - if (readMR() != 0x12) return 0; - writeMR(0x00); - if (readMR() != 0x00) return 0; - //Serial.println("chip is W5100"); - return 1; -} - -uint8_t W5100Class::isW5200(void) -{ - chip = 52; - //Serial.println("w5100.cpp: detect W5200 chip"); - if (!softReset()) return 0; - writeMR(0x08); - if (readMR() != 0x08) return 0; - writeMR(0x10); - if (readMR() != 0x10) return 0; - writeMR(0x00); - if (readMR() != 0x00) return 0; - int ver = readVERSIONR_W5200(); - //Serial.print("version="); - //Serial.println(ver); - if (ver != 3) return 0; - //Serial.println("chip is W5200"); - return 1; -} - -uint8_t W5100Class::isW5500(void) -{ - chip = 55; - //Serial.println("w5100.cpp: detect W5500 chip"); - if (!softReset()) return 0; - writeMR(0x08); - if (readMR() != 0x08) return 0; - writeMR(0x10); - if (readMR() != 0x10) return 0; - writeMR(0x00); - if (readMR() != 0x00) return 0; - int ver = readVERSIONR_W5500(); - //Serial.print("version="); - //Serial.println(ver); - if (ver != 4) return 0; - //Serial.println("chip is W5500"); - return 1; -} - -W5100Linkstatus W5100Class::getLinkStatus() -{ - uint8_t phystatus; - - if (!init()) return UNKNOWN; - switch (chip) { - case 52: - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - phystatus = readPSTATUS_W5200(); - SPI.endTransaction(); - if (phystatus & 0x20) return LINK_ON; - return LINK_OFF; - case 55: - SPI.beginTransaction(SPI_ETHERNET_SETTINGS); - phystatus = readPHYCFGR_W5500(); - SPI.endTransaction(); - if (phystatus & 0x01) return LINK_ON; - return LINK_OFF; - default: - return UNKNOWN; - } -} - -uint16_t W5100Class::write(uint16_t addr, const uint8_t *buf, uint16_t len) -{ - uint8_t cmd[8]; - - if (chip == 51) { - for (uint16_t i=0; i> 8); - SPI.transfer(addr & 0xFF); - addr++; - SPI.transfer(buf[i]); - resetSS(); - } - } else if (chip == 52) { - setSS(); - cmd[0] = addr >> 8; - cmd[1] = addr & 0xFF; - cmd[2] = ((len >> 8) & 0x7F) | 0x80; - cmd[3] = len & 0xFF; - SPI.transfer(cmd, 4); -#ifdef SPI_HAS_TRANSFER_BUF - SPI.transfer(buf, NULL, len); -#else - // TODO: copy 8 bytes at a time to cmd[] and block transfer - for (uint16_t i=0; i < len; i++) { - SPI.transfer(buf[i]); - } -#endif - resetSS(); - } else { // chip == 55 - setSS(); - if (addr < 0x100) { - // common registers 00nn - cmd[0] = 0; - cmd[1] = addr & 0xFF; - cmd[2] = 0x04; - } else if (addr < 0x8000) { - // socket registers 10nn, 11nn, 12nn, 13nn, etc - cmd[0] = 0; - cmd[1] = addr & 0xFF; - cmd[2] = ((addr >> 3) & 0xE0) | 0x0C; - } else if (addr < 0xC000) { - // transmit buffers 8000-87FF, 8800-8FFF, 9000-97FF, etc - // 10## #nnn nnnn nnnn - cmd[0] = addr >> 8; - cmd[1] = addr & 0xFF; - #if defined(ETHERNET_LARGE_BUFFERS) && MAX_SOCK_NUM <= 1 - cmd[2] = 0x14; // 16K buffers - #elif defined(ETHERNET_LARGE_BUFFERS) && MAX_SOCK_NUM <= 2 - cmd[2] = ((addr >> 8) & 0x20) | 0x14; // 8K buffers - #elif defined(ETHERNET_LARGE_BUFFERS) && MAX_SOCK_NUM <= 4 - cmd[2] = ((addr >> 7) & 0x60) | 0x14; // 4K buffers - #else - cmd[2] = ((addr >> 6) & 0xE0) | 0x14; // 2K buffers - #endif - } else { - // receive buffers - cmd[0] = addr >> 8; - cmd[1] = addr & 0xFF; - #if defined(ETHERNET_LARGE_BUFFERS) && MAX_SOCK_NUM <= 1 - cmd[2] = 0x1C; // 16K buffers - #elif defined(ETHERNET_LARGE_BUFFERS) && MAX_SOCK_NUM <= 2 - cmd[2] = ((addr >> 8) & 0x20) | 0x1C; // 8K buffers - #elif defined(ETHERNET_LARGE_BUFFERS) && MAX_SOCK_NUM <= 4 - cmd[2] = ((addr >> 7) & 0x60) | 0x1C; // 4K buffers - #else - cmd[2] = ((addr >> 6) & 0xE0) | 0x1C; // 2K buffers - #endif - } - if (len <= 5) { - for (uint8_t i=0; i < len; i++) { - cmd[i + 3] = buf[i]; - } - SPI.transfer(cmd, len + 3); - } else { - SPI.transfer(cmd, 3); -#ifdef SPI_HAS_TRANSFER_BUF - SPI.transfer(buf, NULL, len); -#else - // TODO: copy 8 bytes at a time to cmd[] and block transfer - for (uint16_t i=0; i < len; i++) { - SPI.transfer(buf[i]); - } -#endif - } - resetSS(); - } - return len; -} - -uint16_t W5100Class::read(uint16_t addr, uint8_t *buf, uint16_t len) -{ - uint8_t cmd[4]; - - if (chip == 51) { - for (uint16_t i=0; i < len; i++) { - setSS(); - #if 1 - SPI.transfer(0x0F); - SPI.transfer(addr >> 8); - SPI.transfer(addr & 0xFF); - addr++; - buf[i] = SPI.transfer(0); - #else - cmd[0] = 0x0F; - cmd[1] = addr >> 8; - cmd[2] = addr & 0xFF; - cmd[3] = 0; - SPI.transfer(cmd, 4); // TODO: why doesn't this work? - buf[i] = cmd[3]; - addr++; - #endif - resetSS(); - } - } else if (chip == 52) { - setSS(); - cmd[0] = addr >> 8; - cmd[1] = addr & 0xFF; - cmd[2] = (len >> 8) & 0x7F; - cmd[3] = len & 0xFF; - SPI.transfer(cmd, 4); - memset(buf, 0, len); - SPI.transfer(buf, len); - resetSS(); - } else { // chip == 55 - setSS(); - if (addr < 0x100) { - // common registers 00nn - cmd[0] = 0; - cmd[1] = addr & 0xFF; - cmd[2] = 0x00; - } else if (addr < 0x8000) { - // socket registers 10nn, 11nn, 12nn, 13nn, etc - cmd[0] = 0; - cmd[1] = addr & 0xFF; - cmd[2] = ((addr >> 3) & 0xE0) | 0x08; - } else if (addr < 0xC000) { - // transmit buffers 8000-87FF, 8800-8FFF, 9000-97FF, etc - // 10## #nnn nnnn nnnn - cmd[0] = addr >> 8; - cmd[1] = addr & 0xFF; - #if defined(ETHERNET_LARGE_BUFFERS) && MAX_SOCK_NUM <= 1 - cmd[2] = 0x10; // 16K buffers - #elif defined(ETHERNET_LARGE_BUFFERS) && MAX_SOCK_NUM <= 2 - cmd[2] = ((addr >> 8) & 0x20) | 0x10; // 8K buffers - #elif defined(ETHERNET_LARGE_BUFFERS) && MAX_SOCK_NUM <= 4 - cmd[2] = ((addr >> 7) & 0x60) | 0x10; // 4K buffers - #else - cmd[2] = ((addr >> 6) & 0xE0) | 0x10; // 2K buffers - #endif - } else { - // receive buffers - cmd[0] = addr >> 8; - cmd[1] = addr & 0xFF; - #if defined(ETHERNET_LARGE_BUFFERS) && MAX_SOCK_NUM <= 1 - cmd[2] = 0x18; // 16K buffers - #elif defined(ETHERNET_LARGE_BUFFERS) && MAX_SOCK_NUM <= 2 - cmd[2] = ((addr >> 8) & 0x20) | 0x18; // 8K buffers - #elif defined(ETHERNET_LARGE_BUFFERS) && MAX_SOCK_NUM <= 4 - cmd[2] = ((addr >> 7) & 0x60) | 0x18; // 4K buffers - #else - cmd[2] = ((addr >> 6) & 0xE0) | 0x18; // 2K buffers - #endif - } - SPI.transfer(cmd, 3); - memset(buf, 0, len); - SPI.transfer(buf, len); - resetSS(); - } - return len; -} - -void W5100Class::execCmdSn(SOCKET s, SockCMD _cmd) -{ - // Send command to socket - writeSnCR(s, _cmd); - // Wait for command to complete - while (readSnCR(s)) ; -} diff --git a/lib/Ethernet/src/utility/w5100.h b/lib/Ethernet/src/utility/w5100.h deleted file mode 100644 index 210503a..0000000 --- a/lib/Ethernet/src/utility/w5100.h +++ /dev/null @@ -1,487 +0,0 @@ -/* - * Copyright 2018 Paul Stoffregen - * Copyright (c) 2010 by Cristian Maglie - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of either the GNU General Public License version 2 - * or the GNU Lesser General Public License version 2.1, both as - * published by the Free Software Foundation. - */ - -// w5100.h contains private W5x00 hardware "driver" level definitions -// which are not meant to be exposed to other libraries or Arduino users - -#ifndef W5100_H_INCLUDED -#define W5100_H_INCLUDED - -#include -#include - -// Safe for all chips -#define SPI_ETHERNET_SETTINGS SPISettings(14000000, MSBFIRST, SPI_MODE0) - -// Safe for W5200 and W5500, but too fast for W5100 -// Uncomment this if you know you'll never need W5100 support. -// Higher SPI clock only results in faster transfer to hosts on a LAN -// or with very low packet latency. With ordinary internet latency, -// the TCP window size & packet loss determine your overall speed. -//#define SPI_ETHERNET_SETTINGS SPISettings(30000000, MSBFIRST, SPI_MODE0) - - -// Require Ethernet.h, because we need MAX_SOCK_NUM -#ifndef ethernet_h_ -#error "Ethernet.h must be included before w5100.h" -#endif - - -// Arduino 101's SPI can not run faster than 8 MHz. -#if defined(ARDUINO_ARCH_ARC32) -#undef SPI_ETHERNET_SETTINGS -#define SPI_ETHERNET_SETTINGS SPISettings(8000000, MSBFIRST, SPI_MODE0) -#endif - -// Arduino Zero can't use W5100-based shields faster than 8 MHz -// https://github.com/arduino-libraries/Ethernet/issues/37#issuecomment-408036848 -// W5500 does seem to work at 12 MHz. Delete this if only using W5500 -#if defined(__SAMD21G18A__) -#undef SPI_ETHERNET_SETTINGS -#define SPI_ETHERNET_SETTINGS SPISettings(8000000, MSBFIRST, SPI_MODE0) -#endif - - -typedef uint8_t SOCKET; - -class SnMR { -public: - static const uint8_t CLOSE = 0x00; - static const uint8_t TCP = 0x21; - static const uint8_t UDP = 0x02; - static const uint8_t IPRAW = 0x03; - static const uint8_t MACRAW = 0x04; - static const uint8_t PPPOE = 0x05; - static const uint8_t ND = 0x20; - static const uint8_t MULTI = 0x80; -}; - -enum SockCMD { - Sock_OPEN = 0x01, - Sock_LISTEN = 0x02, - Sock_CONNECT = 0x04, - Sock_DISCON = 0x08, - Sock_CLOSE = 0x10, - Sock_SEND = 0x20, - Sock_SEND_MAC = 0x21, - Sock_SEND_KEEP = 0x22, - Sock_RECV = 0x40 -}; - -class SnIR { -public: - static const uint8_t SEND_OK = 0x10; - static const uint8_t TIMEOUT = 0x08; - static const uint8_t RECV = 0x04; - static const uint8_t DISCON = 0x02; - static const uint8_t CON = 0x01; -}; - -class SnSR { -public: - static const uint8_t CLOSED = 0x00; - static const uint8_t INIT = 0x13; - static const uint8_t LISTEN = 0x14; - static const uint8_t SYNSENT = 0x15; - static const uint8_t SYNRECV = 0x16; - static const uint8_t ESTABLISHED = 0x17; - static const uint8_t FIN_WAIT = 0x18; - static const uint8_t CLOSING = 0x1A; - static const uint8_t TIME_WAIT = 0x1B; - static const uint8_t CLOSE_WAIT = 0x1C; - static const uint8_t LAST_ACK = 0x1D; - static const uint8_t UDP = 0x22; - static const uint8_t IPRAW = 0x32; - static const uint8_t MACRAW = 0x42; - static const uint8_t PPPOE = 0x5F; -}; - -class IPPROTO { -public: - static const uint8_t IP = 0; - static const uint8_t ICMP = 1; - static const uint8_t IGMP = 2; - static const uint8_t GGP = 3; - static const uint8_t TCP = 6; - static const uint8_t PUP = 12; - static const uint8_t UDP = 17; - static const uint8_t IDP = 22; - static const uint8_t ND = 77; - static const uint8_t RAW = 255; -}; - -enum W5100Linkstatus { - UNKNOWN, - LINK_ON, - LINK_OFF -}; - -class W5100Class { - -public: - static uint8_t init(void); - - inline void setGatewayIp(const uint8_t * addr) { writeGAR(addr); } - inline void getGatewayIp(uint8_t * addr) { readGAR(addr); } - - inline void setSubnetMask(const uint8_t * addr) { writeSUBR(addr); } - inline void getSubnetMask(uint8_t * addr) { readSUBR(addr); } - - inline void setMACAddress(const uint8_t * addr) { writeSHAR(addr); } - inline void getMACAddress(uint8_t * addr) { readSHAR(addr); } - - inline void setIPAddress(const uint8_t * addr) { writeSIPR(addr); } - inline void getIPAddress(uint8_t * addr) { readSIPR(addr); } - - inline void setRetransmissionTime(uint16_t timeout) { writeRTR(timeout); } - inline void setRetransmissionCount(uint8_t retry) { writeRCR(retry); } - - static void execCmdSn(SOCKET s, SockCMD _cmd); - - - // W5100 Registers - // --------------- -//private: -public: - static uint16_t write(uint16_t addr, const uint8_t *buf, uint16_t len); - static uint8_t write(uint16_t addr, uint8_t data) { - return write(addr, &data, 1); - } - static uint16_t read(uint16_t addr, uint8_t *buf, uint16_t len); - static uint8_t read(uint16_t addr) { - uint8_t data; - read(addr, &data, 1); - return data; - } - -#define __GP_REGISTER8(name, address) \ - static inline void write##name(uint8_t _data) { \ - write(address, _data); \ - } \ - static inline uint8_t read##name() { \ - return read(address); \ - } -#define __GP_REGISTER16(name, address) \ - static void write##name(uint16_t _data) { \ - uint8_t buf[2]; \ - buf[0] = _data >> 8; \ - buf[1] = _data & 0xFF; \ - write(address, buf, 2); \ - } \ - static uint16_t read##name() { \ - uint8_t buf[2]; \ - read(address, buf, 2); \ - return (buf[0] << 8) | buf[1]; \ - } -#define __GP_REGISTER_N(name, address, size) \ - static uint16_t write##name(const uint8_t *_buff) { \ - return write(address, _buff, size); \ - } \ - static uint16_t read##name(uint8_t *_buff) { \ - return read(address, _buff, size); \ - } - static W5100Linkstatus getLinkStatus(); - -public: - __GP_REGISTER8 (MR, 0x0000); // Mode - __GP_REGISTER_N(GAR, 0x0001, 4); // Gateway IP address - __GP_REGISTER_N(SUBR, 0x0005, 4); // Subnet mask address - __GP_REGISTER_N(SHAR, 0x0009, 6); // Source MAC address - __GP_REGISTER_N(SIPR, 0x000F, 4); // Source IP address - __GP_REGISTER8 (IR, 0x0015); // Interrupt - __GP_REGISTER8 (IMR, 0x0016); // Interrupt Mask - __GP_REGISTER16(RTR, 0x0017); // Timeout address - __GP_REGISTER8 (RCR, 0x0019); // Retry count - __GP_REGISTER8 (RMSR, 0x001A); // Receive memory size (W5100 only) - __GP_REGISTER8 (TMSR, 0x001B); // Transmit memory size (W5100 only) - __GP_REGISTER8 (PATR, 0x001C); // Authentication type address in PPPoE mode - __GP_REGISTER8 (PTIMER, 0x0028); // PPP LCP Request Timer - __GP_REGISTER8 (PMAGIC, 0x0029); // PPP LCP Magic Number - __GP_REGISTER_N(UIPR, 0x002A, 4); // Unreachable IP address in UDP mode (W5100 only) - __GP_REGISTER16(UPORT, 0x002E); // Unreachable Port address in UDP mode (W5100 only) - __GP_REGISTER8 (VERSIONR_W5200,0x001F); // Chip Version Register (W5200 only) - __GP_REGISTER8 (VERSIONR_W5500,0x0039); // Chip Version Register (W5500 only) - __GP_REGISTER8 (PSTATUS_W5200, 0x0035); // PHY Status - __GP_REGISTER8 (PHYCFGR_W5500, 0x002E); // PHY Configuration register, default: 10111xxx - - -#undef __GP_REGISTER8 -#undef __GP_REGISTER16 -#undef __GP_REGISTER_N - - // W5100 Socket registers - // ---------------------- -private: - static uint16_t CH_BASE(void) { - //if (chip == 55) return 0x1000; - //if (chip == 52) return 0x4000; - //return 0x0400; - return CH_BASE_MSB << 8; - } - static uint8_t CH_BASE_MSB; // 1 redundant byte, saves ~80 bytes code on AVR - static const uint16_t CH_SIZE = 0x0100; - - static inline uint8_t readSn(SOCKET s, uint16_t addr) { - return read(CH_BASE() + s * CH_SIZE + addr); - } - static inline uint8_t writeSn(SOCKET s, uint16_t addr, uint8_t data) { - return write(CH_BASE() + s * CH_SIZE + addr, data); - } - static inline uint16_t readSn(SOCKET s, uint16_t addr, uint8_t *buf, uint16_t len) { - return read(CH_BASE() + s * CH_SIZE + addr, buf, len); - } - static inline uint16_t writeSn(SOCKET s, uint16_t addr, uint8_t *buf, uint16_t len) { - return write(CH_BASE() + s * CH_SIZE + addr, buf, len); - } - -#define __SOCKET_REGISTER8(name, address) \ - static inline void write##name(SOCKET _s, uint8_t _data) { \ - writeSn(_s, address, _data); \ - } \ - static inline uint8_t read##name(SOCKET _s) { \ - return readSn(_s, address); \ - } -#define __SOCKET_REGISTER16(name, address) \ - static void write##name(SOCKET _s, uint16_t _data) { \ - uint8_t buf[2]; \ - buf[0] = _data >> 8; \ - buf[1] = _data & 0xFF; \ - writeSn(_s, address, buf, 2); \ - } \ - static uint16_t read##name(SOCKET _s) { \ - uint8_t buf[2]; \ - readSn(_s, address, buf, 2); \ - return (buf[0] << 8) | buf[1]; \ - } -#define __SOCKET_REGISTER_N(name, address, size) \ - static uint16_t write##name(SOCKET _s, uint8_t *_buff) { \ - return writeSn(_s, address, _buff, size); \ - } \ - static uint16_t read##name(SOCKET _s, uint8_t *_buff) { \ - return readSn(_s, address, _buff, size); \ - } - -public: - __SOCKET_REGISTER8(SnMR, 0x0000) // Mode - __SOCKET_REGISTER8(SnCR, 0x0001) // Command - __SOCKET_REGISTER8(SnIR, 0x0002) // Interrupt - __SOCKET_REGISTER8(SnSR, 0x0003) // Status - __SOCKET_REGISTER16(SnPORT, 0x0004) // Source Port - __SOCKET_REGISTER_N(SnDHAR, 0x0006, 6) // Destination Hardw Addr - __SOCKET_REGISTER_N(SnDIPR, 0x000C, 4) // Destination IP Addr - __SOCKET_REGISTER16(SnDPORT, 0x0010) // Destination Port - __SOCKET_REGISTER16(SnMSSR, 0x0012) // Max Segment Size - __SOCKET_REGISTER8(SnPROTO, 0x0014) // Protocol in IP RAW Mode - __SOCKET_REGISTER8(SnTOS, 0x0015) // IP TOS - __SOCKET_REGISTER8(SnTTL, 0x0016) // IP TTL - __SOCKET_REGISTER8(SnRX_SIZE, 0x001E) // RX Memory Size (W5200 only) - __SOCKET_REGISTER8(SnTX_SIZE, 0x001F) // RX Memory Size (W5200 only) - __SOCKET_REGISTER16(SnTX_FSR, 0x0020) // TX Free Size - __SOCKET_REGISTER16(SnTX_RD, 0x0022) // TX Read Pointer - __SOCKET_REGISTER16(SnTX_WR, 0x0024) // TX Write Pointer - __SOCKET_REGISTER16(SnRX_RSR, 0x0026) // RX Free Size - __SOCKET_REGISTER16(SnRX_RD, 0x0028) // RX Read Pointer - __SOCKET_REGISTER16(SnRX_WR, 0x002A) // RX Write Pointer (supported?) - -#undef __SOCKET_REGISTER8 -#undef __SOCKET_REGISTER16 -#undef __SOCKET_REGISTER_N - - -private: - static uint8_t chip; - static uint8_t ss_pin; - static uint8_t sck_pin; - static uint8_t miso_pin; - static uint8_t mosi_pin; - static uint8_t softReset(void); - static uint8_t isW5100(void); - static uint8_t isW5200(void); - static uint8_t isW5500(void); - -public: - static uint8_t getChip(void) { return chip; } -#ifdef ETHERNET_LARGE_BUFFERS - static uint16_t SSIZE; - static uint16_t SMASK; -#else - static const uint16_t SSIZE = 2048; - static const uint16_t SMASK = 0x07FF; -#endif - static uint16_t SBASE(uint8_t socknum) { - if (chip == 51) { - return socknum * SSIZE + 0x4000; - } else { - return socknum * SSIZE + 0x8000; - } - } - static uint16_t RBASE(uint8_t socknum) { - if (chip == 51) { - return socknum * SSIZE + 0x6000; - } else { - return socknum * SSIZE + 0xC000; - } - } - - static bool hasOffsetAddressMapping(void) { - if (chip == 55) return true; - return false; - } - static void setSS(uint8_t pin) { ss_pin = pin; } - static void setSPI(uint8_t sckpin, uint8_t misopin, uint8_t mosipin) { - sck_pin = sckpin; - miso_pin = misopin; - mosi_pin = mosipin; - } - -private: -#if defined(__AVR__) - static volatile uint8_t *ss_pin_reg; - static uint8_t ss_pin_mask; - inline static void initSS() { - ss_pin_reg = portOutputRegister(digitalPinToPort(ss_pin)); - ss_pin_mask = digitalPinToBitMask(ss_pin); - pinMode(ss_pin, OUTPUT); - } - inline static void setSS() { - *(ss_pin_reg) &= ~ss_pin_mask; - } - inline static void resetSS() { - *(ss_pin_reg) |= ss_pin_mask; - } -#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) - static volatile uint8_t *ss_pin_reg; - inline static void initSS() { - ss_pin_reg = portOutputRegister(ss_pin); - pinMode(ss_pin, OUTPUT); - } - inline static void setSS() { - *(ss_pin_reg+256) = 1; - } - inline static void resetSS() { - *(ss_pin_reg+128) = 1; - } -#elif defined(__MKL26Z64__) - static volatile uint8_t *ss_pin_reg; - static uint8_t ss_pin_mask; - inline static void initSS() { - ss_pin_reg = portOutputRegister(digitalPinToPort(ss_pin)); - ss_pin_mask = digitalPinToBitMask(ss_pin); - pinMode(ss_pin, OUTPUT); - } - inline static void setSS() { - *(ss_pin_reg+8) = ss_pin_mask; - } - inline static void resetSS() { - *(ss_pin_reg+4) = ss_pin_mask; - } -#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__) - static volatile uint32_t *ss_pin_reg; - static uint32_t ss_pin_mask; - inline static void initSS() { - ss_pin_reg = &(digitalPinToPort(ss_pin)->PIO_PER); - ss_pin_mask = digitalPinToBitMask(ss_pin); - pinMode(ss_pin, OUTPUT); - } - inline static void setSS() { - *(ss_pin_reg+13) = ss_pin_mask; - } - inline static void resetSS() { - *(ss_pin_reg+12) = ss_pin_mask; - } -#elif defined(__PIC32MX__) - static volatile uint32_t *ss_pin_reg; - static uint32_t ss_pin_mask; - inline static void initSS() { - ss_pin_reg = portModeRegister(digitalPinToPort(ss_pin)); - ss_pin_mask = digitalPinToBitMask(ss_pin); - pinMode(ss_pin, OUTPUT); - } - inline static void setSS() { - *(ss_pin_reg+8+1) = ss_pin_mask; - } - inline static void resetSS() { - *(ss_pin_reg+8+2) = ss_pin_mask; - } - -#elif defined(ARDUINO_ARCH_ESP8266) - static volatile uint32_t *ss_pin_reg; - static uint32_t ss_pin_mask; - inline static void initSS() { - ss_pin_reg = (volatile uint32_t*)GPO; - ss_pin_mask = 1 << ss_pin; - pinMode(ss_pin, OUTPUT); - } - inline static void setSS() { - GPOC = ss_pin_mask; - } - inline static void resetSS() { - GPOS = ss_pin_mask; - } - -#elif defined(__SAMD21G18A__) - static volatile uint32_t *ss_pin_reg; - static uint32_t ss_pin_mask; - inline static void initSS() { - ss_pin_reg = portModeRegister(digitalPinToPort(ss_pin)); - ss_pin_mask = digitalPinToBitMask(ss_pin); - pinMode(ss_pin, OUTPUT); - } - inline static void setSS() { - *(ss_pin_reg+5) = ss_pin_mask; - } - inline static void resetSS() { - *(ss_pin_reg+6) = ss_pin_mask; - } -#else - inline static void initSS() { - pinMode(ss_pin, OUTPUT); - } - inline static void setSS() { - digitalWrite(ss_pin, LOW); - } - inline static void resetSS() { - digitalWrite(ss_pin, HIGH); - } -#endif -}; - -extern W5100Class W5100; - - - -#endif - -#ifndef UTIL_H -#define UTIL_H - -#ifndef htons -// The host order of the Arduino platform is little endian. -// Sometimes it is desired to convert to big endian (or -// network order) - -// Host to Network short -#define htons(x) ( (((x)&0xFF)<<8) | (((x)>>8)&0xFF) ) - -// Network to Host short -#define ntohs(x) htons(x) - -// Host to Network long -#define htonl(x) ( ((x)<<24 & 0xFF000000UL) | \ - ((x)<< 8 & 0x00FF0000UL) | \ - ((x)>> 8 & 0x0000FF00UL) | \ - ((x)>>24 & 0x000000FFUL) ) - -// Network to Host long -#define ntohl(x) htonl(x) - -#endif // !defined(htons) - -#endif diff --git a/lib/HTTPClient/examples/Authorization/Authorization.ino b/lib/HTTPClient/examples/Authorization/Authorization.ino deleted file mode 100644 index 02bf9ae..0000000 --- a/lib/HTTPClient/examples/Authorization/Authorization.ino +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Authorization.ino - * - * Created on: 09.12.2015 - * - */ - -#include - -#include -#include - -#include - -#define USE_SERIAL Serial - -WiFiMulti wifiMulti; - -void setup() { - - USE_SERIAL.begin(115200); - - USE_SERIAL.println(); - USE_SERIAL.println(); - USE_SERIAL.println(); - - for (uint8_t t = 4; t > 0; t--) { - USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); - USE_SERIAL.flush(); - delay(1000); - } - - wifiMulti.addAP("SSID", "PASSWORD"); -} - -void loop() { - // wait for WiFi connection - if ((wifiMulti.run() == WL_CONNECTED)) { - - HTTPClient http; - - USE_SERIAL.print("[HTTP] begin...\n"); - // configure traged server and url - - http.begin("http://user:password@192.168.1.12/test.html"); - - /* - // or - http.begin("http://192.168.1.12/test.html"); - http.setAuthorization("user", "password"); - - // or - http.begin("http://192.168.1.12/test.html"); - http.setAuthorization("dXNlcjpwYXN3b3Jk"); - */ - - USE_SERIAL.print("[HTTP] GET...\n"); - // start connection and send HTTP header - int httpCode = http.GET(); - - // httpCode will be negative on error - if (httpCode > 0) { - // HTTP header has been send and Server response header has been handled - USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode); - - // file found at server - if (httpCode == HTTP_CODE_OK) { - String payload = http.getString(); - USE_SERIAL.println(payload); - } - } else { - USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); - } - - http.end(); - } - - delay(10000); -} diff --git a/lib/HTTPClient/examples/Authorization/ci.json b/lib/HTTPClient/examples/Authorization/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/HTTPClient/examples/Authorization/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/HTTPClient/examples/BasicHttpClient/BasicHttpClient.ino b/lib/HTTPClient/examples/BasicHttpClient/BasicHttpClient.ino deleted file mode 100644 index e8f5be6..0000000 --- a/lib/HTTPClient/examples/BasicHttpClient/BasicHttpClient.ino +++ /dev/null @@ -1,100 +0,0 @@ -/** - * BasicHTTPClient.ino - * - * Created on: 24.05.2015 - * - */ - -#include - -#include -#include - -#include - -#define USE_SERIAL Serial - -WiFiMulti wifiMulti; - -/* -const char* ca = \ -"-----BEGIN CERTIFICATE-----\n" \ -"MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n" \ -"MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ -"DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n" \ -"SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n" \ -"GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" \ -"AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n" \ -"q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n" \ -"SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\n" \ -"Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n" \ -"a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n" \ -"/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n" \ -"AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n" \ -"CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n" \ -"bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n" \ -"c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n" \ -"VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n" \ -"ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n" \ -"MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n" \ -"Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n" \ -"AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n" \ -"uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n" \ -"wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n" \ -"X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n" \ -"PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n" \ -"KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n" \ -"-----END CERTIFICATE-----\n"; -*/ - -void setup() { - - USE_SERIAL.begin(115200); - - USE_SERIAL.println(); - USE_SERIAL.println(); - USE_SERIAL.println(); - - for (uint8_t t = 4; t > 0; t--) { - USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); - USE_SERIAL.flush(); - delay(1000); - } - - wifiMulti.addAP("SSID", "PASSWORD"); -} - -void loop() { - // wait for WiFi connection - if ((wifiMulti.run() == WL_CONNECTED)) { - - HTTPClient http; - - USE_SERIAL.print("[HTTP] begin...\n"); - // configure traged server and url - //http.begin("https://www.howsmyssl.com/a/check", ca); //HTTPS - http.begin("http://example.com/index.html"); //HTTP - - USE_SERIAL.print("[HTTP] GET...\n"); - // start connection and send HTTP header - int httpCode = http.GET(); - - // httpCode will be negative on error - if (httpCode > 0) { - // HTTP header has been send and Server response header has been handled - USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode); - - // file found at server - if (httpCode == HTTP_CODE_OK) { - String payload = http.getString(); - USE_SERIAL.println(payload); - } - } else { - USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); - } - - http.end(); - } - - delay(5000); -} diff --git a/lib/HTTPClient/examples/BasicHttpClient/ci.json b/lib/HTTPClient/examples/BasicHttpClient/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/HTTPClient/examples/BasicHttpClient/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino b/lib/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino deleted file mode 100644 index eb6caef..0000000 --- a/lib/HTTPClient/examples/BasicHttpsClient/BasicHttpsClient.ino +++ /dev/null @@ -1,132 +0,0 @@ -/** - BasicHTTPSClient.ino - - Created on: 14.10.2018 - -*/ - -#include - -#include -#include - -#include - -#include - -// This is a Baltimore CyberTrust cert, the root Certificate Authority that -// signed the server certificate for the demo server https://jigsaw.w3.org in this -// example. This certificate is valid until Mon, 12 May 2025 23:59:00 GMT -const char *rootCACertificate = "-----BEGIN CERTIFICATE-----\n" - "MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ\n" - "RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD\n" - "VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX\n" - "DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y\n" - "ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy\n" - "VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr\n" - "mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr\n" - "IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK\n" - "mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu\n" - "XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy\n" - "dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye\n" - "jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1\n" - "BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3\n" - "DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92\n" - "9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx\n" - "jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0\n" - "Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz\n" - "ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS\n" - "R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp\n" - "-----END CERTIFICATE-----\n"; - -// Not sure if NetworkClientSecure checks the validity date of the certificate. -// Setting clock just to be sure... -void setClock() { - configTime(0, 0, "pool.ntp.org"); - - Serial.print(F("Waiting for NTP time sync: ")); - time_t nowSecs = time(nullptr); - while (nowSecs < 8 * 3600 * 2) { - delay(500); - Serial.print(F(".")); - yield(); - nowSecs = time(nullptr); - } - - Serial.println(); - struct tm timeinfo; - gmtime_r(&nowSecs, &timeinfo); - Serial.print(F("Current time: ")); - Serial.print(asctime(&timeinfo)); -} - -WiFiMulti WiFiMulti; - -void setup() { - - Serial.begin(115200); - // Serial.setDebugOutput(true); - - Serial.println(); - Serial.println(); - Serial.println(); - - WiFi.mode(WIFI_STA); - WiFiMulti.addAP("SSID", "PASSWORD"); - - // wait for WiFi connection - Serial.print("Waiting for WiFi to connect..."); - while ((WiFiMulti.run() != WL_CONNECTED)) { - Serial.print("."); - } - Serial.println(" connected"); - - setClock(); -} - -void loop() { - NetworkClientSecure *client = new NetworkClientSecure; - if (client) { - client->setCACert(rootCACertificate); - - { - // Add a scoping block for HTTPClient https to make sure it is destroyed before NetworkClientSecure *client is - HTTPClient https; - - Serial.print("[HTTPS] begin...\n"); - if (https.begin(*client, "https://jigsaw.w3.org/HTTP/connection.html")) { // HTTPS - Serial.print("[HTTPS] GET...\n"); - // start connection and send HTTP header - int httpCode = https.GET(); - - // httpCode will be negative on error - if (httpCode > 0) { - // HTTP header has been send and Server response header has been handled - Serial.printf("[HTTPS] GET... code: %d\n", httpCode); - - // file found at server - if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { - String payload = https.getString(); - Serial.println(payload); - } - } else { - Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str()); - } - - https.end(); - } else { - Serial.printf("[HTTPS] Unable to connect\n"); - } - - // End extra scoping block - } - - delete client; - } else { - Serial.println("Unable to create client"); - } - - Serial.println(); - Serial.println("Waiting 10s before the next round..."); - delay(10000); -} diff --git a/lib/HTTPClient/examples/BasicHttpsClient/ci.json b/lib/HTTPClient/examples/BasicHttpsClient/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/HTTPClient/examples/BasicHttpsClient/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/HTTPClient/examples/HTTPClientEnterprise/HTTPClientEnterprise.ino b/lib/HTTPClient/examples/HTTPClientEnterprise/HTTPClientEnterprise.ino deleted file mode 100644 index 7f1d1dd..0000000 --- a/lib/HTTPClient/examples/HTTPClientEnterprise/HTTPClientEnterprise.ino +++ /dev/null @@ -1,107 +0,0 @@ -/*|----------------------------------------------------------|*/ -/*|WORKING EXAMPLE FOR HTTP/HTTPS CONNECTION |*/ -/*|TESTED BOARDS: Devkit v1 DOIT, Devkitc v4 |*/ -/*|CORE: June 2018 |*/ -/*|----------------------------------------------------------|*/ -#include -#include -#if __has_include("esp_eap_client.h") -#include "esp_eap_client.h" -#else -#include "esp_wpa2.h" -#endif -#include -#define EAP_IDENTITY "identity" //if connecting from another corporation, use identity@organization.domain in Eduroam -#define EAP_PASSWORD "password" //your Eduroam password -const char *ssid = "eduroam"; // Eduroam SSID -int counter = 0; -const char *test_root_ca = "-----BEGIN CERTIFICATE-----\n" - "MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n" - "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" - "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" - "QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n" - "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" - "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n" - "9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n" - "CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n" - "nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n" - "43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n" - "T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n" - "gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n" - "BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n" - "TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n" - "DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n" - "hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n" - "06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n" - "PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n" - "YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n" - "CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n" - "-----END CERTIFICATE-----\n"; -void setup() { - Serial.begin(115200); - delay(10); - Serial.println(); - Serial.print("Connecting to network: "); - Serial.println(ssid); - WiFi.disconnect(true); //disconnect form wifi to set new wifi connection - WiFi.mode(WIFI_STA); //init wifi mode -#if __has_include("esp_eap_client.h") - esp_eap_client_set_identity((uint8_t *)EAP_IDENTITY, strlen(EAP_IDENTITY)); //provide identity - esp_eap_client_set_username((uint8_t *)EAP_IDENTITY, strlen(EAP_IDENTITY)); //provide username - esp_eap_client_set_password((uint8_t *)EAP_PASSWORD, strlen(EAP_PASSWORD)); //provide password - esp_wifi_sta_enterprise_enable(); -#else - esp_wifi_sta_wpa2_ent_set_identity((uint8_t *)EAP_IDENTITY, strlen(EAP_IDENTITY)); //provide identity - esp_wifi_sta_wpa2_ent_set_username((uint8_t *)EAP_IDENTITY, strlen(EAP_IDENTITY)); //provide username --> identity and username is same - esp_wifi_sta_wpa2_ent_set_password((uint8_t *)EAP_PASSWORD, strlen(EAP_PASSWORD)); //provide password - esp_wifi_sta_wpa2_ent_enable(); -#endif - WiFi.begin(ssid); //connect to wifi - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - counter++; - if (counter >= 60) { //after 30 seconds timeout - reset board - ESP.restart(); - } - } - Serial.println(""); - Serial.println("WiFi connected"); - Serial.println("IP address set: "); - Serial.println(WiFi.localIP()); //print LAN IP -} -void loop() { - if (WiFi.status() == WL_CONNECTED) { //if we are connected to Eduroam network - counter = 0; //reset counter - Serial.println("Wifi is still connected with IP: "); - Serial.println(WiFi.localIP()); //inform user about his IP address - } else if (WiFi.status() != WL_CONNECTED) { //if we lost connection, retry - WiFi.begin(ssid); - } - while (WiFi.status() != WL_CONNECTED) { //during lost connection, print dots - delay(500); - Serial.print("."); - counter++; - if (counter >= 60) { //30 seconds timeout - reset board - ESP.restart(); - } - } - Serial.print("Connecting to website: "); - HTTPClient http; - http.begin("https://arduino.php5.sk/rele/rele1.txt", test_root_ca); //HTTPS example connection - //http.begin("http://www.arduino.php5.sk/rele/rele1.txt"); //HTTP example connection - //if uncomment HTTP example, you can comment root CA certificate too! - int httpCode = http.GET(); - if (httpCode > 0) { - Serial.printf("[HTTP] GET... code: %d\n", httpCode); - //file found at server --> on unsuccessful connection code will be -1 - if (httpCode == HTTP_CODE_OK) { - String payload = http.getString(); - Serial.println(payload); - } - } else { - Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); - } - http.end(); - delay(2000); -} diff --git a/lib/HTTPClient/examples/HTTPClientEnterprise/ci.json b/lib/HTTPClient/examples/HTTPClientEnterprise/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/HTTPClient/examples/HTTPClientEnterprise/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/HTTPClient/examples/ReuseConnection/ReuseConnection.ino b/lib/HTTPClient/examples/ReuseConnection/ReuseConnection.ino deleted file mode 100644 index 5b3137a..0000000 --- a/lib/HTTPClient/examples/ReuseConnection/ReuseConnection.ino +++ /dev/null @@ -1,64 +0,0 @@ -/** - * reuseConnection.ino - * - * Created on: 22.11.2015 - * - */ - -#include - -#include -#include - -#include - -#define USE_SERIAL Serial - -WiFiMulti wifiMulti; - -HTTPClient http; - -void setup() { - - USE_SERIAL.begin(115200); - - USE_SERIAL.println(); - USE_SERIAL.println(); - USE_SERIAL.println(); - - for (uint8_t t = 4; t > 0; t--) { - USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); - USE_SERIAL.flush(); - delay(1000); - } - - wifiMulti.addAP("SSID", "PASSWORD"); - - // allow reuse (if server supports it) - http.setReuse(true); -} - -void loop() { - // wait for WiFi connection - if ((wifiMulti.run() == WL_CONNECTED)) { - - http.begin("http://192.168.1.12/test.html"); - //http.begin("192.168.1.12", 80, "/test.html"); - - int httpCode = http.GET(); - if (httpCode > 0) { - USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode); - - // file found at server - if (httpCode == HTTP_CODE_OK) { - http.writeToStream(&USE_SERIAL); - } - } else { - USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); - } - - http.end(); - } - - delay(1000); -} diff --git a/lib/HTTPClient/examples/ReuseConnection/ci.json b/lib/HTTPClient/examples/ReuseConnection/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/HTTPClient/examples/ReuseConnection/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino b/lib/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino deleted file mode 100644 index 187526f..0000000 --- a/lib/HTTPClient/examples/StreamHttpClient/StreamHttpClient.ino +++ /dev/null @@ -1,97 +0,0 @@ -/** - * StreamHTTPClient.ino - * - * Created on: 24.05.2015 - * - */ - -#include - -#include -#include - -#include - -#define USE_SERIAL Serial - -WiFiMulti wifiMulti; - -void setup() { - - USE_SERIAL.begin(115200); - - USE_SERIAL.println(); - USE_SERIAL.println(); - USE_SERIAL.println(); - - for (uint8_t t = 4; t > 0; t--) { - USE_SERIAL.printf("[SETUP] WAIT %d...\n", t); - USE_SERIAL.flush(); - delay(1000); - } - - wifiMulti.addAP("SSID", "PASSWORD"); -} - -void loop() { - // wait for WiFi connection - if ((wifiMulti.run() == WL_CONNECTED)) { - - HTTPClient http; - - USE_SERIAL.print("[HTTP] begin...\n"); - - // configure server and url - http.begin("http://192.168.1.12/test.html"); - //http.begin("192.168.1.12", 80, "/test.html"); - - USE_SERIAL.print("[HTTP] GET...\n"); - // start connection and send HTTP header - int httpCode = http.GET(); - if (httpCode > 0) { - // HTTP header has been send and Server response header has been handled - USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode); - - // file found at server - if (httpCode == HTTP_CODE_OK) { - - // get length of document (is -1 when Server sends no Content-Length header) - int len = http.getSize(); - - // create buffer for read - uint8_t buff[128] = {0}; - - // get tcp stream - NetworkClient *stream = http.getStreamPtr(); - - // read all data from server - while (http.connected() && (len > 0 || len == -1)) { - // get available data size - size_t size = stream->available(); - - if (size) { - // read up to 128 byte - int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); - - // write it to Serial - USE_SERIAL.write(buff, c); - - if (len > 0) { - len -= c; - } - } - delay(1); - } - - USE_SERIAL.println(); - USE_SERIAL.print("[HTTP] connection closed or file end.\n"); - } - } else { - USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str()); - } - - http.end(); - } - - delay(10000); -} diff --git a/lib/HTTPClient/examples/StreamHttpClient/ci.json b/lib/HTTPClient/examples/StreamHttpClient/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/HTTPClient/examples/StreamHttpClient/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/HTTPClient/library.properties b/lib/HTTPClient/library.properties deleted file mode 100644 index eca8d22..0000000 --- a/lib/HTTPClient/library.properties +++ /dev/null @@ -1,9 +0,0 @@ -name=HTTPClient -version=3.0.3 -author=Markus Sattler -maintainer=Markus Sattler -sentence=HTTP Client for ESP32 -paragraph= -category=Communication -url=https://github.com/espressif/arduino-esp32/tree/master/libraries/HTTPClient -architectures=esp32 diff --git a/lib/HTTPClient/src/HTTPClient.cpp b/lib/HTTPClient/src/HTTPClient.cpp deleted file mode 100644 index 7881a87..0000000 --- a/lib/HTTPClient/src/HTTPClient.cpp +++ /dev/null @@ -1,1645 +0,0 @@ -#include -/** - * HTTPClient.cpp - * - * Created on: 02.11.2015 - * - * Copyright (c) 2015 Markus Sattler. All rights reserved. - * This file is part of the HTTPClient for Arduino. - * Port to ESP32 by Evandro Luis Copercini (2017), - * changed fingerprints to CA verification. - * - * 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 - * - * Adapted in October 2018 - */ - -#include -#include -#include -#include -#include "HTTPClient.h" - -/// Cookie jar support -#include - -#ifdef HTTPCLIENT_1_1_COMPATIBLE -class TransportTraits { -public: - virtual ~TransportTraits() {} - - virtual std::unique_ptr create() { - return std::unique_ptr(new NetworkClient()); - } - - virtual bool verify(NetworkClient &client, const char *host) { - return true; - } -}; - -#ifndef HTTPCLIENT_NOSECURE -class TLSTraits : public TransportTraits { -public: - TLSTraits(const char *CAcert, const char *clicert = nullptr, const char *clikey = nullptr) : _cacert(CAcert), _clicert(clicert), _clikey(clikey) {} - - std::unique_ptr create() override { - return std::unique_ptr(new NetworkClientSecure()); - } - - bool verify(NetworkClient &client, const char *host) override { - NetworkClientSecure &wcs = static_cast(client); - if (_cacert == nullptr) { - wcs.setInsecure(); - } else { - wcs.setCACert(_cacert); - wcs.setCertificate(_clicert); - wcs.setPrivateKey(_clikey); - } - return true; - } - -protected: - const char *_cacert; - const char *_clicert; - const char *_clikey; -}; -#endif // HTTPCLIENT_NOSECURE -#endif // HTTPCLIENT_1_1_COMPATIBLE - -/** - * constructor - */ -HTTPClient::HTTPClient() {} - -/** - * destructor - */ -HTTPClient::~HTTPClient() { - if (_client) { - _client->stop(); - } - if (_currentHeaders) { - delete[] _currentHeaders; - } - if (_tcpDeprecated) { - _tcpDeprecated.reset(nullptr); - } - if (_transportTraits) { - _transportTraits.reset(nullptr); - } -} - -void HTTPClient::clear() { - _returnCode = 0; - _size = -1; - _headers = ""; -} - -/** - * parsing the url for all needed parameters - * @param client Client& - * @param url String - * @param https bool - * @return success bool - */ -bool HTTPClient::begin(NetworkClient &client, String url) { -#ifdef HTTPCLIENT_1_1_COMPATIBLE - if (_tcpDeprecated) { - log_d("mix up of new and deprecated api"); - _canReuse = false; - end(); - } -#endif - - _client = &client; - - // check for : (http: or https:) - int index = url.indexOf(':'); - if (index < 0) { - log_d("failed to parse protocol"); - return false; - } - - String protocol = url.substring(0, index); - if (protocol != "http" && protocol != "https") { - log_d("unknown protocol '%s'", protocol.c_str()); - return false; - } - - _port = (protocol == "https" ? 443 : 80); - _secure = (protocol == "https"); - -#ifdef HTTPCLIENT_NOSECURE - if (_secure) { - return false; - } -#endif // HTTPCLIENT_NOSECURE - return beginInternal(url, protocol.c_str()); -} - -/** - * directly supply all needed parameters - * @param client Client& - * @param host String - * @param port uint16_t - * @param uri String - * @param https bool - * @return success bool - */ -bool HTTPClient::begin(NetworkClient &client, String host, uint16_t port, String uri, bool https) { -#ifdef HTTPCLIENT_1_1_COMPATIBLE - if (_tcpDeprecated) { - log_d("mix up of new and deprecated api"); - _canReuse = false; - end(); - } -#endif - - _client = &client; - - clear(); - _host = host; - _port = port; - _uri = uri; - _protocol = (https ? "https" : "http"); - _secure = https; - -#ifdef HTTPCLIENT_NOSECURE - return _secure ? false : true; -#else - return true; -#endif // HTTPCLIENT_NOSECURE -} - -#ifdef HTTPCLIENT_1_1_COMPATIBLE -#ifndef HTTPCLIENT_NOSECURE -bool HTTPClient::begin(String url, const char *CAcert) { - if (_client && !_tcpDeprecated) { - log_d("mix up of new and deprecated api"); - _canReuse = false; - end(); - } - - clear(); - _port = 443; - if (!beginInternal(url, "https")) { - return false; - } - _secure = true; - _transportTraits = TransportTraitsPtr(new TLSTraits(CAcert)); - if (!_transportTraits) { - log_e("could not create transport traits"); - return false; - } - - return true; -} -#endif // HTTPCLIENT_NOSECURE - -/** - * parsing the url for all needed parameters - * @param url String - */ -bool HTTPClient::begin(String url) { - if (_client && !_tcpDeprecated) { - log_d("mix up of new and deprecated api"); - _canReuse = false; - end(); - } - - clear(); - _port = 80; - if (!beginInternal(url, "http")) { -#ifdef HTTPCLIENT_NOSECURE - return false; -#else - return begin(url, (const char *)NULL); -#endif // HTTPCLIENT_NOSECURE - } - _transportTraits = TransportTraitsPtr(new TransportTraits()); - if (!_transportTraits) { - log_e("could not create transport traits"); - return false; - } - - return true; -} -#endif // HTTPCLIENT_1_1_COMPATIBLE - -bool HTTPClient::beginInternal(String url, const char *expectedProtocol) { - log_v("url: %s", url.c_str()); - - // check for : (http: or https: - int index = url.indexOf(':'); - if (index < 0) { - log_e("failed to parse protocol"); - return false; - } - - _protocol = url.substring(0, index); - if (_protocol != expectedProtocol) { - log_d("unexpected protocol: %s, expected %s", _protocol.c_str(), expectedProtocol); - return false; - } - - url.remove(0, (index + 3)); // remove http:// or https:// - - index = url.indexOf('/'); - if (index == -1) { - index = url.length(); - url += '/'; - } - String host = url.substring(0, index); - url.remove(0, index); // remove host part - - // get Authorization - index = host.indexOf('@'); - if (index >= 0) { - // auth info - String auth = host.substring(0, index); - host.remove(0, index + 1); // remove auth part including @ - _base64Authorization = base64::encode(auth); - } - - // get port - index = host.indexOf(':'); - String the_host; - if (index >= 0) { - the_host = host.substring(0, index); // hostname - host.remove(0, (index + 1)); // remove hostname + : - _port = host.toInt(); // get port - } else { - the_host = host; - } - if (_host != the_host && connected()) { - log_d("switching host from '%s' to '%s'. disconnecting first", _host.c_str(), the_host.c_str()); - _canReuse = false; - disconnect(true); - } - _host = the_host; - _uri = url; - log_d("protocol: %s, host: %s port: %d url: %s", _protocol.c_str(), _host.c_str(), _port, _uri.c_str()); - return true; -} - -#ifdef HTTPCLIENT_1_1_COMPATIBLE -bool HTTPClient::begin(String host, uint16_t port, String uri) { - if (_client && !_tcpDeprecated) { - log_d("mix up of new and deprecated api"); - _canReuse = false; - end(); - } - - clear(); - _host = host; - _port = port; - _uri = uri; - _transportTraits = TransportTraitsPtr(new TransportTraits()); - log_d("host: %s port: %d uri: %s", host.c_str(), port, uri.c_str()); - return true; -} - -#ifndef HTTPCLIENT_NOSECURE -bool HTTPClient::begin(String host, uint16_t port, String uri, const char *CAcert) { - if (_client && !_tcpDeprecated) { - log_d("mix up of new and deprecated api"); - _canReuse = false; - end(); - } - - clear(); - _host = host; - _port = port; - _uri = uri; - - if (strlen(CAcert) == 0) { - return false; - } - _secure = true; - _transportTraits = TransportTraitsPtr(new TLSTraits(CAcert)); - return true; -} - -bool HTTPClient::begin(String host, uint16_t port, String uri, const char *CAcert, const char *cli_cert, const char *cli_key) { - if (_client && !_tcpDeprecated) { - log_d("mix up of new and deprecated api"); - _canReuse = false; - end(); - } - - clear(); - _host = host; - _port = port; - _uri = uri; - - if (strlen(CAcert) == 0) { - return false; - } - _secure = true; - _transportTraits = TransportTraitsPtr(new TLSTraits(CAcert, cli_cert, cli_key)); - return true; -} -#endif // HTTPCLIENT_NOSECURE -#endif // HTTPCLIENT_1_1_COMPATIBLE - -/** - * end - * called after the payload is handled - */ -void HTTPClient::end(void) { - disconnect(false); - clear(); -} - -/** - * disconnect - * close the TCP socket - */ -void HTTPClient::disconnect(bool preserveClient) { - if (connected()) { - if (_client->available() > 0) { - log_d("still data in buffer (%d), clean up.\n", _client->available()); - _client->flush(); - } - - if (_reuse && _canReuse) { - log_d("tcp keep open for reuse"); - } else { - log_d("tcp stop"); - _client->stop(); - if (!preserveClient) { - _client = nullptr; -#ifdef HTTPCLIENT_1_1_COMPATIBLE - if (_tcpDeprecated) { - _transportTraits.reset(nullptr); - _tcpDeprecated.reset(nullptr); - } -#endif - } - } - } else { - log_d("tcp is closed\n"); - } -} - -/** - * connected - * @return connected status - */ -bool HTTPClient::connected() { - if (_client) { - return ((_client->available() > 0) || _client->connected()); - } - return false; -} - -/** - * try to reuse the connection to the server - * keep-alive - * @param reuse bool - */ -void HTTPClient::setReuse(bool reuse) { - _reuse = reuse; -} - -/** - * set User Agent - * @param userAgent const char * - */ -void HTTPClient::setUserAgent(const String &userAgent) { - _userAgent = userAgent; -} - -/** - * set Accept Encoding Header - * @param acceptEncoding const char * - */ -void HTTPClient::setAcceptEncoding(const String &acceptEncoding) { - _acceptEncoding = acceptEncoding; -} - -/** - * set the Authorizatio for the http request - * @param user const char * - * @param password const char * - */ -void HTTPClient::setAuthorization(const char *user, const char *password) { - if (user && password) { - String auth = user; - auth += ":"; - auth += password; - _base64Authorization = base64::encode(auth); - } -} - -/** - * set the Authorizatio for the http request - * @param auth const char * base64 - */ -void HTTPClient::setAuthorization(const char *auth) { - if (auth) { - _base64Authorization = auth; - } -} - -/** - * set the Authorization type for the http request - * @param authType const char * - */ -void HTTPClient::setAuthorizationType(const char *authType) { - if (authType) { - _authorizationType = authType; - } -} - -/** - * set the timeout (ms) for establishing a connection to the server - * @param connectTimeout int32_t - */ -void HTTPClient::setConnectTimeout(int32_t connectTimeout) { - _connectTimeout = connectTimeout; -} - -/** - * set the timeout for the TCP connection - * @param timeout unsigned int - */ -void HTTPClient::setTimeout(uint16_t timeout) { - _tcpTimeout = timeout; - if (connected()) { - _client->setTimeout(timeout); - } -} - -/** - * use HTTP1.0 - * @param use - */ -void HTTPClient::useHTTP10(bool useHTTP10) { - _useHTTP10 = useHTTP10; - _reuse = !useHTTP10; -} - -/** - * send a GET request - * @return http code - */ -int HTTPClient::GET() { - return sendRequest("GET"); -} - -/** - * sends a post request to the server - * @param payload uint8_t * - * @param size size_t - * @return http code - */ -int HTTPClient::POST(uint8_t *payload, size_t size) { - return sendRequest("POST", payload, size); -} - -int HTTPClient::POST(String payload) { - return POST((uint8_t *)payload.c_str(), payload.length()); -} - -/** - * sends a patch request to the server - * @param payload uint8_t * - * @param size size_t - * @return http code - */ -int HTTPClient::PATCH(uint8_t *payload, size_t size) { - return sendRequest("PATCH", payload, size); -} - -int HTTPClient::PATCH(String payload) { - return PATCH((uint8_t *)payload.c_str(), payload.length()); -} - -/** - * sends a put request to the server - * @param payload uint8_t * - * @param size size_t - * @return http code - */ -int HTTPClient::PUT(uint8_t *payload, size_t size) { - return sendRequest("PUT", payload, size); -} - -int HTTPClient::PUT(String payload) { - return PUT((uint8_t *)payload.c_str(), payload.length()); -} - -/** - * sendRequest - * @param type const char * "GET", "POST", .... - * @param payload String data for the message body - * @return - */ -int HTTPClient::sendRequest(const char *type, String payload) { - return sendRequest(type, (uint8_t *)payload.c_str(), payload.length()); -} - -/** - * sendRequest - * @param type const char * "GET", "POST", .... - * @param payload uint8_t * data for the message body if null not send - * @param size size_t size for the message body if 0 not send - * @return -1 if no info or > 0 when Content-Length is set by server - */ -int HTTPClient::sendRequest(const char *type, uint8_t *payload, size_t size) { - int code; - bool redirect = false; - uint16_t redirectCount = 0; - do { - // wipe out any existing headers from previous request - for (size_t i = 0; i < _headerKeysCount; i++) { - if (_currentHeaders[i].value.length() > 0) { - _currentHeaders[i].value.clear(); - } - } - - log_d("request type: '%s' redirCount: %d\n", type, redirectCount); - - // connect to server - if (!connect()) { - return returnError(HTTPC_ERROR_CONNECTION_REFUSED); - } - - if (payload && size > 0) { - addHeader(F("Content-Length"), String(size)); - } - - // add cookies to header, if present - String cookie_string; - if (generateCookieString(&cookie_string)) { - addHeader("Cookie", cookie_string); - } - - // send Header - if (!sendHeader(type)) { - return returnError(HTTPC_ERROR_SEND_HEADER_FAILED); - } - - // send Payload if needed - if (payload && size > 0) { - size_t sent_bytes = 0; - while (sent_bytes < size) { - size_t sent = _client->write(&payload[sent_bytes], size - sent_bytes); - if (sent == 0) { - log_w("Failed to send chunk! Lets wait a bit"); - delay(100); - sent = _client->write(&payload[sent_bytes], size - sent_bytes); - if (sent == 0) { - log_e("Failed to send chunk!"); - break; - } - } - sent_bytes += sent; - } - if (sent_bytes != size) { - return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); - } - } - - code = handleHeaderResponse(); - log_d("sendRequest code=%d\n", code); - - // Handle redirections as stated in RFC document: - // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html - // - // Implementing HTTP_CODE_FOUND as redirection with GET method, - // to follow most of existing user agent implementations. - // - redirect = false; - if (_followRedirects != HTTPC_DISABLE_FOLLOW_REDIRECTS && redirectCount < _redirectLimit && _location.length() > 0) { - switch (code) { - // redirecting using the same method - case HTTP_CODE_MOVED_PERMANENTLY: - case HTTP_CODE_TEMPORARY_REDIRECT: - { - if ( - // allow to force redirections on other methods - // (the RFC require user to accept the redirection) - _followRedirects == HTTPC_FORCE_FOLLOW_REDIRECTS || - // allow GET and HEAD methods without force - !strcmp(type, "GET") || !strcmp(type, "HEAD")) { - redirectCount += 1; - log_d("following redirect (the same method): '%s' redirCount: %d\n", _location.c_str(), redirectCount); - if (!setURL(_location)) { - log_d("failed setting URL for redirection\n"); - // no redirection - break; - } - // redirect using the same request method and payload, different URL - redirect = true; - } - break; - } - // redirecting with method dropped to GET or HEAD - // note: it does not need `HTTPC_FORCE_FOLLOW_REDIRECTS` for any method - case HTTP_CODE_FOUND: - case HTTP_CODE_SEE_OTHER: - { - redirectCount += 1; - log_d("following redirect (dropped to GET/HEAD): '%s' redirCount: %d\n", _location.c_str(), redirectCount); - if (!setURL(_location)) { - log_d("failed setting URL for redirection\n"); - // no redirection - break; - } - // redirect after changing method to GET/HEAD and dropping payload - type = "GET"; - payload = nullptr; - size = 0; - redirect = true; - break; - } - - default: break; - } - } - - } while (redirect); - // handle Server Response (Header) - return returnError(code); -} - -/** - * sendRequest - * @param type const char * "GET", "POST", .... - * @param stream Stream * data stream for the message body - * @param size size_t size for the message body if 0 not Content-Length is send - * @return -1 if no info or > 0 when Content-Length is set by server - */ -int HTTPClient::sendRequest(const char *type, Stream *stream, size_t size) { - - if (!stream) { - return returnError(HTTPC_ERROR_NO_STREAM); - } - - // connect to server - if (!connect()) { - return returnError(HTTPC_ERROR_CONNECTION_REFUSED); - } - - if (size > 0) { - addHeader("Content-Length", String(size)); - } - - // add cookies to header, if present - String cookie_string; - if (generateCookieString(&cookie_string)) { - addHeader("Cookie", cookie_string); - } - - // send Header - if (!sendHeader(type)) { - return returnError(HTTPC_ERROR_SEND_HEADER_FAILED); - } - - int buff_size = HTTP_TCP_TX_BUFFER_SIZE; - - int len = size; - int bytesWritten = 0; - - if (len == 0) { - len = -1; - } - - // if possible create smaller buffer then HTTP_TCP_TX_BUFFER_SIZE - if ((len > 0) && (len < buff_size)) { - buff_size = len; - } - - // create buffer for read - uint8_t *buff = (uint8_t *)malloc(buff_size); - - if (buff) { - // read all data from stream and send it to server - while (connected() && (stream->available() > -1) && (len > 0 || len == -1)) { - - // get available data size - int sizeAvailable = stream->available(); - - if (sizeAvailable) { - - int readBytes = sizeAvailable; - - // read only the asked bytes - if (len > 0 && readBytes > len) { - readBytes = len; - } - - // not read more the buffer can handle - if (readBytes > buff_size) { - readBytes = buff_size; - } - - // read data - int bytesRead = stream->readBytes(buff, readBytes); - - // write it to Stream - int bytesWrite = _client->write((const uint8_t *)buff, bytesRead); - bytesWritten += bytesWrite; - - // are all Bytes a written to stream ? - if (bytesWrite != bytesRead) { - log_d("short write, asked for %d but got %d retry...", bytesRead, bytesWrite); - - // check for write error - if (_client->getWriteError()) { - log_d("stream write error %d", _client->getWriteError()); - - //reset write error for retry - _client->clearWriteError(); - } - - // some time for the stream - delay(1); - - int leftBytes = (readBytes - bytesWrite); - - // retry to send the missed bytes - bytesWrite = _client->write((const uint8_t *)(buff + bytesWrite), leftBytes); - bytesWritten += bytesWrite; - - if (bytesWrite != leftBytes) { - // failed again - log_d("short write, asked for %d but got %d failed.", leftBytes, bytesWrite); - free(buff); - return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); - } - } - - // check for write error - if (_client->getWriteError()) { - log_d("stream write error %d", _client->getWriteError()); - free(buff); - return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); - } - - // count bytes to read left - if (len > 0) { - len -= readBytes; - } - - delay(0); - } else { - delay(1); - } - } - - free(buff); - - if (size && (int)size != bytesWritten) { - log_d("Stream payload bytesWritten %d and size %d mismatch!.", bytesWritten, size); - log_d("ERROR SEND PAYLOAD FAILED!"); - return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED); - } else { - log_d("Stream payload written: %d", bytesWritten); - } - - } else { - log_d("too less ram! need %d", buff_size); - return returnError(HTTPC_ERROR_TOO_LESS_RAM); - } - - // handle Server Response (Header) - return returnError(handleHeaderResponse()); -} - -/** - * size of message body / payload - * @return -1 if no info or > 0 when Content-Length is set by server - */ -int HTTPClient::getSize(void) { - return _size; -} - -/** - * returns the stream of the tcp connection - * @return NetworkClient - */ -NetworkClient &HTTPClient::getStream(void) { - if (connected()) { - return *_client; - } - - log_w("getStream: not connected"); - static NetworkClient empty; - return empty; -} - -/** - * returns a pointer to the stream of the tcp connection - * @return NetworkClient* - */ -NetworkClient *HTTPClient::getStreamPtr(void) { - if (connected()) { - return _client; - } - - log_w("getStreamPtr: not connected"); - return nullptr; -} - -/** - * write all message body / payload to Stream - * @param stream Stream * - * @return bytes written ( negative values are error codes ) - */ -int HTTPClient::writeToStream(Stream *stream) { - - if (!stream) { - return returnError(HTTPC_ERROR_NO_STREAM); - } - - if (!connected()) { - return returnError(HTTPC_ERROR_NOT_CONNECTED); - } - - // get length of document (is -1 when Server sends no Content-Length header) - int len = _size; - int ret = 0; - - if (_transferEncoding == HTTPC_TE_IDENTITY) { - ret = writeToStreamDataBlock(stream, len); - - // have we an error? - if (ret < 0) { - return returnError(ret); - } - } else if (_transferEncoding == HTTPC_TE_CHUNKED) { - int size = 0; - while (1) { - if (!connected()) { - return returnError(HTTPC_ERROR_CONNECTION_LOST); - } - String chunkHeader = _client->readStringUntil('\n'); - - if (chunkHeader.length() <= 0) { - return returnError(HTTPC_ERROR_READ_TIMEOUT); - } - - chunkHeader.trim(); // remove \r - - // read size of chunk - len = (uint32_t)strtol((const char *)chunkHeader.c_str(), NULL, 16); - size += len; - log_v(" read chunk len: %d", len); - - // data left? - if (len > 0) { - int r = writeToStreamDataBlock(stream, len); - if (r < 0) { - // error in writeToStreamDataBlock - return returnError(r); - } - ret += r; - } else { - - // if no length Header use global chunk size - if (_size <= 0) { - _size = size; - } - - // check if we have write all data out - if (ret != _size) { - return returnError(HTTPC_ERROR_STREAM_WRITE); - } - break; - } - - // read trailing \r\n at the end of the chunk - char buf[2]; - auto trailing_seq_len = _client->readBytes((uint8_t *)buf, 2); - if (trailing_seq_len != 2 || buf[0] != '\r' || buf[1] != '\n') { - return returnError(HTTPC_ERROR_READ_TIMEOUT); - } - - delay(0); - } - } else { - return returnError(HTTPC_ERROR_ENCODING); - } - - // end(); - disconnect(true); - return ret; -} - -/** - * return all payload as String (may need lot of ram or trigger out of memory!) - * @return String - */ -String HTTPClient::getString(void) { - // _size can be -1 when Server sends no Content-Length header - if (_size > 0 || _size == -1) { - StreamString sstring; - // try to reserve needed memory (noop if _size == -1) - if (sstring.reserve((_size + 1))) { - writeToStream(&sstring); - return sstring; - } else { - log_d("not enough memory to reserve a string! need: %d", (_size + 1)); - } - } - - return ""; -} - -/** - * converts error code to String - * @param error int - * @return String - */ -String HTTPClient::errorToString(int error) { - switch (error) { - case HTTPC_ERROR_CONNECTION_REFUSED: return F("connection refused"); - case HTTPC_ERROR_SEND_HEADER_FAILED: return F("send header failed"); - case HTTPC_ERROR_SEND_PAYLOAD_FAILED: return F("send payload failed"); - case HTTPC_ERROR_NOT_CONNECTED: return F("not connected"); - case HTTPC_ERROR_CONNECTION_LOST: return F("connection lost"); - case HTTPC_ERROR_NO_STREAM: return F("no stream"); - case HTTPC_ERROR_NO_HTTP_SERVER: return F("no HTTP server"); - case HTTPC_ERROR_TOO_LESS_RAM: return F("too less ram"); - case HTTPC_ERROR_ENCODING: return F("Transfer-Encoding not supported"); - case HTTPC_ERROR_STREAM_WRITE: return F("Stream write error"); - case HTTPC_ERROR_READ_TIMEOUT: return F("read Timeout"); - default: return String(); - } -} - -/** - * adds Header to the request - * @param name - * @param value - * @param first - */ -void HTTPClient::addHeader(const String &name, const String &value, bool first, bool replace) { - // not allow set of Header handled by code - if (!name.equalsIgnoreCase(F("Connection")) && !name.equalsIgnoreCase(F("User-Agent")) && !name.equalsIgnoreCase(F("Accept-Encoding")) - && !name.equalsIgnoreCase(F("Host")) && !(name.equalsIgnoreCase(F("Authorization")) && _base64Authorization.length())) { - - String headerLine = name; - headerLine += ": "; - - if (replace) { - int headerStart = _headers.indexOf(headerLine); - if (headerStart != -1 && (headerStart == 0 || _headers[headerStart - 1] == '\n')) { - int headerEnd = _headers.indexOf('\n', headerStart); - _headers = _headers.substring(0, headerStart) + _headers.substring(headerEnd + 1); - } - } - - headerLine += value; - headerLine += "\r\n"; - if (first) { - _headers = headerLine + _headers; - } else { - _headers += headerLine; - } - } -} - -void HTTPClient::collectHeaders(const char *headerKeys[], const size_t headerKeysCount) { - _headerKeysCount = headerKeysCount; - if (_currentHeaders) { - delete[] _currentHeaders; - } - _currentHeaders = new RequestArgument[_headerKeysCount]; - for (size_t i = 0; i < _headerKeysCount; i++) { - _currentHeaders[i].key = headerKeys[i]; - } -} - -String HTTPClient::header(const char *name) { - for (size_t i = 0; i < _headerKeysCount; ++i) { - if (_currentHeaders[i].key.equalsIgnoreCase(name)) { - return _currentHeaders[i].value; - } - } - return String(); -} - -String HTTPClient::header(size_t i) { - if (i < _headerKeysCount) { - return _currentHeaders[i].value; - } - return String(); -} - -String HTTPClient::headerName(size_t i) { - if (i < _headerKeysCount) { - return _currentHeaders[i].key; - } - return String(); -} - -int HTTPClient::headers() { - return _headerKeysCount; -} - -bool HTTPClient::hasHeader(const char *name) { - for (size_t i = 0; i < _headerKeysCount; ++i) { - if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0)) { - return true; - } - } - return false; -} - -/** - * init TCP connection and handle ssl verify if needed - * @return true if connection is ok - */ -bool HTTPClient::connect(void) { - if (connected()) { - if (_reuse) { - log_d("already connected, reusing connection"); - } else { - log_d("already connected, try reuse!"); - } - while (_client->available() > 0) { - _client->read(); - } - return true; - } - -#ifdef HTTPCLIENT_1_1_COMPATIBLE - if (_transportTraits && !_client) { - _tcpDeprecated = _transportTraits->create(); - if (!_tcpDeprecated) { - log_e("failed to create client"); - return false; - } - _client = _tcpDeprecated.get(); - } -#endif - - if (!_client) { - log_d("HTTPClient::begin was not called or returned error"); - return false; - } -#ifdef HTTPCLIENT_1_1_COMPATIBLE - if (_tcpDeprecated && !_transportTraits->verify(*_client, _host.c_str())) { - log_d("transport level verify failed"); - _client->stop(); - return false; - } -#endif - if (!_client->connect(_host.c_str(), _port, _connectTimeout)) { - log_d("failed connect to %s:%u", _host.c_str(), _port); - return false; - } - - // set Timeout for NetworkClient and for Stream::readBytesUntil() and Stream::readStringUntil() - _client->setTimeout(_tcpTimeout); - - log_d(" connected to %s:%u", _host.c_str(), _port); - - /* -#ifdef ESP8266 - _client->setNoDelay(true); -#endif - */ - return connected(); -} - -/** - * sends HTTP request header - * @param type (GET, POST, ...) - * @return status - */ -bool HTTPClient::sendHeader(const char *type) { - if (!connected()) { - return false; - } - - String header = String(type) + " " + _uri + F(" HTTP/1."); - - if (_useHTTP10) { - header += "0"; - } else { - header += "1"; - } - - header += String(F("\r\nHost: ")) + _host; - if (_port != 80 && _port != 443) { - header += ':'; - header += String(_port); - } - header += String(F("\r\nUser-Agent: ")) + _userAgent + F("\r\nConnection: "); - - if (_reuse) { - header += F("keep-alive"); - } else { - header += F("close"); - } - header += "\r\n"; - - if (!_useHTTP10) { - header += String(F("Accept-Encoding: ")) + _acceptEncoding + F("\r\n"); - } - - if (_base64Authorization.length()) { - _base64Authorization.replace("\n", ""); - header += F("Authorization: "); - header += _authorizationType; - header += " "; - header += _base64Authorization; - header += "\r\n"; - } - - header += _headers + "\r\n"; - - return (_client->write((const uint8_t *)header.c_str(), header.length()) == header.length()); -} - -/** - * reads the response from the server - * @return int http code - */ -int HTTPClient::handleHeaderResponse() { - - if (!connected()) { - return HTTPC_ERROR_NOT_CONNECTED; - } - - _returnCode = 0; - _size = -1; - _canReuse = _reuse; - - String transferEncoding; - - _transferEncoding = HTTPC_TE_IDENTITY; - unsigned long lastDataTime = millis(); - bool firstLine = true; - String date; - - while (connected()) { - size_t len = _client->available(); - if (len > 0) { - String headerLine = _client->readStringUntil('\n'); - headerLine.trim(); // remove \r - - lastDataTime = millis(); - - log_v("RX: '%s'", headerLine.c_str()); - - if (firstLine) { - firstLine = false; - if (_canReuse && headerLine.startsWith("HTTP/1.")) { - _canReuse = (headerLine[sizeof "HTTP/1." - 1] != '0'); - } - int codePos = headerLine.indexOf(' ') + 1; - _returnCode = headerLine.substring(codePos, headerLine.indexOf(' ', codePos)).toInt(); - } else if (headerLine.indexOf(':')) { - String headerName = headerLine.substring(0, headerLine.indexOf(':')); - String headerValue = headerLine.substring(headerLine.indexOf(':') + 1); - headerValue.trim(); - - if (headerName.equalsIgnoreCase("Date")) { - date = headerValue; - } - - if (headerName.equalsIgnoreCase("Content-Length")) { - _size = headerValue.toInt(); - } - - if (_canReuse && headerName.equalsIgnoreCase("Connection")) { - if (headerValue.indexOf("close") >= 0 && headerValue.indexOf("keep-alive") < 0) { - _canReuse = false; - } - } - - if (headerName.equalsIgnoreCase("Transfer-Encoding")) { - transferEncoding = headerValue; - } - - if (headerName.equalsIgnoreCase("Location")) { - _location = headerValue; - } - - if (headerName.equalsIgnoreCase("Set-Cookie")) { - setCookie(date, headerValue); - } - - for (size_t i = 0; i < _headerKeysCount; i++) { - if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) { - // Uncomment the following lines if you need to add support for multiple headers with the same key: - // if (!_currentHeaders[i].value.isEmpty()) { - // // Existing value, append this one with a comma - // _currentHeaders[i].value += ','; - // _currentHeaders[i].value += headerValue; - // } else { - _currentHeaders[i].value = headerValue; - // } - break; // We found a match, stop looking - } - } - } - - if (headerLine == "") { - log_d("code: %d", _returnCode); - - if (_size > 0) { - log_d("size: %d", _size); - } - - if (transferEncoding.length() > 0) { - log_d("Transfer-Encoding: %s", transferEncoding.c_str()); - if (transferEncoding.equalsIgnoreCase("chunked")) { - _transferEncoding = HTTPC_TE_CHUNKED; - } else if (transferEncoding.equalsIgnoreCase("identity")) { - _transferEncoding = HTTPC_TE_IDENTITY; - } else { - return HTTPC_ERROR_ENCODING; - } - } else { - _transferEncoding = HTTPC_TE_IDENTITY; - } - - if (_returnCode) { - return _returnCode; - } else { - log_d("Remote host is not an HTTP Server!"); - return HTTPC_ERROR_NO_HTTP_SERVER; - } - } - - } else { - if ((millis() - lastDataTime) > _tcpTimeout) { - return HTTPC_ERROR_READ_TIMEOUT; - } - delay(10); - } - } - - return HTTPC_ERROR_CONNECTION_LOST; -} - -/** - * write one Data Block to Stream - * @param stream Stream * - * @param size int - * @return < 0 = error >= 0 = size written - */ -int HTTPClient::writeToStreamDataBlock(Stream *stream, int size) { - int buff_size = HTTP_TCP_RX_BUFFER_SIZE; - int len = size; - int bytesWritten = 0; - - // if possible create smaller buffer then HTTP_TCP_RX_BUFFER_SIZE - if ((len > 0) && (len < buff_size)) { - buff_size = len; - } - - // create buffer for read - uint8_t *buff = (uint8_t *)malloc(buff_size); - - if (buff) { - // read all data from server - while (connected() && (len > 0 || len == -1)) { - - // get available data size - size_t sizeAvailable = buff_size; - if (len < 0) { - sizeAvailable = _client->available(); - } - - if (sizeAvailable) { - - int readBytes = sizeAvailable; - - // read only the asked bytes - if (len > 0 && readBytes > len) { - readBytes = len; - } - - // not read more the buffer can handle - if (readBytes > buff_size) { - readBytes = buff_size; - } - - // stop if no more reading - if (readBytes == 0) { - break; - } - - // read data - int bytesRead = _client->readBytes(buff, readBytes); - - // write it to Stream - int bytesWrite = stream->write(buff, bytesRead); - bytesWritten += bytesWrite; - - // are all Bytes a written to stream ? - if (bytesWrite != bytesRead) { - log_d("short write asked for %d but got %d retry...", bytesRead, bytesWrite); - - // check for write error - if (stream->getWriteError()) { - log_d("stream write error %d", stream->getWriteError()); - - //reset write error for retry - stream->clearWriteError(); - } - - // some time for the stream - delay(1); - - int leftBytes = (bytesRead - bytesWrite); - - // retry to send the missed bytes - bytesWrite = stream->write((buff + bytesWrite), leftBytes); - bytesWritten += bytesWrite; - - if (bytesWrite != leftBytes) { - // failed again - log_w("short write asked for %d but got %d failed.", leftBytes, bytesWrite); - free(buff); - return HTTPC_ERROR_STREAM_WRITE; - } - } - - // check for write error - if (stream->getWriteError()) { - log_w("stream write error %d", stream->getWriteError()); - free(buff); - return HTTPC_ERROR_STREAM_WRITE; - } - - // count bytes to read left - if (len > 0) { - len -= bytesRead; - } - - delay(0); - } else { - delay(1); - } - } - - free(buff); - - log_v("connection closed or file end (written: %d).", bytesWritten); - - if ((size > 0) && (size != bytesWritten)) { - log_d("bytesWritten %d and size %d mismatch!.", bytesWritten, size); - return HTTPC_ERROR_STREAM_WRITE; - } - - } else { - log_w("too less ram! need %d", buff_size); - return HTTPC_ERROR_TOO_LESS_RAM; - } - - return bytesWritten; -} - -/** - * called to handle error return, may disconnect the connection if still exists - * @param error - * @return error - */ -int HTTPClient::returnError(int error) { - if (error < 0) { - log_w("error(%d): %s", error, errorToString(error).c_str()); - if (connected()) { - log_d("tcp stop"); - _client->stop(); - } - } - return error; -} - -void HTTPClient::setFollowRedirects(followRedirects_t follow) { - _followRedirects = follow; -} - -void HTTPClient::setRedirectLimit(uint16_t limit) { - _redirectLimit = limit; -} - -/** - * set the URL to a new value. Handy for following redirects. - * @param url - */ -bool HTTPClient::setURL(const String &url) { - // if the new location is only a path then only update the URI - if (url && url[0] == '/') { - _uri = url; - clear(); - return true; - } - - if (!url.startsWith(_protocol + ':')) { - log_d("new URL not the same protocol, expected '%s', URL: '%s'\n", _protocol.c_str(), url.c_str()); - return false; - } - - // check if the port is specified - int indexPort = url.indexOf(':', 6); // find the first ':' excluding the one from the protocol - int indexURI = url.indexOf('/', 7); // find where the URI starts to make sure the ':' is not part of it - if (indexPort == -1 || indexPort > indexURI) { - // the port is not specified - _port = (_protocol == "https" ? 443 : 80); - } - - // disconnect but preserve _client. - // Also have to keep the connection otherwise it will free some of the memory used by _client - // and will blow up later when trying to do _client->available() or similar - _canReuse = true; - disconnect(true); - return beginInternal(url, _protocol.c_str()); -} - -const String &HTTPClient::getLocation(void) { - return _location; -} - -void HTTPClient::setCookieJar(CookieJar *cookieJar) { - _cookieJar = cookieJar; -} - -void HTTPClient::resetCookieJar() { - _cookieJar = nullptr; -} - -void HTTPClient::clearAllCookies() { - if (_cookieJar) { - _cookieJar->clear(); - } -} - -void HTTPClient::setCookie(String date, String headerValue) { - if (!_cookieJar) { - return; - } -#define HTTP_TIME_PATTERN "%a, %d %b %Y %H:%M:%S" - - Cookie cookie; - String value; - int pos1, pos2; - - struct tm tm; - strptime(date.c_str(), HTTP_TIME_PATTERN, &tm); - cookie.date = mktime(&tm); - - pos1 = headerValue.indexOf('='); - pos2 = headerValue.indexOf(';'); - - if (pos1 >= 0 && pos2 > pos1) { - cookie.name = headerValue.substring(0, pos1); - cookie.value = headerValue.substring(pos1 + 1, pos2); - } else { - return; // invalid cookie header - } - - // only Cookie Attributes are case insensitive from this point on - headerValue.toLowerCase(); - - // expires - if (headerValue.indexOf("expires=") >= 0) { - pos1 = headerValue.indexOf("expires=") + strlen("expires="); - pos2 = headerValue.indexOf(';', pos1); - - if (pos2 > pos1) { - value = headerValue.substring(pos1, pos2); - } else { - value = headerValue.substring(pos1); - } - - strptime(value.c_str(), HTTP_TIME_PATTERN, &tm); - cookie.expires.date = mktime(&tm); - cookie.expires.valid = true; - } - - // max-age - if (headerValue.indexOf("max-age=") >= 0) { - pos1 = headerValue.indexOf("max-age=") + strlen("max-age="); - pos2 = headerValue.indexOf(';', pos1); - - if (pos2 > pos1) { - value = headerValue.substring(pos1, pos2); - } else { - value = headerValue.substring(pos1); - } - - cookie.max_age.duration = value.toInt(); - cookie.max_age.valid = true; - } - - // domain - if (headerValue.indexOf("domain=") >= 0) { - pos1 = headerValue.indexOf("domain=") + strlen("domain="); - pos2 = headerValue.indexOf(';', pos1); - - if (pos2 > pos1) { - value = headerValue.substring(pos1, pos2); - } else { - value = headerValue.substring(pos1); - } - - if (value.startsWith(".")) { - value.remove(0, 1); - } - - if (_host.indexOf(value) >= 0) { - cookie.domain = value; - } else { - return; // server tries to set a cookie on a different domain; ignore it - } - } else { - pos1 = _host.lastIndexOf('.', _host.lastIndexOf('.') - 1); - if (pos1 >= 0) { - cookie.domain = _host.substring(pos1 + 1); - } else { - cookie.domain = _host; - } - } - - // path - if (headerValue.indexOf("path=") >= 0) { - pos1 = headerValue.indexOf("path=") + strlen("path="); - pos2 = headerValue.indexOf(';', pos1); - - if (pos2 > pos1) { - cookie.path = headerValue.substring(pos1, pos2); - } else { - cookie.path = headerValue.substring(pos1); - } - } - - // HttpOnly - cookie.http_only = (headerValue.indexOf("httponly") >= 0); - - // secure - cookie.secure = (headerValue.indexOf("secure") >= 0); - - // overwrite or delete cookie in/from cookie jar - time_t now_local = time(NULL); - time_t now_gmt = mktime(gmtime(&now_local)); - - bool found = false; - - for (auto c = _cookieJar->begin(); c != _cookieJar->end(); ++c) { - if (c->domain == cookie.domain && c->name == cookie.name) { - // when evaluating, max-age takes precedence over expires if both are defined - if ((cookie.max_age.valid && ((cookie.date + cookie.max_age.duration) < now_gmt)) || cookie.max_age.duration <= 0 - || (!cookie.max_age.valid && cookie.expires.valid && cookie.expires.date < now_gmt)) { - _cookieJar->erase(c); - c--; - } else { - *c = cookie; - } - found = true; - } - } - - // add cookie to jar - if (!found && !(cookie.max_age.valid && cookie.max_age.duration <= 0)) { - _cookieJar->push_back(cookie); - } -} - -bool HTTPClient::generateCookieString(String *cookieString) { - time_t now_local = time(NULL); - time_t now_gmt = mktime(gmtime(&now_local)); - - *cookieString = ""; - bool found = false; - - if (!_cookieJar) { - return false; - } - for (auto c = _cookieJar->begin(); c != _cookieJar->end(); ++c) { - if ((c->max_age.valid && ((c->date + c->max_age.duration) < now_gmt)) || (!c->max_age.valid && c->expires.valid && c->expires.date < now_gmt)) { - _cookieJar->erase(c); - c--; - } else if (_host.indexOf(c->domain) >= 0 && (!c->secure || _secure)) { - if (*cookieString == "") { - *cookieString = c->name + "=" + c->value; - } else { - *cookieString += " ;" + c->name + "=" + c->value; - } - found = true; - } - } - - return found; -} diff --git a/lib/HTTPClient/src/HTTPClient.h b/lib/HTTPClient/src/HTTPClient.h deleted file mode 100644 index 80f6da2..0000000 --- a/lib/HTTPClient/src/HTTPClient.h +++ /dev/null @@ -1,322 +0,0 @@ -/** - * HTTPClient.h - * - * Created on: 02.11.2015 - * - * Copyright (c) 2015 Markus Sattler. All rights reserved. - * This file is part of the HTTPClient for Arduino. - * Port to ESP32 by Evandro Luis Copercini (2017), - * changed fingerprints to CA verification. - * - * 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 - * - */ - -#ifndef HTTPClient_H_ -#define HTTPClient_H_ - -#ifndef HTTPCLIENT_1_1_COMPATIBLE -#define HTTPCLIENT_1_1_COMPATIBLE -#endif - -#include -#include -#include -#ifndef HTTPCLIENT_NOSECURE -#include -#endif // HTTPCLIENT_NOSECURE - -/// Cookie jar support -#include - -#define HTTPCLIENT_DEFAULT_TCP_TIMEOUT (5000) - -/// HTTP client errors -#define HTTPC_ERROR_CONNECTION_REFUSED (-1) -#define HTTPC_ERROR_SEND_HEADER_FAILED (-2) -#define HTTPC_ERROR_SEND_PAYLOAD_FAILED (-3) -#define HTTPC_ERROR_NOT_CONNECTED (-4) -#define HTTPC_ERROR_CONNECTION_LOST (-5) -#define HTTPC_ERROR_NO_STREAM (-6) -#define HTTPC_ERROR_NO_HTTP_SERVER (-7) -#define HTTPC_ERROR_TOO_LESS_RAM (-8) -#define HTTPC_ERROR_ENCODING (-9) -#define HTTPC_ERROR_STREAM_WRITE (-10) -#define HTTPC_ERROR_READ_TIMEOUT (-11) - -/// size for the stream handling -#define HTTP_TCP_RX_BUFFER_SIZE (4096) -#define HTTP_TCP_TX_BUFFER_SIZE (1460) - -/// HTTP codes see RFC7231 -typedef enum { - HTTP_CODE_CONTINUE = 100, - HTTP_CODE_SWITCHING_PROTOCOLS = 101, - HTTP_CODE_PROCESSING = 102, - HTTP_CODE_OK = 200, - HTTP_CODE_CREATED = 201, - HTTP_CODE_ACCEPTED = 202, - HTTP_CODE_NON_AUTHORITATIVE_INFORMATION = 203, - HTTP_CODE_NO_CONTENT = 204, - HTTP_CODE_RESET_CONTENT = 205, - HTTP_CODE_PARTIAL_CONTENT = 206, - HTTP_CODE_MULTI_STATUS = 207, - HTTP_CODE_ALREADY_REPORTED = 208, - HTTP_CODE_IM_USED = 226, - HTTP_CODE_MULTIPLE_CHOICES = 300, - HTTP_CODE_MOVED_PERMANENTLY = 301, - HTTP_CODE_FOUND = 302, - HTTP_CODE_SEE_OTHER = 303, - HTTP_CODE_NOT_MODIFIED = 304, - HTTP_CODE_USE_PROXY = 305, - HTTP_CODE_TEMPORARY_REDIRECT = 307, - HTTP_CODE_PERMANENT_REDIRECT = 308, - HTTP_CODE_BAD_REQUEST = 400, - HTTP_CODE_UNAUTHORIZED = 401, - HTTP_CODE_PAYMENT_REQUIRED = 402, - HTTP_CODE_FORBIDDEN = 403, - HTTP_CODE_NOT_FOUND = 404, - HTTP_CODE_METHOD_NOT_ALLOWED = 405, - HTTP_CODE_NOT_ACCEPTABLE = 406, - HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED = 407, - HTTP_CODE_REQUEST_TIMEOUT = 408, - HTTP_CODE_CONFLICT = 409, - HTTP_CODE_GONE = 410, - HTTP_CODE_LENGTH_REQUIRED = 411, - HTTP_CODE_PRECONDITION_FAILED = 412, - HTTP_CODE_PAYLOAD_TOO_LARGE = 413, - HTTP_CODE_URI_TOO_LONG = 414, - HTTP_CODE_UNSUPPORTED_MEDIA_TYPE = 415, - HTTP_CODE_RANGE_NOT_SATISFIABLE = 416, - HTTP_CODE_EXPECTATION_FAILED = 417, - HTTP_CODE_MISDIRECTED_REQUEST = 421, - HTTP_CODE_UNPROCESSABLE_ENTITY = 422, - HTTP_CODE_LOCKED = 423, - HTTP_CODE_FAILED_DEPENDENCY = 424, - HTTP_CODE_UPGRADE_REQUIRED = 426, - HTTP_CODE_PRECONDITION_REQUIRED = 428, - HTTP_CODE_TOO_MANY_REQUESTS = 429, - HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, - HTTP_CODE_INTERNAL_SERVER_ERROR = 500, - HTTP_CODE_NOT_IMPLEMENTED = 501, - HTTP_CODE_BAD_GATEWAY = 502, - HTTP_CODE_SERVICE_UNAVAILABLE = 503, - HTTP_CODE_GATEWAY_TIMEOUT = 504, - HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED = 505, - HTTP_CODE_VARIANT_ALSO_NEGOTIATES = 506, - HTTP_CODE_INSUFFICIENT_STORAGE = 507, - HTTP_CODE_LOOP_DETECTED = 508, - HTTP_CODE_NOT_EXTENDED = 510, - HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED = 511 -} t_http_codes; - -typedef enum { - HTTPC_TE_IDENTITY, - HTTPC_TE_CHUNKED -} transferEncoding_t; - -/** - * redirection follow mode. - * + `HTTPC_DISABLE_FOLLOW_REDIRECTS` - no redirection will be followed. - * + `HTTPC_STRICT_FOLLOW_REDIRECTS` - strict RFC2616, only requests using - * GET or HEAD methods will be redirected (using the same method), - * since the RFC requires end-user confirmation in other cases. - * + `HTTPC_FORCE_FOLLOW_REDIRECTS` - all redirections will be followed, - * regardless of a used method. New request will use the same method, - * and they will include the same body data and the same headers. - * In the sense of the RFC, it's just like every redirection is confirmed. - */ -typedef enum { - HTTPC_DISABLE_FOLLOW_REDIRECTS, - HTTPC_STRICT_FOLLOW_REDIRECTS, - HTTPC_FORCE_FOLLOW_REDIRECTS -} followRedirects_t; - -#ifdef HTTPCLIENT_1_1_COMPATIBLE -class TransportTraits; -typedef std::unique_ptr TransportTraitsPtr; -#endif - -// cookie jar support -typedef struct { - String host; // host which tries to set the cookie - time_t date; // timestamp of the response that set the cookie - String name; - String value; - String domain; - String path = ""; - struct { - time_t date = 0; - bool valid = false; - } expires; - struct { - time_t duration = 0; - bool valid = false; - } max_age; - bool http_only = false; - bool secure = false; -} Cookie; -typedef std::vector CookieJar; - -class HTTPClient { -public: - HTTPClient(); - ~HTTPClient(); - - /* - * Since both begin() functions take a reference to client as a parameter, you need to - * ensure the client object lives the entire time of the HTTPClient - */ - bool begin(NetworkClient &client, String url); - bool begin(NetworkClient &client, String host, uint16_t port, String uri = "/", bool https = false); - -#ifdef HTTPCLIENT_1_1_COMPATIBLE - bool begin(String url); - bool begin(String host, uint16_t port, String uri = "/"); -#ifndef HTTPCLIENT_NOSECURE - bool begin(String url, const char *CAcert); - bool begin(String host, uint16_t port, String uri, const char *CAcert); - bool begin(String host, uint16_t port, String uri, const char *CAcert, const char *cli_cert, const char *cli_key); -#else - bool begin(String url, const char *CAcert) { - return false; - }; - bool begin(String host, uint16_t port, String uri, const char *CAcert) { - return false; - }; - bool begin(String host, uint16_t port, String uri, const char *CAcert, const char *cli_cert, const char *cli_key) { - return false; - }; -#endif // HTTPCLIENT_NOSECURE - -#endif - - void end(void); - - bool connected(void); - - void setReuse(bool reuse); /// keep-alive - void setUserAgent(const String &userAgent); - void setAcceptEncoding(const String &acceptEncoding); - void setAuthorization(const char *user, const char *password); - void setAuthorization(const char *auth); - void setAuthorizationType(const char *authType); - void setConnectTimeout(int32_t connectTimeout); - void setTimeout(uint16_t timeout); - - // Redirections - void setFollowRedirects(followRedirects_t follow); - void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request - - bool setURL(const String &url); - void useHTTP10(bool usehttp10 = true); - - /// request handling - int GET(); - int PATCH(uint8_t *payload, size_t size); - int PATCH(String payload); - int POST(uint8_t *payload, size_t size); - int POST(String payload); - int PUT(uint8_t *payload, size_t size); - int PUT(String payload); - int sendRequest(const char *type, String payload); - int sendRequest(const char *type, uint8_t *payload = NULL, size_t size = 0); - int sendRequest(const char *type, Stream *stream, size_t size = 0); - - void addHeader(const String &name, const String &value, bool first = false, bool replace = true); - - /// Response handling - void collectHeaders(const char *headerKeys[], const size_t headerKeysCount); - String header(const char *name); // get request header value by name - String header(size_t i); // get request header value by number - String headerName(size_t i); // get request header name by number - int headers(); // get header count - bool hasHeader(const char *name); // check if header exists - - int getSize(void); - const String &getLocation(void); - - NetworkClient &getStream(void); - NetworkClient *getStreamPtr(void); - int writeToStream(Stream *stream); - String getString(void); - - static String errorToString(int error); - - /// Cookie jar support - void setCookieJar(CookieJar *cookieJar); - void resetCookieJar(); - void clearAllCookies(); - -protected: - struct RequestArgument { - String key; - String value; - }; - - bool beginInternal(String url, const char *expectedProtocol); - void disconnect(bool preserveClient = false); - void clear(); - int returnError(int error); - bool connect(void); - bool sendHeader(const char *type); - int handleHeaderResponse(); - int writeToStreamDataBlock(Stream *stream, int len); - - /// Cookie jar support - void setCookie(String date, String headerValue); - bool generateCookieString(String *cookieString); - -#ifdef HTTPCLIENT_1_1_COMPATIBLE - TransportTraitsPtr _transportTraits; - std::unique_ptr _tcpDeprecated; -#endif - - NetworkClient *_client = nullptr; - - /// request handling - String _host; - uint16_t _port = 0; - int32_t _connectTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT; - bool _reuse = true; - uint16_t _tcpTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT; - bool _useHTTP10 = false; - bool _secure = false; - - String _uri; - String _protocol; - String _headers; - String _userAgent = "ESP32HTTPClient"; - String _base64Authorization; - String _authorizationType = "Basic"; - String _acceptEncoding = "identity;q=1,chunked;q=0.1,*;q=0"; - - /// Response handling - RequestArgument *_currentHeaders = nullptr; - size_t _headerKeysCount = 0; - - int _returnCode = 0; - int _size = -1; - bool _canReuse = false; - followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS; - uint16_t _redirectLimit = 10; - String _location; - transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY; - - /// Cookie jar support - CookieJar *_cookieJar = nullptr; -}; - -#endif /* HTTPClient_H_ */ diff --git a/lib/NetworkClientSecure/README.md b/lib/NetworkClientSecure/README.md deleted file mode 100644 index 0710d3f..0000000 --- a/lib/NetworkClientSecure/README.md +++ /dev/null @@ -1,133 +0,0 @@ -NetworkClientSecure -================ - -The NetworkClientSecure class implements support for secure connections using TLS (SSL). -It inherits from NetworkClient and thus implements a superset of that class' interface. -There are three ways to establish a secure connection using the NetworkClientSecure class: -using a root certificate authority (CA) cert, using a root CA cert plus a client cert and key, -and using a pre-shared key (PSK). - -Using a root certificate authority cert ---------------------------------------- -This method authenticates the server and negotiates an encrypted connection. -It is the same functionality as implemented in your web browser when you connect to HTTPS sites. - -If you are accessing your own server: -- Generate a root certificate for your own certificate authority -- Generate a cert & private key using your root certificate ("self-signed cert") for your server - -If you are accessing a public server: -- Obtain the cert of the public CA that signed that server's cert -Then: -- In NetworkClientSecure use setCACert (or the appropriate connect method) to set the root cert of your - CA or of the public CA -- When NetworkClientSecure connects to the target server it uses the CA cert to verify the certificate - presented by the server, and then negotiates encryption for the connection - -Please see the NetworkClientSecure example. - -Using a bundle of root certificate authority certificates ---------------------------------------------------------- -This method is similar to the single root certificate verification above, but it uses a standard set of -root certificates from Mozilla to authenticate against, while the previous method only accepts a single -certificate for a given server. This allows the client to connect to all public SSL servers. - -To use this feature in PlatformIO: -1. create a certificate bundle as described in the document below, or obtain a pre-built one you trust: -https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/esp_crt_bundle.html -(gen_crt_bundle.py can be found in the /tools folder) - a. note: the full bundle will take up around 64k of flash space, but has minimal RAM usage, as only - the index of the certificates is kept in RAM -2. Place the bundle under the file name "data/cert/x509_crt_bundle.bin" in your platformio project -3. add "board_build.embed_files = data/cert/x509_crt_bundle.bin" in your platformio.ini -4. add the following global declaration in your project: - extern const uint8_t rootca_crt_bundle_start[] asm("_binary_data_cert_x509_crt_bundle_bin_start"); -5. before initiating the first SSL connection, call - my_client.setCACertBundle(rootca_crt_bundle_start); - -To use this feature in Arduino IDE: -If the Arduino IDE added support for embedding files in the meantime, then follow the instructions above. -If not, you have three choices: -1. convert your project to PlatformIO -2. create a makefile where you can add the idf_component_register() declaration to include the certificate bundle -3. Store the bundle as a SPIFFS file, but then you have to load it into RAM in runtime and waste 64k of precious memory - -Using a root CA cert and client cert/keys ------------------------------------------ -This method authenticates the server and additionally also authenticates -the client to the server, then negotiates an encrypted connection. - -- Follow steps above -- Using your root CA generate cert/key for your client -- Register the keys with the server you will be accessing so the server can authenticate your client -- In NetworkClientSecure use setCACert (or the appropriate connect method) to set the root cert of your - CA or of the public CA, this is used to authenticate the server -- In NetworkClientSecure use setCertificate, and setPrivateKey (or the appropriate connect method) to - set your client's cert & key, this will be used to authenticate your client to the server -- When NetworkClientSecure connects to the target server it uses the CA cert to verify the certificate - presented by the server, it will use the cert/key to authenticate your client to the server, and - it will then negotiate encryption for the connection - -Using Pre-Shared Keys (PSK) ---------------------------- - -TLS supports authentication and encryption using a pre-shared key (i.e. a key that both client and -server know) as an alternative to the public key cryptography commonly used on the web for HTTPS. -PSK is starting to be used for MQTT, e.g. in mosquitto, to simplify the set-up and avoid having to -go through the whole CA, cert, and private key process. - -A pre-shared key is a binary string of up to 32 bytes and is commonly represented in hex form. In -addition to the key, clients can also present an id and typically the server allows a different key -to be associated with each client id. In effect this is very similar to username and password pairs, -except that unlike a password the key is not directly transmitted to the server, thus a connection to a -malicious server does not divulge the password. Plus the server is also authenticated to the client. - -To use PSK: -- Generate a random hex string (generating an MD5 or SHA for some file is one way to do this) -- Come up with a string id for your client and configure your server to accept the id/key pair -- In NetworkClientSecure use setPreSharedKey (or the appropriate connect method) to - set the id/key combo -- When NetworkClientSecure connects to the target server it uses the id/key combo to authenticate the - server (it must prove that it has the key too), authenticate the client and then negotiate - encryption for the connection - -Please see the NetworkClientPSK example. - -Specifying the ALPN Protocol ----------------------------- - -Application-Layer Protocol Negotiation (ALPN) is a Transport Layer Security (TLS) extension that allows -the application layer to negotiate which protocol should be performed over a secure connection in a manner -that avoids additional round trips and which is independent of the application-layer protocols. - -For example, this is used with AWS IoT Custom Authorizers where an MQTT client must set the ALPN protocol to ```mqtt```: - -``` -const char *aws_protos[] = {"mqtt", NULL}; -... -wiFiClient.setAlpnProtocols(aws_protos); -``` - -Examples --------- -#### NetworkClientInsecure -Demonstrates usage of insecure connection using `NetworkClientSecure::setInsecure()` -#### NetworkClientPSK -Wifi secure connection example for ESP32 using a pre-shared key (PSK) -This is useful with MQTT servers instead of using a self-signed cert, tested with mosquitto. -Running on TLS 1.2 using mbedTLS -#### NetworkClientSecure -Wifi secure connection example for ESP32 -Running on TLS 1.2 using mbedTLS -#### NetworkClientSecureEnterprise -This example demonstrates a secure connection to a WiFi network using WPA/WPA2 Enterprise (for example eduroam), -and establishing a secure HTTPS connection with an external server (for example arduino.php5.sk) using the defined anonymous identity, user identity, and password. - -.. note:: - This example is outdated and might not work. For more examples see [https://github.com/martinius96/ESP32-eduroam](https://github.com/martinius96/ESP32-eduroam) - -#### NetworkClientShowPeerCredentials -Example of a establishing a secure connection and then showing the fingerprint of the certificate. -This can be useful in an IoT setting to know for sure that you are connecting to the right server. -Especially in situations where you cannot hardcode a trusted root certificate for long -periods of time (as they tend to get replaced more often than the lifecycle of IoT hardware). diff --git a/lib/NetworkClientSecure/examples/WiFiClientInsecure/WiFiClientInsecure.ino b/lib/NetworkClientSecure/examples/WiFiClientInsecure/WiFiClientInsecure.ino deleted file mode 100644 index 95009c8..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientInsecure/WiFiClientInsecure.ino +++ /dev/null @@ -1,70 +0,0 @@ -#include -#include - -/* This is a very INSECURE approach. - * If for some reason the secure, proper example NetworkClientSecure - * does not work for you; then you may want to check the - * NetworkClientTrustOnFirstUse example first. It is less secure than - * NetworkClientSecure, but a lot better than this totally insecure - * approach shown below. - */ - -const char *ssid = "your-ssid"; // your network SSID (name of wifi network) -const char *password = "your-password"; // your network password - -const char *server = "www.howsmyssl.com"; // Server URL - -NetworkClientSecure client; - -void setup() { - //Initialize serial and wait for port to open: - Serial.begin(115200); - delay(100); - - Serial.print("Attempting to connect to SSID: "); - Serial.println(ssid); - WiFi.begin(ssid, password); - - // attempt to connect to Wifi network: - while (WiFi.status() != WL_CONNECTED) { - Serial.print("."); - // wait 1 second for re-trying - delay(1000); - } - - Serial.print("Connected to "); - Serial.println(ssid); - - Serial.println("\nStarting connection to server..."); - client.setInsecure(); //skip verification - if (!client.connect(server, 443)) { - Serial.println("Connection failed!"); - } else { - Serial.println("Connected to server!"); - // Make a HTTP request: - client.println("GET https://www.howsmyssl.com/a/check HTTP/1.0"); - client.println("Host: www.howsmyssl.com"); - client.println("Connection: close"); - client.println(); - - while (client.connected()) { - String line = client.readStringUntil('\n'); - if (line == "\r") { - Serial.println("headers received"); - break; - } - } - // if there are incoming bytes available - // from the server, read them and print them: - while (client.available()) { - char c = client.read(); - Serial.write(c); - } - - client.stop(); - } -} - -void loop() { - // do nothing -} diff --git a/lib/NetworkClientSecure/examples/WiFiClientInsecure/ci.json b/lib/NetworkClientSecure/examples/WiFiClientInsecure/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientInsecure/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/NetworkClientSecure/examples/WiFiClientPSK/WiFiClientPSK.ino b/lib/NetworkClientSecure/examples/WiFiClientPSK/WiFiClientPSK.ino deleted file mode 100644 index 42bc886..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientPSK/WiFiClientPSK.ino +++ /dev/null @@ -1,86 +0,0 @@ -/* - Wifi secure connection example for ESP32 using a pre-shared key (PSK) - This is useful with MQTT servers instead of using a self-signed cert, tested with mosquitto. - Running on TLS 1.2 using mbedTLS - - To test run a test server using: openssl s_server -accept 8443 -psk 1a2b3c4d -nocert - It will show the http request made, but there's no easy way to send a reply back... - - 2017 - Evandro Copercini - Apache 2.0 License. - 2018 - Adapted for PSK by Thorsten von Eicken -*/ - -#include -#include - -#if 0 -const char* ssid = "your-ssid"; // your network SSID (name of wifi network) -const char* password = "your-password"; // your network password -#else -const char *ssid = "test"; // your network SSID (name of wifi network) -const char *password = "securetest"; // your network password -#endif - -//const char* server = "server.local"; // Server hostname -const IPAddress server = IPAddress(192, 168, 0, 14); // Server IP address -const int port = 8443; // server's port (8883 for MQTT) - -const char *pskIdent = "Client_identity"; // PSK identity (sometimes called key hint) -const char *psKey = "1a2b3c4d"; // PSK Key (must be hex string without 0x) - -NetworkClientSecure client; - -void setup() { - //Initialize serial and wait for port to open: - Serial.begin(115200); - delay(100); - - Serial.print("Attempting to connect to SSID: "); - Serial.println(ssid); - WiFi.begin(ssid, password); - - // attempt to connect to Wifi network: - while (WiFi.status() != WL_CONNECTED) { - Serial.print("."); - // wait 1 second for re-trying - delay(1000); - } - - Serial.print("Connected to "); - Serial.println(ssid); - - client.setPreSharedKey(pskIdent, psKey); - - Serial.println("\nStarting connection to server..."); - if (!client.connect(server, port)) { - Serial.println("Connection failed!"); - } else { - Serial.println("Connected to server!"); - // Make a HTTP request: - client.println("GET /a/check HTTP/1.0"); - client.print("Host: "); - client.println(server); - client.println("Connection: close"); - client.println(); - - while (client.connected()) { - String line = client.readStringUntil('\n'); - if (line == "\r") { - Serial.println("headers received"); - break; - } - } - // if there are incoming bytes available - // from the server, read them and print them: - while (client.available()) { - char c = client.read(); - Serial.write(c); - } - - client.stop(); - } -} - -void loop() { - // do nothing -} diff --git a/lib/NetworkClientSecure/examples/WiFiClientPSK/ci.json b/lib/NetworkClientSecure/examples/WiFiClientPSK/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientPSK/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/NetworkClientSecure/examples/WiFiClientSecure/WiFiClientSecure.ino b/lib/NetworkClientSecure/examples/WiFiClientSecure/WiFiClientSecure.ino deleted file mode 100644 index 0f95826..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientSecure/WiFiClientSecure.ino +++ /dev/null @@ -1,112 +0,0 @@ -/* - Wifi secure connection example for ESP32 - Running on TLS 1.2 using mbedTLS - Supporting the following ciphersuites: - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_DHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_AES_256_CCM","TLS_DHE_RSA_WITH_AES_256_CCM","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384","TLS_DHE_RSA_WITH_AES_256_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA","TLS_DHE_RSA_WITH_AES_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8","TLS_DHE_RSA_WITH_AES_256_CCM_8","TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256","TLS_DHE_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_CCM","TLS_DHE_RSA_WITH_AES_128_CCM","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","TLS_DHE_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA","TLS_DHE_RSA_WITH_AES_128_CBC_SHA","TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8","TLS_DHE_RSA_WITH_AES_128_CCM_8","TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA","TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA","TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA","TLS_DHE_PSK_WITH_AES_256_GCM_SHA384","TLS_DHE_PSK_WITH_AES_256_CCM","TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384","TLS_DHE_PSK_WITH_AES_256_CBC_SHA384","TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA","TLS_DHE_PSK_WITH_AES_256_CBC_SHA","TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_PSK_DHE_WITH_AES_256_CCM_8","TLS_DHE_PSK_WITH_AES_128_GCM_SHA256","TLS_DHE_PSK_WITH_AES_128_CCM","TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256","TLS_DHE_PSK_WITH_AES_128_CBC_SHA256","TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA","TLS_DHE_PSK_WITH_AES_128_CBC_SHA","TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_PSK_DHE_WITH_AES_128_CCM_8","TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA","TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA","TLS_RSA_WITH_AES_256_GCM_SHA384","TLS_RSA_WITH_AES_256_CCM","TLS_RSA_WITH_AES_256_CBC_SHA256","TLS_RSA_WITH_AES_256_CBC_SHA","TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_RSA_WITH_AES_256_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384","TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA","TLS_RSA_WITH_AES_256_CCM_8","TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256","TLS_RSA_WITH_CAMELLIA_256_CBC_SHA","TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384","TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384","TLS_RSA_WITH_AES_128_GCM_SHA256","TLS_RSA_WITH_AES_128_CCM","TLS_RSA_WITH_AES_128_CBC_SHA256","TLS_RSA_WITH_AES_128_CBC_SHA","TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256","TLS_ECDH_RSA_WITH_AES_128_CBC_SHA","TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256","TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA","TLS_RSA_WITH_AES_128_CCM_8","TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_WITH_CAMELLIA_128_CBC_SHA","TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256","TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA","TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA","TLS_RSA_PSK_WITH_AES_256_GCM_SHA384","TLS_RSA_PSK_WITH_AES_256_CBC_SHA384","TLS_RSA_PSK_WITH_AES_256_CBC_SHA","TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_RSA_PSK_WITH_AES_128_GCM_SHA256","TLS_RSA_PSK_WITH_AES_128_CBC_SHA256","TLS_RSA_PSK_WITH_AES_128_CBC_SHA","TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA","TLS_PSK_WITH_AES_256_GCM_SHA384","TLS_PSK_WITH_AES_256_CCM","TLS_PSK_WITH_AES_256_CBC_SHA384","TLS_PSK_WITH_AES_256_CBC_SHA","TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384","TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384","TLS_PSK_WITH_AES_256_CCM_8","TLS_PSK_WITH_AES_128_GCM_SHA256","TLS_PSK_WITH_AES_128_CCM","TLS_PSK_WITH_AES_128_CBC_SHA256","TLS_PSK_WITH_AES_128_CBC_SHA","TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256","TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256","TLS_PSK_WITH_AES_128_CCM_8","TLS_PSK_WITH_3DES_EDE_CBC_SHA","TLS_EMPTY_RENEGOTIATION_INFO_SCSV"] - 2017 - Evandro Copercini - Apache 2.0 License. -*/ - -#include -#include - -const char *ssid = "your-ssid"; // your network SSID (name of wifi network) -const char *password = "your-password"; // your network password - -const char *server = "www.howsmyssl.com"; // Server URL - -// www.howsmyssl.com root certificate authority, to verify the server -// change it to your server root CA -// SHA1 fingerprint is broken now! - -const char *test_root_ca = R"literal( ------BEGIN CERTIFICATE----- -MIIFBTCCAu2gAwIBAgIQS6hSk/eaL6JzBkuoBI110DANBgkqhkiG9w0BAQsFADBP -MQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFy -Y2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMTAeFw0yNDAzMTMwMDAwMDBa -Fw0yNzAzMTIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBF -bmNyeXB0MQwwCgYDVQQDEwNSMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQDPV+XmxFQS7bRH/sknWHZGUCiMHT6I3wWd1bUYKb3dtVq/+vbOo76vACFL -YlpaPAEvxVgD9on/jhFD68G14BQHlo9vH9fnuoE5CXVlt8KvGFs3Jijno/QHK20a -/6tYvJWuQP/py1fEtVt/eA0YYbwX51TGu0mRzW4Y0YCF7qZlNrx06rxQTOr8IfM4 -FpOUurDTazgGzRYSespSdcitdrLCnF2YRVxvYXvGLe48E1KGAdlX5jgc3421H5KR -mudKHMxFqHJV8LDmowfs/acbZp4/SItxhHFYyTr6717yW0QrPHTnj7JHwQdqzZq3 -DZb3EoEmUVQK7GH29/Xi8orIlQ2NAgMBAAGjgfgwgfUwDgYDVR0PAQH/BAQDAgGG -MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATASBgNVHRMBAf8ECDAGAQH/ -AgEAMB0GA1UdDgQWBBS7vMNHpeS8qcbDpHIMEI2iNeHI6DAfBgNVHSMEGDAWgBR5 -tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAKG -Fmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0gBAwwCjAIBgZngQwBAgEwJwYD -VR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVuY3Iub3JnLzANBgkqhkiG9w0B -AQsFAAOCAgEAkrHnQTfreZ2B5s3iJeE6IOmQRJWjgVzPw139vaBw1bGWKCIL0vIo -zwzn1OZDjCQiHcFCktEJr59L9MhwTyAWsVrdAfYf+B9haxQnsHKNY67u4s5Lzzfd -u6PUzeetUK29v+PsPmI2cJkxp+iN3epi4hKu9ZzUPSwMqtCceb7qPVxEbpYxY1p9 -1n5PJKBLBX9eb9LU6l8zSxPWV7bK3lG4XaMJgnT9x3ies7msFtpKK5bDtotij/l0 -GaKeA97pb5uwD9KgWvaFXMIEt8jVTjLEvwRdvCn294GPDF08U8lAkIv7tghluaQh -1QnlE4SEN4LOECj8dsIGJXpGUk3aU3KkJz9icKy+aUgA+2cP21uh6NcDIS3XyfaZ -QjmDQ993ChII8SXWupQZVBiIpcWO4RqZk3lr7Bz5MUCwzDIA359e57SSq5CCkY0N -4B6Vulk7LktfwrdGNVI5BsC9qqxSwSKgRJeZ9wygIaehbHFHFhcBaMDKpiZlBHyz -rsnnlFXCb5s8HKn5LsUgGvB24L7sGNZP2CX7dhHov+YhD+jozLW2p9W4959Bz2Ei -RmqDtmiXLnzqTpXbI+suyCsohKRg6Un0RC47+cpiVwHiXZAW+cn8eiNIjqbVgXLx -KPpdzvvtTnOPlC7SQZSYmdunr3Bf9b77AiC/ZidstK36dRILKz7OA54= ------END CERTIFICATE----- -)literal"; -// You can use x.509 client certificates if you want -//const char* test_client_key = ""; //to verify the client -//const char* test_client_cert = ""; //to verify the client - -NetworkClientSecure client; - -void setup() { - //Initialize serial and wait for port to open: - Serial.begin(115200); - delay(100); - - Serial.print("Attempting to connect to SSID: "); - Serial.println(ssid); - WiFi.begin(ssid, password); - - // attempt to connect to Wifi network: - while (WiFi.status() != WL_CONNECTED) { - Serial.print("."); - // wait 1 second for re-trying - delay(1000); - } - - Serial.print("Connected to "); - Serial.println(ssid); - - client.setCACert(test_root_ca); - //client.setCertificate(test_client_cert); // for client verification - //client.setPrivateKey(test_client_key); // for client verification - - Serial.println("\nStarting connection to server..."); - if (!client.connect(server, 443)) { - Serial.println("Connection failed!"); - } else { - Serial.println("Connected to server!"); - // Make a HTTP request: - client.println("GET https://www.howsmyssl.com/a/check HTTP/1.0"); - client.println("Host: www.howsmyssl.com"); - client.println("Connection: close"); - client.println(); - - while (client.connected()) { - String line = client.readStringUntil('\n'); - if (line == "\r") { - Serial.println("headers received"); - break; - } - } - // if there are incoming bytes available - // from the server, read them and print them: - while (client.available()) { - char c = client.read(); - Serial.write(c); - } - - client.stop(); - } -} - -void loop() { - // do nothing -} diff --git a/lib/NetworkClientSecure/examples/WiFiClientSecure/ci.json b/lib/NetworkClientSecure/examples/WiFiClientSecure/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientSecure/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/NetworkClientSecure/examples/WiFiClientSecureEnterprise/WiFiClientSecureEnterprise.ino b/lib/NetworkClientSecure/examples/WiFiClientSecureEnterprise/WiFiClientSecureEnterprise.ino deleted file mode 100644 index a7149e0..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientSecureEnterprise/WiFiClientSecureEnterprise.ino +++ /dev/null @@ -1,132 +0,0 @@ -/*|-----------------------------------------------------------|*/ -/*|WORKING EXAMPLE FOR HTTPS CONNECTION |*/ -/*|Author: Bc. Martin Chlebovec |*/ -/*|Technical University of Košice |*/ -/*|TESTED BOARDS: Devkit v1 DOIT, Devkitc v4 |*/ -/*|CORE: 0.9x, 1.0.0, 1.0.1 tested, working (newer not tested)|*/ -/*|Supported methods: PEAP + MsCHAPv2, EAP-TTLS + MsCHAPv2 |*/ -/*|-----------------------------------------------------------|*/ - -// This example demonstrates a secure connection to a WiFi network using WPA/WPA2 Enterprise (for example eduroam), -// and establishing a secure HTTPS connection with an external server (for example arduino.php5.sk) using the defined anonymous identity, user identity, and password. - -// Note: this example is outdated and may not work! -// For more examples see https://github.com/martinius96/ESP32-eduroam - -#include -#include -#if __has_include("esp_eap_client.h") -#include "esp_eap_client.h" -#else -#include "esp_wpa2.h" -#endif -#include -#define EAP_ANONYMOUS_IDENTITY "anonymous@example.com" //anonymous identity -#define EAP_IDENTITY "id@example.com" //user identity -#define EAP_PASSWORD "password" //eduroam user password -const char *ssid = "eduroam"; // eduroam SSID -const char *host = "arduino.php5.sk"; //external server domain for HTTPS connection -int counter = 0; -const char *test_root_ca = "-----BEGIN CERTIFICATE-----\n" - "MIIEsTCCA5mgAwIBAgIQCKWiRs1LXIyD1wK0u6tTSTANBgkqhkiG9w0BAQsFADBh\n" - "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" - "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" - "QTAeFw0xNzExMDYxMjIzMzNaFw0yNzExMDYxMjIzMzNaMF4xCzAJBgNVBAYTAlVT\n" - "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" - "b20xHTAbBgNVBAMTFFJhcGlkU1NMIFJTQSBDQSAyMDE4MIIBIjANBgkqhkiG9w0B\n" - "AQEFAAOCAQ8AMIIBCgKCAQEA5S2oihEo9nnpezoziDtx4WWLLCll/e0t1EYemE5n\n" - "+MgP5viaHLy+VpHP+ndX5D18INIuuAV8wFq26KF5U0WNIZiQp6mLtIWjUeWDPA28\n" - "OeyhTlj9TLk2beytbtFU6ypbpWUltmvY5V8ngspC7nFRNCjpfnDED2kRyJzO8yoK\n" - "MFz4J4JE8N7NA1uJwUEFMUvHLs0scLoPZkKcewIRm1RV2AxmFQxJkdf7YN9Pckki\n" - "f2Xgm3b48BZn0zf0qXsSeGu84ua9gwzjzI7tbTBjayTpT+/XpWuBVv6fvarI6bik\n" - "KB859OSGQuw73XXgeuFwEPHTIRoUtkzu3/EQ+LtwznkkdQIDAQABo4IBZjCCAWIw\n" - "HQYDVR0OBBYEFFPKF1n8a8ADIS8aruSqqByCVtp1MB8GA1UdIwQYMBaAFAPeUDVW\n" - "0Uy7ZvCj4hsbw5eyPdFVMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEF\n" - "BQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADA0BggrBgEFBQcBAQQo\n" - "MCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBCBgNVHR8E\n" - "OzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9i\n" - "YWxSb290Q0EuY3JsMGMGA1UdIARcMFowNwYJYIZIAYb9bAECMCowKAYIKwYBBQUH\n" - "AgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCwYJYIZIAYb9bAEBMAgG\n" - "BmeBDAECATAIBgZngQwBAgIwDQYJKoZIhvcNAQELBQADggEBAH4jx/LKNW5ZklFc\n" - "YWs8Ejbm0nyzKeZC2KOVYR7P8gevKyslWm4Xo4BSzKr235FsJ4aFt6yAiv1eY0tZ\n" - "/ZN18bOGSGStoEc/JE4ocIzr8P5Mg11kRYHbmgYnr1Rxeki5mSeb39DGxTpJD4kG\n" - "hs5lXNoo4conUiiJwKaqH7vh2baryd8pMISag83JUqyVGc2tWPpO0329/CWq2kry\n" - "qv66OSMjwulUz0dXf4OHQasR7CNfIr+4KScc6ABlQ5RDF86PGeE6kdwSQkFiB/cQ\n" - "ysNyq0jEDQTkfa2pjmuWtMCNbBnhFXBYejfubIhaUbEv2FOQB3dCav+FPg5eEveX\n" - "TVyMnGo=\n" - "-----END CERTIFICATE-----\n"; -// You can use x.509 client certificates if you want -//const char* test_client_key = ""; //to verify the client -//const char* test_client_cert = ""; //to verify the client -NetworkClientSecure client; -void setup() { - Serial.begin(115200); - delay(10); - Serial.println(); - Serial.print("Connecting to network: "); - Serial.println(ssid); - WiFi.disconnect(true); //disconnect form wifi to set new wifi connection - WiFi.mode(WIFI_STA); //init wifi mode -#if __has_include("esp_eap_client.h") - esp_eap_client_set_identity((uint8_t *)EAP_ANONYMOUS_IDENTITY, strlen(EAP_ANONYMOUS_IDENTITY)); //provide identity - esp_eap_client_set_username((uint8_t *)EAP_IDENTITY, strlen(EAP_IDENTITY)); //provide username - esp_eap_client_set_password((uint8_t *)EAP_PASSWORD, strlen(EAP_PASSWORD)); //provide password - esp_wifi_sta_enterprise_enable(); -#else - esp_wifi_sta_wpa2_ent_set_identity((uint8_t *)EAP_ANONYMOUS_IDENTITY, strlen(EAP_ANONYMOUS_IDENTITY)); //provide identity - esp_wifi_sta_wpa2_ent_set_username((uint8_t *)EAP_IDENTITY, strlen(EAP_IDENTITY)); //provide username - esp_wifi_sta_wpa2_ent_set_password((uint8_t *)EAP_PASSWORD, strlen(EAP_PASSWORD)); //provide password - esp_wifi_sta_wpa2_ent_enable(); -#endif - WiFi.begin(ssid); //connect to wifi - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - counter++; - if (counter >= 60) { //after 30 seconds timeout - reset board (on unsuccessful connection) - ESP.restart(); - } - } - client.setCACert(test_root_ca); - //client.setCertificate(test_client_cert); // for client verification - certificate - //client.setPrivateKey(test_client_key); // for client verification - private key - Serial.println(""); - Serial.println("WiFi connected"); - Serial.println("IP address set: "); - Serial.println(WiFi.localIP()); //print LAN IP -} -void loop() { - if (WiFi.status() == WL_CONNECTED) { //if we are connected to eduroam network - counter = 0; //reset counter - Serial.println("Wifi is still connected with IP: "); - Serial.println(WiFi.localIP()); //inform user about his IP address - } else if (WiFi.status() != WL_CONNECTED) { //if we lost connection, retry - WiFi.begin(ssid); - } - while (WiFi.status() != WL_CONNECTED) { //during lost connection, print dots - delay(500); - Serial.print("."); - counter++; - if (counter >= 60) { //30 seconds timeout - reset board - ESP.restart(); - } - } - Serial.print("Connecting to website: "); - Serial.println(host); - if (client.connect(host, 443)) { - String url = "/rele/rele1.txt"; - client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "User-Agent: ESP32\r\n" + "Connection: close\r\n\r\n"); - while (client.connected()) { - String header = client.readStringUntil('\n'); - Serial.println(header); - if (header == "\r") { - break; - } - } - String line = client.readStringUntil('\n'); - Serial.println(line); - } else { - Serial.println("Connection unsuccessful"); - } - delay(5000); -} diff --git a/lib/NetworkClientSecure/examples/WiFiClientSecureEnterprise/ci.json b/lib/NetworkClientSecure/examples/WiFiClientSecureEnterprise/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientSecureEnterprise/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/NetworkClientSecure/examples/WiFiClientSecureProtocolUpgrade/WiFiClientSecureProtocolUpgrade.ino b/lib/NetworkClientSecure/examples/WiFiClientSecureProtocolUpgrade/WiFiClientSecureProtocolUpgrade.ino deleted file mode 100644 index a6f76cb..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientSecureProtocolUpgrade/WiFiClientSecureProtocolUpgrade.ino +++ /dev/null @@ -1,190 +0,0 @@ -/* STARTSSL example - - Inline upgrading from a clear-text connection to an SSL/TLS connection. - - Some protocols such as SMTP, XMPP, Mysql, Postgresql and others allow, or require, - that you start the connection without encryption; and then send a command to switch - over to encryption. - - E.g. a typical SMTP submission would entail a dialog such as this: - - 1. client connects to server in the clear - 2. server says hello - 3. client sents a EHLO - 4. server tells the client that it supports SSL/TLS - 5. client sends a 'STARTTLS' to make use of this faciltiy - 6. client/server negiotiate a SSL or TLS connection. - 7. client sends another EHLO - 8. server now tells the client what (else) is supported; such as additional authentication options. - ... conversation continues encrypted. - - This can be enabled in NetworkClientSecure by telling it to start in plaintext: - - client.setPlainStart(); - - and client is than a plain, TCP, connection (just as NetworkClient would be); until the client calls - the method: - - client.startTLS(); // returns zero on error; non zero on success. - - After which things switch to TLS/SSL. -*/ - -#include -#include - -#ifndef WIFI_NETWORK -#define WIFI_NETWORK "YOUR Wifi SSID" -#endif - -#ifndef WIFI_PASSWD -#define WIFI_PASSWD "your-secret-password" -#endif - -#ifndef SMTP_HOST -#define SMTP_HOST "smtp.gmail.com" -#endif - -#ifndef SMTP_PORT -#define SMTP_PORT (587) // Standard (plaintext) submission port -#endif - -const char *ssid = WIFI_NETWORK; // your network SSID (name of wifi network) -const char *password = WIFI_PASSWD; // your network password -const char *server = SMTP_HOST; // Server URL -const int submission_port = SMTP_PORT; // submission port. - -NetworkClientSecure client; - -static bool readAllSMTPLines(); - -void setup() { - int ret; - //Initialize serial and wait for port to open: - Serial.begin(115200); - delay(100); - - Serial.print("Attempting to connect to SSID: "); - Serial.print(ssid); - WiFi.begin(ssid, password); - - // attempt to connect to Wifi network: - while (WiFi.status() != WL_CONNECTED) { - Serial.print("."); - // wait 1 second for re-trying - delay(1000); - } - - Serial.print("Connected to "); - Serial.println(ssid); - - Serial.printf("\nStarting connection to server: %s:%d\n", server, submission_port); - - // skip verification for this demo. In production one should at the very least - // enable TOFU; or ideally hardcode a (CA) certificate that is trusted. - client.setInsecure(); - - // Enable a plain-test start. - client.setPlainStart(); - - if (!client.connect(server, SMTP_PORT)) { - Serial.println("Connection failed!"); - return; - }; - - Serial.println("Connected to server (in the clear, in plaintest)"); - - if (!readAllSMTPLines()) { - goto err; - } - - Serial.println("Sending : EHLO\t\tin the clear"); - client.print("EHLO there\r\n"); - - if (!readAllSMTPLines()) { - goto err; - } - - Serial.println("Sending : STARTTLS\t\tin the clear"); - client.print("STARTTLS\r\n"); - - if (!readAllSMTPLines()) { - goto err; - } - - Serial.println("Upgrading connection to TLS"); - if ((ret = client.startTLS()) <= 0) { - Serial.printf("Upgrade connection failed: err %d\n", ret); - goto err; - } - - Serial.println("Sending : EHLO again\t\tover the now encrypted connection"); - client.print("EHLO again\r\n"); - - if (!readAllSMTPLines()) { - goto err; - } - - // normally, as this point - we'd be authenticating and then be submitting - // an email. This has been left out of this example. - - Serial.println("Sending : QUIT\t\t\tover the now encrypted connection"); - client.print("QUIT\r\n"); - - if (!readAllSMTPLines()) { - goto err; - } - - Serial.println("Completed OK\n"); -err: - Serial.println("Closing connection"); - client.stop(); -} - -// SMTP command repsponse start with three digits and a space; -// or, for continuation, with three digits and a '-'. -static bool readAllSMTPLines() { - String s = ""; - int i; - - // blocking read; we cannot rely on a timeout - // of a NetworkClientSecure read; as it is non - // blocking. - const unsigned long timeout = 15 * 1000; - unsigned long start = millis(); // the timeout is for the entire CMD block response; not per character/line. - while (1) { - while ((i = client.available()) == 0 && millis() - start < timeout) { - /* .. wait */ - }; - if (i == 0) { - Serial.println("Timeout reading SMTP response"); - return false; - }; - if (i < 0) { - break; - } - - i = client.read(); - if (i < 0) { - break; - } - - if (i > 31 && i < 128) { - s += (char)i; - } - if (i == 0x0A) { - Serial.print("Receiving: "); - Serial.println(s); - if (s.charAt(3) == ' ') { - return true; - } - s = ""; - } - } - Serial.printf("Error reading SMTP command response line: %d\n", i); - return false; -} - -void loop() { - // do nothing -} diff --git a/lib/NetworkClientSecure/examples/WiFiClientSecureProtocolUpgrade/ci.json b/lib/NetworkClientSecure/examples/WiFiClientSecureProtocolUpgrade/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientSecureProtocolUpgrade/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/NetworkClientSecure/examples/WiFiClientShowPeerCredentials/WiFiClientShowPeerCredentials.ino b/lib/NetworkClientSecure/examples/WiFiClientShowPeerCredentials/WiFiClientShowPeerCredentials.ino deleted file mode 100644 index be12050..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientShowPeerCredentials/WiFiClientShowPeerCredentials.ino +++ /dev/null @@ -1,98 +0,0 @@ -// NetworkClientShowPeerCredentials -// -// Example of a establishing a secure connection and then -// showing the fingerprint of the certificate. This can -// be useful in an IoT setting to know for sure that you -// are connecting to the right server. Especially in -// situations where you cannot hardcode a trusted root -// certificate for long periods of time (as they tend to -// get replaced more often than the lifecycle of IoT -// hardware). -// - -#include -#include -#include - -#ifndef WIFI_NETWORK -#define WIFI_NETWORK "MyWifiNetwork" -#endif - -#ifndef WIFI_PASSWD -#define WIFI_PASSWD "MySecretWifiPassword" -#endif - -#define URL "https://arduino.cc" - -void demo() { - NetworkClientSecure *client = new NetworkClientSecure; - client->setInsecure(); // - - HTTPClient https; - if (!https.begin(*client, URL)) { - Serial.println("HTTPS setup failed"); - return; - }; - - https.setTimeout(5000); - - int httpCode = https.GET(); - if (httpCode != 200) { - Serial.print("Connect failed: "); - Serial.println(https.errorToString(httpCode)); - return; - } - - const mbedtls_x509_crt *peer = client->getPeerCertificate(); - - // Show general output / certificate information - // - char buf[1024]; - int l = mbedtls_x509_crt_info(buf, sizeof(buf), "", peer); - if (l <= 0) { - Serial.println("Peer conversion to printable buffer failed"); - return; - }; - Serial.println(); - Serial.println(buf); - - uint8_t fingerprint_remote[32]; - if (!client->getFingerprintSHA256(fingerprint_remote)) { - Serial.println("Failed to get the fingerprint"); - return; - } - // Fingerprint late 2021 - Serial.println("Expecting Fingerprint (SHA256): 70 CF A4 B7 5D 09 E9 2A 52 A8 B6 85 B5 0B D6 BE 83 47 83 5B 3A 4D 3C 3E 32 30 EC 1D 61 98 D7 0F"); - Serial.print(" Received Fingerprint (SHA256): "); - - for (int i = 0; i < 32; i++) { - Serial.print(fingerprint_remote[i], HEX); - Serial.print(" "); - }; - Serial.println(""); -}; - -void setup() { - Serial.begin(115200); - Serial.println("Started " __FILE__ " build " __DATE__ " " __TIME__); - - WiFi.mode(WIFI_STA); - WiFi.begin(WIFI_NETWORK, WIFI_PASSWD); - - while (WiFi.waitForConnectResult() != WL_CONNECTED) { - Serial.println("Wifi fail - rebooting"); - delay(5000); - ESP.restart(); - } -} - -void loop() { - bool already_tried = false; - if ((millis() < 1000) || already_tried) { - return; - } - already_tried = true; - - // Run the test just once. - demo(); -} diff --git a/lib/NetworkClientSecure/examples/WiFiClientShowPeerCredentials/ci.json b/lib/NetworkClientSecure/examples/WiFiClientShowPeerCredentials/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientShowPeerCredentials/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/NetworkClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino b/lib/NetworkClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino deleted file mode 100644 index 5b68381..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientTrustOnFirstUse/WiFiClientTrustOnFirstUse.ino +++ /dev/null @@ -1,270 +0,0 @@ -/* For any secure connection - it is (at least) essential for the - the client to verify that it is talking with the server it - thinks it is talking to. And not some (invisible) man in the middle. - - See https://en.wikipedia.org/wiki/Man-in-the-middle_attack, - https://www.ai.rug.nl/mas/finishedprojects/2011/TLS/hermsencomputerservices.nl/mas/mitm.html or - https://medium.com/@munteanu210/ssl-certificates-vs-man-in-the-middle-attacks-3fb7846fa5db - for some background on this. - - Unfortunately this means that one needs to hardcode a server - public key, certificate or some cryptographically strong hash - thereoff into the code, to verify that you are indeed talking to - the right server. This is sometimes somewhat impractical. Especially - if you do not know the server in advance; or if your code needs to be - stable ovr very long times - during which the server may change. - - However completely dispensing with any checks (See the WifiClientInSecure - example) is also not a good idea either. - - This example gives you some middle ground; "Trust on First Use" -- - TOFU - see https://developer.mozilla.org/en-US/docs/Glossary/TOFU or - https://en.wikipedia.org/wiki/Trust_on_first_use). - - In this scheme; we start the very first time without any security checks - but once we have our first connection; we store the public crytpographic - details (or a proxy, such as a sha256 of this). And then we use this for - any subsequent connections. - - The assumption here is that we do our very first connection in a somewhat - trusted network environment; where the chance of a man in the middle is - very low; or one where the person doing the first run can check the - details manually. - - So this is not quite as good as building a CA certificate into your - code (as per the WifiClientSecure example). But not as bad as something - with no trust management at all. - - To make it possible for the enduser to 'reset' this trust; the - startup sequence checks if a certain GPIO is low (assumed to be wired - to some physical button or jumper on the PCB). And we only allow - the TOFU to be configured when this pin is LOW. -*/ -#ifndef WIFI_NETWORK -#define WIFI_NETWORK "Your Wifi SSID" -#endif - -#ifndef WIFI_PASSWD -#define WIFI_PASSWD "your-secret-wifi-password" -#endif - -const char *ssid = WIFI_NETWORK; // your network SSID (name of wifi network) -const char *password = WIFI_PASSWD; // your network password -const char *server = "www.howsmyssl.com"; // Server to test with. - -const int TOFU_RESET_BUTTON = 35; /* Trust reset button wired between GPIO 35 and GND (pulldown) */ - -#include -#include -#include - -/* Set aside some persistent memory (i.e. memory that is preserved on reboots and - power cycling; and will generally survive software updates as well. -*/ -EEPROMClass TOFU("tofu0"); - -// Utility function; checks if a given buffer is entirely -// with with 0 bytes over its full length. Returns 0 on -// success; a non zero value on fail. -// -static int memcmpzero(unsigned char *ptr, size_t len) { - while (len--) { - if (0xff != *ptr++) { - return -1; - } - } - return 0; -}; - -static void printSHA256(unsigned char *ptr) { - for (int i = 0; i < 32; i++) { - Serial.printf("%s%02x", i ? ":" : "", ptr[i]); - } - Serial.println(""); -}; - -NetworkClientSecure client; - -bool get_tofu(); -bool doTOFU_Protected_Connection(uint8_t *fingerprint_tofu); - -void setup() { - bool tofu_reset = false; - //Initialize serial and wait for port to open: - Serial.begin(115200); - delay(100); - - if (!TOFU.begin(32)) { - Serial.println("Could not initialsize the EEPROM"); - return; - } - uint8_t fingerprint_tofu[32]; - - // reset the trust if the tofu reset button is pressed. - // - pinMode(TOFU_RESET_BUTTON, INPUT_PULLUP); - if (digitalRead(TOFU_RESET_BUTTON) == LOW) { - Serial.println("The TOFU reset button is pressed."); - tofu_reset = true; - } - /* if the button is not pressed; see if we can get the TOFU - fingerprint from the EEPROM. - */ - else if (32 != TOFU.readBytes(0, fingerprint_tofu, 32)) { - Serial.println("Failed to get the fingerprint from memory."); - tofu_reset = true; - } - /* And check that the EEPROM value is not all 0's; in which - case we also need to do a TOFU. - */ - else if (!memcmpzero(fingerprint_tofu, 32)) { - Serial.println("TOFU fingerprint in memory all zero."); - tofu_reset = true; - }; - if (!tofu_reset) { - Serial.print("TOFU pegged to fingerprint: SHA256="); - printSHA256(fingerprint_tofu); - Serial.print("Note: You can check this fingerprint by going to the URL\n" - " and then click on the lock icon.\n"); - }; - - // attempt to connect to Wifi network: - Serial.print("Attempting to connect to SSID: "); - Serial.println(ssid); - WiFi.begin(ssid, password); - while (WiFi.status() != WL_CONNECTED) { - Serial.print("."); - // wait 1 second for re-trying - delay(1000); - } - - Serial.print("Connected to "); - Serial.println(ssid); - - if (tofu_reset) { - Serial.println("Resetting trust fingerprint."); - if (!get_tofu()) { - Serial.println("Trust reset failed. Giving up"); - return; - } - Serial.println("(New) Trust of First used configured. Rebooting in 3 seconds"); - delay(3 * 1000); - ESP.restart(); - }; - - Serial.println("Trying to connect to a server; using TOFU details from the eeprom"); - - if (doTOFU_Protected_Connection(fingerprint_tofu)) { - Serial.println("ALL OK"); - } -} - -bool get_tofu() { - Serial.println("\nStarting our insecure connection to server..."); - client.setInsecure(); //skip verification - - if (!client.connect(server, 443)) { - Serial.println("Connection failed!"); - client.stop(); - return false; - }; - - Serial.println("Connected to server. Extracting trust data."); - - // Now extract the data of the certificate and show it to - // the user over the serial connection for optional - // verification. - const mbedtls_x509_crt *peer = client.getPeerCertificate(); - char buf[1024]; - int l = mbedtls_x509_crt_info(buf, sizeof(buf), "", peer); - if (l <= 0) { - Serial.println("Peer conversion to printable buffer failed"); - client.stop(); - return false; - }; - Serial.println(); - Serial.println(buf); - - // Extract the fingerprint - and store this in our EEPROM - // to be used for future validation. - - uint8_t fingerprint_remote[32]; - if (!client.getFingerprintSHA256(fingerprint_remote)) { - Serial.println("Failed to get the fingerprint"); - client.stop(); - return false; - } - if ((32 != TOFU.writeBytes(0, fingerprint_remote, 32)) || (!TOFU.commit())) { - Serial.println("Could not write the fingerprint to the EEPROM"); - client.stop(); - return false; - }; - TOFU.end(); - client.stop(); - - Serial.print("TOFU pegged to fingerprint: SHA256="); - printSHA256(fingerprint_remote); - - return true; -}; - -bool doTOFU_Protected_Connection(uint8_t *fingerprint_tofu) { - - // As we're not using a (CA) certificate to check the - // connection; but the hash of the peer - we need to initially - // allow the connection to be set up without the CA check. - client.setInsecure(); //skip verification - - if (!client.connect(server, 443)) { - Serial.println("Connection failed!"); - client.stop(); - return false; - }; - - // Now that we're connected - we can check that we have - // end to end trust - by comparing the fingerprint we (now) - // see (of the server certificate) to the one we have stored - // in our EEPROM as part of an earlier trust-on-first use. - uint8_t fingerprint_remote[32]; - if (!client.getFingerprintSHA256(fingerprint_remote)) { - Serial.println("Failed to get the fingerprint of the server"); - client.stop(); - return false; - } - if (memcmp(fingerprint_remote, fingerprint_tofu, 32)) { - Serial.println("TOFU fingerprint not the same as the one from the server."); - Serial.print("TOFU : SHA256="); - printSHA256(fingerprint_tofu); - Serial.print("Remote: SHA256="); - printSHA256(fingerprint_remote); - Serial.println(" : NOT identical -- Aborting!"); - client.stop(); - return false; - }; - - Serial.println("All well - you are talking to the same server as\n" - "when you set up TOFU. So we can now do a GET.\n\n"); - - client.println("GET /a/check HTTP/1.0"); - client.print("Host: "); - client.println(server); - client.println("Connection: close"); - client.println(); - - bool inhdr = true; - while (client.connected()) { - String line = client.readStringUntil('\n'); - Serial.println(line); - if (inhdr && line == "\r") { - inhdr = false; - Serial.println("-- headers received. Payload follows\n\n"); - } - } - Serial.println("\n\n-- Payload ended."); - client.stop(); - return true; -} - -void loop() {} diff --git a/lib/NetworkClientSecure/examples/WiFiClientTrustOnFirstUse/ci.json b/lib/NetworkClientSecure/examples/WiFiClientTrustOnFirstUse/ci.json deleted file mode 100644 index d8b3664..0000000 --- a/lib/NetworkClientSecure/examples/WiFiClientTrustOnFirstUse/ci.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "targets": { - "esp32h2": false - } -} diff --git a/lib/NetworkClientSecure/keywords.txt b/lib/NetworkClientSecure/keywords.txt deleted file mode 100644 index 7b62f17..0000000 --- a/lib/NetworkClientSecure/keywords.txt +++ /dev/null @@ -1,36 +0,0 @@ -####################################### -# Syntax Coloring Map For WiFi -####################################### - -####################################### -# Library (KEYWORD3) -####################################### - -NetworkClientSecure KEYWORD3 - -####################################### -# Datatypes (KEYWORD1) -####################################### - -NetworkClientSecure KEYWORD1 - -####################################### -# Methods and Functions (KEYWORD2) -####################################### - -connect KEYWORD2 -write KEYWORD2 -available KEYWORD2 -config KEYWORD2 -read KEYWORD2 -flush KEYWORD2 -stop KEYWORD2 -connected KEYWORD2 -setCACert KEYWORD2 -setCertificate KEYWORD2 -setPrivateKey KEYWORD2 -setAlpnProtocols KEYWORD2 - -####################################### -# Constants (LITERAL1) -####################################### diff --git a/lib/NetworkClientSecure/library.properties b/lib/NetworkClientSecure/library.properties deleted file mode 100644 index e7dab6a..0000000 --- a/lib/NetworkClientSecure/library.properties +++ /dev/null @@ -1,9 +0,0 @@ -name=NetworkClientSecure -version=3.0.3 -author=Evandro Luis Copercini -maintainer=Github Community -sentence=Enables secure network connection (local and Internet) using the ESP32 built-in WiFi. -paragraph=With this library you can make a TLS or SSL connection to a remote server. -category=Communication -url= -architectures=esp32 diff --git a/lib/NetworkClientSecure/src/NetworkClientSecure.cpp b/lib/NetworkClientSecure/src/NetworkClientSecure.cpp deleted file mode 100644 index 908ddb2..0000000 --- a/lib/NetworkClientSecure/src/NetworkClientSecure.cpp +++ /dev/null @@ -1,460 +0,0 @@ -/* - NetworkClientSecure.cpp - Client Secure class for ESP32 - Copyright (c) 2016 Hristo Gochkov All right reserved. - Additions Copyright (C) 2017 Evandro Luis Copercini. - - 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 -*/ - -#include "NetworkClientSecure.h" -#include "esp_crt_bundle.h" -#include -#include -#include - -#undef connect -#undef write -#undef read - -NetworkClientSecure::NetworkClientSecure() { - _connected = false; - _timeout = 30000; // Same default as ssl_client - - sslclient.reset(new sslclient_context, [](struct sslclient_context *sslclient) { - stop_ssl_socket(sslclient); - delete sslclient; - }); - ssl_init(sslclient.get()); - sslclient->socket = -1; - sslclient->handshake_timeout = 120000; - _use_insecure = false; - _stillinPlainStart = false; - _ca_cert_free = false; - _cert_free = false; - _private_key_free = false; - _CA_cert = NULL; - _cert = NULL; - _private_key = NULL; - _pskIdent = NULL; - _psKey = NULL; - next = NULL; - _alpn_protos = NULL; - _use_ca_bundle = false; -} - -NetworkClientSecure::NetworkClientSecure(int sock) { - _connected = false; - _timeout = 30000; // Same default as ssl_client - _lastReadTimeout = 0; - _lastWriteTimeout = 0; - - sslclient.reset(new sslclient_context, [](struct sslclient_context *sslclient) { - stop_ssl_socket(sslclient); - delete sslclient; - }); - ssl_init(sslclient.get()); - sslclient->socket = sock; - sslclient->handshake_timeout = 120000; - - if (sock >= 0) { - _connected = true; - } - - _use_insecure = false; - _stillinPlainStart = false; - _ca_cert_free = false; - _cert_free = false; - _private_key_free = false; - _CA_cert = NULL; - _cert = NULL; - _private_key = NULL; - _pskIdent = NULL; - _psKey = NULL; - next = NULL; - _alpn_protos = NULL; -} - -NetworkClientSecure::~NetworkClientSecure() { - if (_ca_cert_free && _CA_cert) { - free((void *)_CA_cert); - } - if (_cert_free && _cert) { - free((void *)_cert); - } - if (_private_key_free && _private_key) { - free((void *)_private_key); - } -} - -void NetworkClientSecure::stop() { - stop_ssl_socket(sslclient.get()); - - _connected = false; - sslclient->peek_buf = -1; - _lastReadTimeout = 0; - _lastWriteTimeout = 0; -} - -int NetworkClientSecure::connect(IPAddress ip, uint16_t port) { - if (_pskIdent && _psKey) { - return connect(ip, port, _pskIdent, _psKey); - } - return connect(ip, port, _CA_cert, _cert, _private_key); -} - -int NetworkClientSecure::connect(IPAddress ip, uint16_t port, int32_t timeout) { - _timeout = timeout; - return connect(ip, port); -} - -int NetworkClientSecure::connect(const char *host, uint16_t port) { - if (_pskIdent && _psKey) { - return connect(host, port, _pskIdent, _psKey); - } - return connect(host, port, _CA_cert, _cert, _private_key); -} - -int NetworkClientSecure::connect(const char *host, uint16_t port, int32_t timeout) { - _timeout = timeout; - return connect(host, port); -} - -int NetworkClientSecure::connect(IPAddress ip, uint16_t port, const char *CA_cert, const char *cert, const char *private_key) { - return connect(ip, port, NULL, CA_cert, cert, private_key); -} - -int NetworkClientSecure::connect(const char *host, uint16_t port, const char *CA_cert, const char *cert, const char *private_key) { - IPAddress address; - if (!Network.hostByName(host, address)) { - return 0; - } - - return connect(address, port, host, CA_cert, cert, private_key); -} - -int NetworkClientSecure::connect(IPAddress ip, uint16_t port, const char *host, const char *CA_cert, const char *cert, const char *private_key) { - int ret = start_ssl_client(sslclient.get(), ip, port, host, _timeout, CA_cert, _use_ca_bundle, cert, private_key, NULL, NULL, _use_insecure, _alpn_protos); - - if (ret >= 0 && !_stillinPlainStart) { - ret = ssl_starttls_handshake(sslclient.get()); - } else { - log_i("Actual TLS start postponed."); - } - - sslclient->last_error = ret; - - if (ret < 0) { - log_e("start_ssl_client: connect failed: %d", ret); - stop(); - return 0; - } - _connected = true; - return 1; -} - -int NetworkClientSecure::startTLS() { - int ret = 1; - if (_stillinPlainStart) { - log_i("startTLS: starting TLS/SSL on this dplain connection"); - ret = ssl_starttls_handshake(sslclient.get()); - if (ret < 0) { - log_e("startTLS: %d", ret); - stop(); - return 0; - }; - _stillinPlainStart = false; - } else { - log_i("startTLS: ignoring StartTLS - as we should be secure already"); - } - return 1; -} - -int NetworkClientSecure::connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey) { - return connect(ip.toString().c_str(), port, pskIdent, psKey); -} - -int NetworkClientSecure::connect(const char *host, uint16_t port, const char *pskIdent, const char *psKey) { - log_v("start_ssl_client with PSK"); - - IPAddress address; - if (!Network.hostByName(host, address)) { - return 0; - } - - int ret = start_ssl_client(sslclient.get(), address, port, host, _timeout, NULL, false, NULL, NULL, pskIdent, psKey, _use_insecure, _alpn_protos); - sslclient->last_error = ret; - if (ret < 0) { - log_e("start_ssl_client: connect failed %d", ret); - stop(); - return 0; - } - _connected = true; - return 1; -} - -int NetworkClientSecure::peek() { - if (sslclient->peek_buf >= 0) { - return sslclient->peek_buf; - } - sslclient->peek_buf = timedRead(); - return sslclient->peek_buf; -} - -size_t NetworkClientSecure::write(uint8_t data) { - return write(&data, 1); -} - -int NetworkClientSecure::read() { - uint8_t data = -1; - int res = read(&data, 1); - return res < 0 ? res : data; -} - -size_t NetworkClientSecure::write(const uint8_t *buf, size_t size) { - if (!_connected) { - return 0; - } - - if (_stillinPlainStart) { - return send_net_data(sslclient.get(), buf, size); - } - - if (_lastWriteTimeout != _timeout) { - struct timeval timeout_tv; - timeout_tv.tv_sec = _timeout / 1000; - timeout_tv.tv_usec = (_timeout % 1000) * 1000; - if (setSocketOption(SO_SNDTIMEO, (char *)&timeout_tv, sizeof(struct timeval)) >= 0) { - _lastWriteTimeout = _timeout; - } - } - int res = send_ssl_data(sslclient.get(), buf, size); - if (res < 0) { - log_e("Closing connection on failed write"); - stop(); - res = 0; - } - return res; -} - -int NetworkClientSecure::read(uint8_t *buf, size_t size) { - if (_stillinPlainStart) { - return get_net_receive(sslclient.get(), buf, size); - } - - if (_lastReadTimeout != _timeout) { - if (fd() >= 0) { - struct timeval timeout_tv; - timeout_tv.tv_sec = _timeout / 1000; - timeout_tv.tv_usec = (_timeout % 1000) * 1000; - if (setSocketOption(SO_RCVTIMEO, (char *)&timeout_tv, sizeof(struct timeval)) >= 0) { - _lastReadTimeout = _timeout; - } - } - } - - int peeked = 0, res = -1; - int avail = available(); - if ((!buf && size) || avail <= 0) { - return -1; - } - if (!size) { - return 0; - } - if (sslclient->peek_buf >= 0) { - buf[0] = sslclient->peek_buf; - sslclient->peek_buf = -1; - size--; - avail--; - if (!size || !avail) { - return 1; - } - buf++; - peeked = 1; - } - res = get_ssl_receive(sslclient.get(), buf, size); - - if (res < 0) { - log_e("Closing connection on failed read"); - stop(); - return peeked ? peeked : res; - } - return res + peeked; -} - -int NetworkClientSecure::available() { - if (_stillinPlainStart) { - return peek_net_receive(sslclient.get(), 0); - } - - int peeked = (sslclient->peek_buf >= 0), res = -1; - if (!_connected) { - return peeked; - } - res = data_to_read(sslclient.get()); - - if (res < 0 && !_stillinPlainStart) { - if (res != MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { - log_e("Closing connection on failed available check"); - } - stop(); - return peeked; - } - return res + peeked; -} - -uint8_t NetworkClientSecure::connected() { - uint8_t dummy = 0; - read(&dummy, 0); - - return _connected; -} - -void NetworkClientSecure::setInsecure() { - _CA_cert = NULL; - _cert = NULL; - _private_key = NULL; - _pskIdent = NULL; - _psKey = NULL; - _use_insecure = true; -} - -void NetworkClientSecure::setCACert(const char *rootCA) { - if (_ca_cert_free && _CA_cert) { - free((void *)_CA_cert); - _ca_cert_free = false; - } - _CA_cert = rootCA; - _use_insecure = false; -} - -void NetworkClientSecure::setCACertBundle(const uint8_t *bundle, size_t size) { - if (bundle != NULL && size > 0) { - esp_crt_bundle_set(bundle, size); - attach_ssl_certificate_bundle(sslclient.get(), true); - _use_ca_bundle = true; - } else { - esp_crt_bundle_detach(NULL); - attach_ssl_certificate_bundle(sslclient.get(), false); - _use_ca_bundle = false; - } -} - -void NetworkClientSecure::setDefaultCACertBundle() { - attach_ssl_certificate_bundle(sslclient.get(), true); - _use_ca_bundle = true; -} - -void NetworkClientSecure::setCertificate(const char *client_ca) { - if (_cert_free && _cert) { - free((void *)_cert); - _cert_free = false; - } - _cert = client_ca; -} - -void NetworkClientSecure::setPrivateKey(const char *private_key) { - if (_private_key_free && _private_key) { - free((void *)_private_key); - _private_key_free = false; - } - _private_key = private_key; -} - -void NetworkClientSecure::setPreSharedKey(const char *pskIdent, const char *psKey) { - _pskIdent = pskIdent; - _psKey = psKey; -} - -bool NetworkClientSecure::verify(const char *fp, const char *domain_name) { - if (!sslclient) { - return false; - } - - return verify_ssl_fingerprint(sslclient.get(), fp, domain_name); -} - -char *NetworkClientSecure::_streamLoad(Stream &stream, size_t size) { - char *dest = (char *)malloc(size + 1); - if (!dest) { - return nullptr; - } - if (size != stream.readBytes(dest, size)) { - free(dest); - dest = nullptr; - return nullptr; - } - dest[size] = '\0'; - return dest; -} - -bool NetworkClientSecure::loadCACert(Stream &stream, size_t size) { - if (_CA_cert != NULL) { - free(const_cast(_CA_cert)); - } - char *dest = _streamLoad(stream, size); - bool ret = false; - if (dest) { - setCACert(dest); - _ca_cert_free = true; - ret = true; - } - return ret; -} - -bool NetworkClientSecure::loadCertificate(Stream &stream, size_t size) { - if (_cert != NULL) { - free(const_cast(_cert)); - } - char *dest = _streamLoad(stream, size); - bool ret = false; - if (dest) { - setCertificate(dest); - _cert_free = true; - ret = true; - } - return ret; -} - -bool NetworkClientSecure::loadPrivateKey(Stream &stream, size_t size) { - if (_private_key != NULL) { - free(const_cast(_private_key)); - } - char *dest = _streamLoad(stream, size); - bool ret = false; - if (dest) { - setPrivateKey(dest); - _private_key_free = true; - ret = true; - } - return ret; -} - -int NetworkClientSecure::lastError(char *buf, const size_t size) { - int lastError = sslclient->last_error; - mbedtls_strerror(lastError, buf, size); - return lastError; -} - -void NetworkClientSecure::setHandshakeTimeout(unsigned long handshake_timeout) { - sslclient->handshake_timeout = handshake_timeout * 1000; -} - -void NetworkClientSecure::setAlpnProtocols(const char **alpn_protos) { - _alpn_protos = alpn_protos; -} - -int NetworkClientSecure::fd() const { - return sslclient->socket; -} diff --git a/lib/NetworkClientSecure/src/NetworkClientSecure.h b/lib/NetworkClientSecure/src/NetworkClientSecure.h deleted file mode 100644 index ebfcf76..0000000 --- a/lib/NetworkClientSecure/src/NetworkClientSecure.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - NetworkClientSecure.h - Base class that provides Client SSL to ESP32 - Copyright (c) 2011 Adrian McEwen. All right reserved. - Additions Copyright (C) 2017 Evandro Luis Copercini. - - 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 -*/ - -#ifndef NetworkClientSecure_h -#define NetworkClientSecure_h -#include "Arduino.h" -#include "IPAddress.h" -#include "Network.h" -#include "ssl_client.h" -#include - -class NetworkClientSecure : public NetworkClient { -protected: - std::shared_ptr sslclient; - - bool _use_insecure; - bool _stillinPlainStart; - bool _ca_cert_free; - bool _cert_free; - bool _private_key_free; - const char *_CA_cert; - const char *_cert; - const char *_private_key; - const char *_pskIdent; // identity for PSK cipher suites - const char *_psKey; // key in hex for PSK cipher suites - const char **_alpn_protos; - bool _use_ca_bundle; - -public: - NetworkClientSecure *next; - NetworkClientSecure(); - NetworkClientSecure(int socket); - ~NetworkClientSecure(); - int connect(IPAddress ip, uint16_t port); - int connect(IPAddress ip, uint16_t port, int32_t timeout); - int connect(const char *host, uint16_t port); - int connect(const char *host, uint16_t port, int32_t timeout); - int connect(IPAddress ip, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key); - int connect(const char *host, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key); - int connect(IPAddress ip, uint16_t port, const char *pskIdent, const char *psKey); - int connect(const char *host, uint16_t port, const char *pskIdent, const char *psKey); - int connect(IPAddress ip, uint16_t port, const char *host, const char *CA_cert, const char *cert, const char *private_key); - int peek(); - size_t write(uint8_t data); - size_t write(const uint8_t *buf, size_t size); - int available(); - int read(); - int read(uint8_t *buf, size_t size); - void flush() {} - void stop(); - uint8_t connected(); - int lastError(char *buf, const size_t size); - void setInsecure(); // Don't validate the chain, just accept whatever is given. VERY INSECURE! - void setPreSharedKey(const char *pskIdent, const char *psKey); // psKey in Hex - void setCACert(const char *rootCA); - void setCertificate(const char *client_ca); - void setPrivateKey(const char *private_key); - bool loadCACert(Stream &stream, size_t size); - void setCACertBundle(const uint8_t *bundle, size_t size); - void setDefaultCACertBundle(); - bool loadCertificate(Stream &stream, size_t size); - bool loadPrivateKey(Stream &stream, size_t size); - bool verify(const char *fingerprint, const char *domain_name); - void setHandshakeTimeout(unsigned long handshake_timeout); - void setAlpnProtocols(const char **alpn_protos); - - // Certain protocols start in plain-text; and then have the client - // give some STARTSSL command to `upgrade' the connection to TLS - // or SSL. Setting PlainStart to true (the default is false) enables - // this. It is up to the application code to then call 'startTLS()' - // at the right point to initialize the SSL or TLS upgrade. - - void setPlainStart() { - _stillinPlainStart = true; - }; - bool stillInPlainStart() { - return _stillinPlainStart; - }; - int startTLS(); - - const mbedtls_x509_crt *getPeerCertificate() { - return mbedtls_ssl_get_peer_cert(&sslclient->ssl_ctx); - }; - bool getFingerprintSHA256(uint8_t sha256_result[32]) { - return get_peer_fingerprint(sslclient.get(), sha256_result); - }; - int fd() const; - - operator bool() { - return connected(); - } - - bool operator==(const bool value) { - return bool() == value; - } - bool operator!=(const bool value) { - return bool() != value; - } - bool operator==(const NetworkClientSecure &); - bool operator!=(const NetworkClientSecure &rhs) { - return !this->operator==(rhs); - }; - - int socket() { - return sslclient->socket = -1; - } - -private: - char *_streamLoad(Stream &stream, size_t size); - - //friend class NetworkServer; - using Print::write; -}; - -#endif /* _WIFICLIENT_H_ */ diff --git a/lib/NetworkClientSecure/src/WiFiClientSecure.h b/lib/NetworkClientSecure/src/WiFiClientSecure.h deleted file mode 100644 index 56e7f28..0000000 --- a/lib/NetworkClientSecure/src/WiFiClientSecure.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once -#include "NetworkClientSecure.h" -typedef NetworkClientSecure WiFiClientSecure; diff --git a/lib/NetworkClientSecure/src/ssl_client.cpp b/lib/NetworkClientSecure/src/ssl_client.cpp deleted file mode 100644 index 0f93f5c..0000000 --- a/lib/NetworkClientSecure/src/ssl_client.cpp +++ /dev/null @@ -1,633 +0,0 @@ -/* Provide SSL/TLS functions to ESP32 with Arduino IDE -* -* Adapted from the ssl_client1 example of mbedtls. -* -* Original Copyright (C) 2006-2015, ARM Limited, All Rights Reserved, Apache 2.0 License. -* Additions Copyright (C) 2017 Evandro Luis Copercini, Apache 2.0 License. -*/ - -#include "Arduino.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "ssl_client.h" -#include "esp_crt_bundle.h" - -#if !defined(MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED) && !defined(MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED) -#warning \ - "Please call `idf.py menuconfig` then go to Component config -> mbedTLS -> TLS Key Exchange Methods -> Enable pre-shared-key ciphersuites and then check `Enable PSK based ciphersuite modes`. Save and Quit." -#else - -const char *pers = "esp32-tls"; - -static int _handle_error(int err, const char *function, int line) { - if (err == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) { - return err; - } -#ifdef MBEDTLS_ERROR_C - char error_buf[100]; - mbedtls_strerror(err, error_buf, 100); - log_e("[%s():%d]: (%d) %s", function, line, err, error_buf); -#else - log_e("[%s():%d]: code %d", function, line, err); -#endif - return err; -} - -#define handle_error(e) _handle_error(e, __FUNCTION__, __LINE__) - -void ssl_init(sslclient_context *ssl_client) { - // reset embedded pointers to zero - memset(ssl_client, 0, sizeof(sslclient_context)); - mbedtls_ssl_init(&ssl_client->ssl_ctx); - mbedtls_ssl_config_init(&ssl_client->ssl_conf); - mbedtls_ctr_drbg_init(&ssl_client->drbg_ctx); - ssl_client->peek_buf = -1; -} - -void attach_ssl_certificate_bundle(sslclient_context *ssl_client, bool att) { - if (att) { - ssl_client->bundle_attach_cb = &esp_crt_bundle_attach; - } else { - ssl_client->bundle_attach_cb = NULL; - } -} - -int start_ssl_client( - sslclient_context *ssl_client, const IPAddress &ip, uint32_t port, const char *hostname, int timeout, const char *rootCABuff, bool useRootCABundle, - const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos -) { - int ret; - int enable = 1; - log_v("Free internal heap before TLS %u", ESP.getFreeHeap()); - - if (rootCABuff == NULL && pskIdent == NULL && psKey == NULL && !insecure && !useRootCABundle) { - return -1; - } - - int domain = ip.type() == IPv6 ? AF_INET6 : AF_INET; - log_v("Starting socket (domain %d)", domain); - ssl_client->socket = -1; - - ssl_client->socket = lwip_socket(domain, SOCK_STREAM, IPPROTO_TCP); - if (ssl_client->socket < 0) { - log_e("ERROR opening socket"); - return ssl_client->socket; - } - - fcntl(ssl_client->socket, F_SETFL, fcntl(ssl_client->socket, F_GETFL, 0) | O_NONBLOCK); - struct sockaddr_storage serv_addr = {}; - if (domain == AF_INET6) { - struct sockaddr_in6 *tmpaddr = (struct sockaddr_in6 *)&serv_addr; - tmpaddr->sin6_family = AF_INET6; - for (int index = 0; index < 16; index++) { - tmpaddr->sin6_addr.s6_addr[index] = ip[index]; - } - tmpaddr->sin6_port = htons(port); - tmpaddr->sin6_scope_id = ip.zone(); - } else { - struct sockaddr_in *tmpaddr = (struct sockaddr_in *)&serv_addr; - tmpaddr->sin_family = AF_INET; - tmpaddr->sin_addr.s_addr = ip; - tmpaddr->sin_port = htons(port); - } - - if (timeout <= 0) { - timeout = 30000; // Milli seconds. - } - - ssl_client->socket_timeout = timeout; - - fd_set fdset; - struct timeval tv; - FD_ZERO(&fdset); - FD_SET(ssl_client->socket, &fdset); - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; - - int res = lwip_connect(ssl_client->socket, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); - if (res < 0 && errno != EINPROGRESS) { - log_e("connect on fd %d, errno: %d, \"%s\"", ssl_client->socket, errno, strerror(errno)); - lwip_close(ssl_client->socket); - ssl_client->socket = -1; - return -1; - } - - res = select(ssl_client->socket + 1, nullptr, &fdset, nullptr, timeout < 0 ? nullptr : &tv); - if (res < 0) { - log_e("select on fd %d, errno: %d, \"%s\"", ssl_client->socket, errno, strerror(errno)); - lwip_close(ssl_client->socket); - ssl_client->socket = -1; - return -1; - } else if (res == 0) { - log_i("select returned due to timeout %d ms for fd %d", timeout, ssl_client->socket); - lwip_close(ssl_client->socket); - ssl_client->socket = -1; - return -1; - } else { - int sockerr; - socklen_t len = (socklen_t)sizeof(int); - res = getsockopt(ssl_client->socket, SOL_SOCKET, SO_ERROR, &sockerr, &len); - - if (res < 0) { - log_e("getsockopt on fd %d, errno: %d, \"%s\"", ssl_client->socket, errno, strerror(errno)); - lwip_close(ssl_client->socket); - ssl_client->socket = -1; - return -1; - } - - if (sockerr != 0) { - log_e("socket error on fd %d, errno: %d, \"%s\"", ssl_client->socket, sockerr, strerror(sockerr)); - lwip_close(ssl_client->socket); - ssl_client->socket = -1; - return -1; - } - } - -#define ROE(x, msg) \ - { \ - if (((x) < 0)) { \ - log_e("LWIP Socket config of " msg " failed."); \ - return -1; \ - } \ - } - ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), "SO_RCVTIMEO"); - ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), "SO_SNDTIMEO"); - - ROE(lwip_setsockopt(ssl_client->socket, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)), "TCP_NODELAY"); - ROE(lwip_setsockopt(ssl_client->socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)), "SO_KEEPALIVE"); - - log_v("Seeding the random number generator"); - mbedtls_entropy_init(&ssl_client->entropy_ctx); - - ret = mbedtls_ctr_drbg_seed(&ssl_client->drbg_ctx, mbedtls_entropy_func, &ssl_client->entropy_ctx, (const unsigned char *)pers, strlen(pers)); - if (ret < 0) { - return handle_error(ret); - } - - log_v("Setting up the SSL/TLS structure..."); - - if ((ret = mbedtls_ssl_config_defaults(&ssl_client->ssl_conf, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { - return handle_error(ret); - } - - if (alpn_protos != NULL) { - log_v("Setting ALPN protocols"); - if ((ret = mbedtls_ssl_conf_alpn_protocols(&ssl_client->ssl_conf, alpn_protos)) != 0) { - return handle_error(ret); - } - } - - // MBEDTLS_SSL_VERIFY_REQUIRED if a CA certificate is defined on Arduino IDE and - // MBEDTLS_SSL_VERIFY_NONE if not. - - if (insecure) { - mbedtls_ssl_conf_authmode(&ssl_client->ssl_conf, MBEDTLS_SSL_VERIFY_NONE); - log_d("WARNING: Skipping SSL Verification. INSECURE!"); - } else if (rootCABuff != NULL) { - log_v("Loading CA cert"); - mbedtls_x509_crt_init(&ssl_client->ca_cert); - mbedtls_ssl_conf_authmode(&ssl_client->ssl_conf, MBEDTLS_SSL_VERIFY_REQUIRED); - ret = mbedtls_x509_crt_parse(&ssl_client->ca_cert, (const unsigned char *)rootCABuff, strlen(rootCABuff) + 1); - mbedtls_ssl_conf_ca_chain(&ssl_client->ssl_conf, &ssl_client->ca_cert, NULL); - //mbedtls_ssl_conf_verify(&ssl_client->ssl_ctx, my_verify, NULL ); - if (ret < 0) { - // free the ca_cert in the case parse failed, otherwise, the old ca_cert still in the heap memory, that lead to "out of memory" crash. - mbedtls_x509_crt_free(&ssl_client->ca_cert); - return handle_error(ret); - } - } else if (useRootCABundle) { - if (ssl_client->bundle_attach_cb != NULL) { - log_v("Attaching root CA cert bundle"); - ret = ssl_client->bundle_attach_cb(&ssl_client->ssl_conf); - if (ret < 0) { - return handle_error(ret); - } - } else { - log_e("useRootCABundle is set, but attach_ssl_certificate_bundle(ssl, true); was not called!"); - } - } else if (pskIdent != NULL && psKey != NULL) { - log_v("Setting up PSK"); - // convert PSK from hex to binary - if ((strlen(psKey) & 1) != 0 || strlen(psKey) > 2 * MBEDTLS_PSK_MAX_LEN) { - log_e("pre-shared key not valid hex or too long"); - return -1; - } - unsigned char psk[MBEDTLS_PSK_MAX_LEN]; - size_t psk_len = strlen(psKey) / 2; - for (int j = 0; j < strlen(psKey); j += 2) { - char c = psKey[j]; - if (c >= '0' && c <= '9') { - c -= '0'; - } else if (c >= 'A' && c <= 'F') { - c -= 'A' - 10; - } else if (c >= 'a' && c <= 'f') { - c -= 'a' - 10; - } else { - return -1; - } - psk[j / 2] = c << 4; - c = psKey[j + 1]; - if (c >= '0' && c <= '9') { - c -= '0'; - } else if (c >= 'A' && c <= 'F') { - c -= 'A' - 10; - } else if (c >= 'a' && c <= 'f') { - c -= 'a' - 10; - } else { - return -1; - } - psk[j / 2] |= c; - } - // set mbedtls config - ret = mbedtls_ssl_conf_psk(&ssl_client->ssl_conf, psk, psk_len, (const unsigned char *)pskIdent, strlen(pskIdent)); - if (ret != 0) { - log_e("mbedtls_ssl_conf_psk returned %d", ret); - return handle_error(ret); - } - } else { - return -1; - } - - // Note - this check for BOTH key and cert is relied on - // later during cleanup. - - if (!insecure && cli_cert != NULL && cli_key != NULL) { - mbedtls_x509_crt_init(&ssl_client->client_cert); - mbedtls_pk_init(&ssl_client->client_key); - - log_v("Loading CRT cert"); - - ret = mbedtls_x509_crt_parse(&ssl_client->client_cert, (const unsigned char *)cli_cert, strlen(cli_cert) + 1); - if (ret < 0) { - // free the client_cert in the case parse failed, otherwise, the old client_cert still in the heap memory, that lead to "out of memory" crash. - mbedtls_x509_crt_free(&ssl_client->client_cert); - return handle_error(ret); - } - - log_v("Loading private key"); - mbedtls_ctr_drbg_context ctr_drbg; - mbedtls_ctr_drbg_init(&ctr_drbg); - ret = mbedtls_pk_parse_key(&ssl_client->client_key, (const unsigned char *)cli_key, strlen(cli_key) + 1, NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg); - mbedtls_ctr_drbg_free(&ctr_drbg); - - if (ret != 0) { - mbedtls_x509_crt_free(&ssl_client->client_cert); // cert+key are free'd in pair - return handle_error(ret); - } - - mbedtls_ssl_conf_own_cert(&ssl_client->ssl_conf, &ssl_client->client_cert, &ssl_client->client_key); - } - - log_v("Setting hostname for TLS session..."); - - // Hostname set here should match CN in server certificate - if ((ret = mbedtls_ssl_set_hostname(&ssl_client->ssl_ctx, hostname != NULL ? hostname : ip.toString().c_str())) != 0) { - return handle_error(ret); - } - - mbedtls_ssl_conf_rng(&ssl_client->ssl_conf, mbedtls_ctr_drbg_random, &ssl_client->drbg_ctx); - - if ((ret = mbedtls_ssl_setup(&ssl_client->ssl_ctx, &ssl_client->ssl_conf)) != 0) { - return handle_error(ret); - } - - mbedtls_ssl_set_bio(&ssl_client->ssl_ctx, &ssl_client->socket, mbedtls_net_send, mbedtls_net_recv, NULL); - return ssl_client->socket; -} - -int ssl_starttls_handshake(sslclient_context *ssl_client) { - char buf[512]; - int ret, flags; - - log_v("Performing the SSL/TLS handshake..."); - unsigned long handshake_start_time = millis(); - while ((ret = mbedtls_ssl_handshake(&ssl_client->ssl_ctx)) != 0) { - if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - return handle_error(ret); - } - if ((millis() - handshake_start_time) > ssl_client->handshake_timeout) { - return -1; - } - vTaskDelay(2); //2 ticks - } - - if (ssl_client->client_cert.version) { - log_d("Protocol is %s Ciphersuite is %s", mbedtls_ssl_get_version(&ssl_client->ssl_ctx), mbedtls_ssl_get_ciphersuite(&ssl_client->ssl_ctx)); - if ((ret = mbedtls_ssl_get_record_expansion(&ssl_client->ssl_ctx)) >= 0) { - log_d("Record expansion is %d", ret); - } else { - log_w("Record expansion is unknown (compression)"); - } - } - - log_v("Verifying peer X.509 certificate..."); - - if ((flags = mbedtls_ssl_get_verify_result(&ssl_client->ssl_ctx)) != 0) { - memset(buf, 0, sizeof(buf)); - mbedtls_x509_crt_verify_info(buf, sizeof(buf), " ! ", flags); - log_e("Failed to verify peer certificate! verification info: %s", buf); - return handle_error(ret); - } else { - log_v("Certificate verified."); - } - - if (ssl_client->ca_cert.version) { - mbedtls_x509_crt_free(&ssl_client->ca_cert); - } - - // We know that we always have a client cert/key pair -- and we - // cannot look into the private client_key pk struct for newer - // versions of mbedtls. So rely on a public field of the cert - // and infer that there is a key too. - if (ssl_client->client_cert.version) { - mbedtls_x509_crt_free(&ssl_client->client_cert); - mbedtls_pk_free(&ssl_client->client_key); - } - - log_v("Free internal heap after TLS %u", ESP.getFreeHeap()); - - return ssl_client->socket; -} - -void stop_ssl_socket(sslclient_context *ssl_client) { - log_v("Cleaning SSL connection."); - - if (ssl_client->socket >= 0) { - lwip_close(ssl_client->socket); - ssl_client->socket = -1; - } - - // avoid memory leak if ssl connection attempt failed - // if (ssl_client->ssl_conf.ca_chain != NULL) { - mbedtls_x509_crt_free(&ssl_client->ca_cert); - // } - // if (ssl_client->ssl_conf.key_cert != NULL) { - mbedtls_x509_crt_free(&ssl_client->client_cert); - mbedtls_pk_free(&ssl_client->client_key); - // } - mbedtls_ssl_free(&ssl_client->ssl_ctx); - mbedtls_ssl_config_free(&ssl_client->ssl_conf); - mbedtls_ctr_drbg_free(&ssl_client->drbg_ctx); - mbedtls_entropy_free(&ssl_client->entropy_ctx); - - // save only interesting fields - int handshake_timeout = ssl_client->handshake_timeout; - int socket_timeout = ssl_client->socket_timeout; - int last_err = ssl_client->last_error; - - // reset embedded pointers to zero - memset(ssl_client, 0, sizeof(sslclient_context)); - - ssl_client->handshake_timeout = handshake_timeout; - ssl_client->socket_timeout = socket_timeout; - ssl_client->last_error = last_err; - ssl_client->peek_buf = -1; -} - -int data_to_read(sslclient_context *ssl_client) { - int ret, res; - ret = mbedtls_ssl_read(&ssl_client->ssl_ctx, NULL, 0); - //log_e("RET: %i",ret); //for low level debug - res = mbedtls_ssl_get_bytes_avail(&ssl_client->ssl_ctx); - //log_e("RES: %i",res); //for low level debug - if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { - return handle_error(ret); - } - - return res; -} - -int send_ssl_data(sslclient_context *ssl_client, const uint8_t *data, size_t len) { - unsigned long write_start_time = millis(); - int ret = -1; - - while ((ret = mbedtls_ssl_write(&ssl_client->ssl_ctx, data, len)) <= 0) { - if ((millis() - write_start_time) > ssl_client->socket_timeout) { - log_v("SSL write timed out."); - return -1; - } - - if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE && ret < 0) { - log_v("Handling error %d", ret); //for low level debug - return handle_error(ret); - } - - //wait for space to become available - vTaskDelay(2); - } - - return ret; -} - -// Some protocols, such as SMTP, XMPP, MySQL/Posgress and various others -// do a 'in-line' upgrade from plaintext to SSL or TLS (usually with some -// sort of 'STARTTLS' textual command from client to sever). For this -// we need to have access to the 'raw' socket; i.e. without TLS/SSL state -// handling before the handshake starts; but after setting up the TLS -// connection. -// -int peek_net_receive(sslclient_context *ssl_client, int timeout) { -#if MBEDTLS_FIXED_LINKING_NET_POLL - int ret = mbedtls_net_poll((mbedtls_net_context *)ssl_client, MBEDTLS_NET_POLL_READ, timeout); - ret == MBEDTLS_NET_POLL_READ ? 1 : ret; -#else - // We should be using mbedtls_net_poll(); which is part of mbedtls and - // included in the EspressifSDK. Unfortunately - it did not make it into - // the statically linked library file. So, for now, we replace it by - // substancially similar code. - // - struct timeval tv = {.tv_sec = timeout / 1000, .tv_usec = (timeout % 1000) * 1000}; - - fd_set fdset; - FD_SET(ssl_client->socket, &fdset); - - int ret = select(ssl_client->socket + 1, &fdset, nullptr, nullptr, timeout < 0 ? nullptr : &tv); - if (ret < 0) { - log_e("select on read fd %d, errno: %d, \"%s\"", ssl_client->socket, errno, strerror(errno)); - lwip_close(ssl_client->socket); - ssl_client->socket = -1; - return -1; - }; -#endif - return ret; -}; - -int get_net_receive(sslclient_context *ssl_client, uint8_t *data, int length) { - int ret = peek_net_receive(ssl_client, ssl_client->socket_timeout); - if (ret > 0) { - ret = mbedtls_net_recv(ssl_client, data, length); - } - - // log_v( "%d bytes NET read of %d", ret, length); //for low level debug - return ret; -} - -int send_net_data(sslclient_context *ssl_client, const uint8_t *data, size_t len) { - int ret = mbedtls_net_send(ssl_client, data, len); - // log_v("Net sending %d btes->ret %d", len, ret); //for low level debug - return ret; -} - -int get_ssl_receive(sslclient_context *ssl_client, uint8_t *data, int length) { - int ret = mbedtls_ssl_read(&ssl_client->ssl_ctx, data, length); - // log_v( "%d bytes SSL read", ret); //for low level debug - return ret; -} - -static bool parseHexNibble(char pb, uint8_t *res) { - if (pb >= '0' && pb <= '9') { - *res = (uint8_t)(pb - '0'); - return true; - } else if (pb >= 'a' && pb <= 'f') { - *res = (uint8_t)(pb - 'a' + 10); - return true; - } else if (pb >= 'A' && pb <= 'F') { - *res = (uint8_t)(pb - 'A' + 10); - return true; - } - return false; -} - -// Compare a name from certificate and domain name, return true if they match -static bool matchName(const std::string &name, const std::string &domainName) { - size_t wildcardPos = name.find('*'); - if (wildcardPos == std::string::npos) { - // Not a wildcard, expect an exact match - return name == domainName; - } - - size_t firstDotPos = name.find('.'); - if (wildcardPos > firstDotPos) { - // Wildcard is not part of leftmost component of domain name - // Do not attempt to match (rfc6125 6.4.3.1) - return false; - } - if (wildcardPos != 0 || firstDotPos != 1) { - // Matching of wildcards such as baz*.example.com and b*z.example.com - // is optional. Maybe implement this in the future? - return false; - } - size_t domainNameFirstDotPos = domainName.find('.'); - if (domainNameFirstDotPos == std::string::npos) { - return false; - } - return domainName.substr(domainNameFirstDotPos) == name.substr(firstDotPos); -} - -// Verifies certificate provided by the peer to match specified SHA256 fingerprint -bool verify_ssl_fingerprint(sslclient_context *ssl_client, const char *fp, const char *domain_name) { - // Convert hex string to byte array - uint8_t fingerprint_local[32]; - int len = strlen(fp); - int pos = 0; - for (size_t i = 0; i < sizeof(fingerprint_local); ++i) { - while (pos < len && ((fp[pos] == ' ') || (fp[pos] == ':'))) { - ++pos; - } - if (pos > len - 2) { - log_d("pos:%d len:%d fingerprint too short", pos, len); - return false; - } - uint8_t high, low; - if (!parseHexNibble(fp[pos], &high) || !parseHexNibble(fp[pos + 1], &low)) { - log_d("pos:%d len:%d invalid hex sequence: %c%c", pos, len, fp[pos], fp[pos + 1]); - return false; - } - pos += 2; - fingerprint_local[i] = low | (high << 4); - } - - // Calculate certificate's SHA256 fingerprint - uint8_t fingerprint_remote[32]; - if (!get_peer_fingerprint(ssl_client, fingerprint_remote)) { - return false; - } - - // Check if fingerprints match - if (memcmp(fingerprint_local, fingerprint_remote, 32)) { - log_d("fingerprint doesn't match"); - return false; - } - - // Additionally check if certificate has domain name if provided - if (domain_name) { - return verify_ssl_dn(ssl_client, domain_name); - } else { - return true; - } -} - -bool get_peer_fingerprint(sslclient_context *ssl_client, uint8_t sha256[32]) { - if (!ssl_client) { - log_d("Invalid ssl_client pointer"); - return false; - }; - - const mbedtls_x509_crt *crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx); - if (!crt) { - log_d("Failed to get peer cert."); - return false; - }; - - mbedtls_sha256_context sha256_ctx; - mbedtls_sha256_init(&sha256_ctx); - mbedtls_sha256_starts(&sha256_ctx, false); - mbedtls_sha256_update(&sha256_ctx, crt->raw.p, crt->raw.len); - mbedtls_sha256_finish(&sha256_ctx, sha256); - - return true; -} - -// Checks if peer certificate has specified domain in CN or SANs -bool verify_ssl_dn(sslclient_context *ssl_client, const char *domain_name) { - log_d("domain name: '%s'", (domain_name) ? domain_name : "(null)"); - std::string domain_name_str(domain_name); - std::transform(domain_name_str.begin(), domain_name_str.end(), domain_name_str.begin(), ::tolower); - - // Get certificate provided by the peer - const mbedtls_x509_crt *crt = mbedtls_ssl_get_peer_cert(&ssl_client->ssl_ctx); - - // Check for domain name in SANs - const mbedtls_x509_sequence *san = &crt->subject_alt_names; - while (san != nullptr) { - std::string san_str((const char *)san->buf.p, san->buf.len); - std::transform(san_str.begin(), san_str.end(), san_str.begin(), ::tolower); - - if (matchName(san_str, domain_name_str)) { - return true; - } - - log_d("SAN '%s': no match", san_str.c_str()); - - // Fetch next SAN - san = san->next; - } - - // Check for domain name in CN - const mbedtls_asn1_named_data *common_name = &crt->subject; - while (common_name != nullptr) { - // While iterating through DN objects, check for CN object - if (!MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &common_name->oid)) { - std::string common_name_str((const char *)common_name->val.p, common_name->val.len); - - if (matchName(common_name_str, domain_name_str)) { - return true; - } - - log_d("CN '%s': not match", common_name_str.c_str()); - } - - // Fetch next DN object - common_name = common_name->next; - } - - return false; -} -#endif diff --git a/lib/NetworkClientSecure/src/ssl_client.h b/lib/NetworkClientSecure/src/ssl_client.h deleted file mode 100644 index 892adc8..0000000 --- a/lib/NetworkClientSecure/src/ssl_client.h +++ /dev/null @@ -1,56 +0,0 @@ -/* Provide SSL/TLS functions to ESP32 with Arduino IDE - * by Evandro Copercini - 2017 - Apache 2.0 License - */ - -#ifndef ARD_SSL_H -#define ARD_SSL_H -#include "mbedtls/platform.h" -#include "mbedtls/net_sockets.h" -#include "mbedtls/debug.h" -#include "mbedtls/ssl.h" -#include "mbedtls/entropy.h" -#include "mbedtls/ctr_drbg.h" -#include "mbedtls/error.h" - -typedef esp_err_t (*crt_bundle_attach_cb)(void *conf); - -typedef struct sslclient_context { - int socket; - mbedtls_ssl_context ssl_ctx; - mbedtls_ssl_config ssl_conf; - - mbedtls_ctr_drbg_context drbg_ctx; - mbedtls_entropy_context entropy_ctx; - - mbedtls_x509_crt ca_cert; - mbedtls_x509_crt client_cert; - mbedtls_pk_context client_key; - - crt_bundle_attach_cb bundle_attach_cb; - - unsigned long socket_timeout; - unsigned long handshake_timeout; - - int last_error; - int peek_buf; - -} sslclient_context; - -void ssl_init(sslclient_context *ssl_client); -int start_ssl_client( - sslclient_context *ssl_client, const IPAddress &ip, uint32_t port, const char *hostname, int timeout, const char *rootCABuff, bool useRootCABundle, - const char *cli_cert, const char *cli_key, const char *pskIdent, const char *psKey, bool insecure, const char **alpn_protos -); -void attach_ssl_certificate_bundle(sslclient_context *ssl_client, bool att); -int ssl_starttls_handshake(sslclient_context *ssl_client); -void stop_ssl_socket(sslclient_context *ssl_client); -int data_to_read(sslclient_context *ssl_client); -int send_ssl_data(sslclient_context *ssl_client, const uint8_t *data, size_t len); -int get_ssl_receive(sslclient_context *ssl_client, uint8_t *data, int length); -int send_net_data(sslclient_context *ssl_client, const uint8_t *data, size_t len); -int get_net_receive(sslclient_context *ssl_client, uint8_t *data, int length); -int peek_net_receive(sslclient_context *ssl_client, int timeout); -bool verify_ssl_fingerprint(sslclient_context *ssl_client, const char *fp, const char *domain_name); -bool verify_ssl_dn(sslclient_context *ssl_client, const char *domain_name); -bool get_peer_fingerprint(sslclient_context *ssl_client, uint8_t sha256[32]); -#endif diff --git a/lib/WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino b/lib/WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino deleted file mode 100644 index e8e8153..0000000 --- a/lib/WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino +++ /dev/null @@ -1,147 +0,0 @@ -/* - Copyright (c) 2015, Majenko Technologies - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: - - * * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - * * Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - - * * Neither the name of Majenko Technologies nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include -#include -#include -#include - -const char *ssid = "YourSSIDHere"; -const char *password = "YourPSKHere"; - -WebServer server(80); - -const int led = 13; - -void handleRoot() { - digitalWrite(led, 1); - char temp[400]; - int sec = millis() / 1000; - int min = sec / 60; - int hr = min / 60; - - snprintf(temp, 400, - - "\ - \ - \ - ESP32 Demo\ - \ - \ - \ -

Hello from ESP32!

\ -

Uptime: %02d:%02d:%02d

\ - \ - \ -", - - hr, min % 60, sec % 60 - ); - server.send(200, "text/html", temp); - digitalWrite(led, 0); -} - -void handleNotFound() { - digitalWrite(led, 1); - String message = "File Not Found\n\n"; - message += "URI: "; - message += server.uri(); - message += "\nMethod: "; - message += (server.method() == HTTP_GET) ? "GET" : "POST"; - message += "\nArguments: "; - message += server.args(); - message += "\n"; - - for (uint8_t i = 0; i < server.args(); i++) { - message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; - } - - server.send(404, "text/plain", message); - digitalWrite(led, 0); -} - -void setup(void) { - pinMode(led, OUTPUT); - digitalWrite(led, 0); - Serial.begin(115200); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - Serial.println(""); - - // Wait for connection - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - Serial.println(""); - Serial.print("Connected to "); - Serial.println(ssid); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - - if (MDNS.begin("esp32")) { - Serial.println("MDNS responder started"); - } - - server.on("/", handleRoot); - server.on("/test.svg", drawGraph); - server.on("/inline", []() { - server.send(200, "text/plain", "this works as well"); - }); - server.onNotFound(handleNotFound); - server.begin(); - Serial.println("HTTP server started"); -} - -void loop(void) { - server.handleClient(); - delay(2);//allow the cpu to switch to other tasks -} - -void drawGraph() { - String out = ""; - char temp[100]; - out += "\n"; - out += "\n"; - out += "\n"; - int y = rand() % 130; - for (int x = 10; x < 390; x += 10) { - int y2 = rand() % 130; - sprintf(temp, "\n", x, 140 - y, x + 10, 140 - y2); - out += temp; - y = y2; - } - out += "\n\n"; - - server.send(200, "image/svg+xml", out); -} diff --git a/lib/WebServer/examples/FSBrowser/FSBrowser.ino b/lib/WebServer/examples/FSBrowser/FSBrowser.ino deleted file mode 100644 index f33f5db..0000000 --- a/lib/WebServer/examples/FSBrowser/FSBrowser.ino +++ /dev/null @@ -1,304 +0,0 @@ -/* - FSWebServer - Example WebServer with FS backend for esp8266/esp32 - Copyright (c) 2015 Hristo Gochkov. All rights reserved. - This file is part of the WebServer library for Arduino environment. - - 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 - - upload the contents of the data folder with MkSPIFFS Tool ("ESP32 Sketch Data Upload" in Tools menu in Arduino IDE) - or you can upload the contents of a folder if you CD in that folder and run the following command: - for file in `ls -A1`; do curl -F "file=@$PWD/$file" esp32fs.local/edit; done - - access the sample web page at http://esp32fs.local - edit the page by going to http://esp32fs.local/edit -*/ -#include -#include -#include -#include - -#define FILESYSTEM SPIFFS -// You only need to format the filesystem once -#define FORMAT_FILESYSTEM false -#define DBG_OUTPUT_PORT Serial - -#if FILESYSTEM == FFat -#include -#endif -#if FILESYSTEM == SPIFFS -#include -#endif - -const char* ssid = "wifi-ssid"; -const char* password = "wifi-password"; -const char* host = "esp32fs"; -WebServer server(80); -//holds the current upload -File fsUploadFile; - -//format bytes -String formatBytes(size_t bytes) { - if (bytes < 1024) { - return String(bytes) + "B"; - } else if (bytes < (1024 * 1024)) { - return String(bytes / 1024.0) + "KB"; - } else if (bytes < (1024 * 1024 * 1024)) { - return String(bytes / 1024.0 / 1024.0) + "MB"; - } else { - return String(bytes / 1024.0 / 1024.0 / 1024.0) + "GB"; - } -} - -String getContentType(String filename) { - if (server.hasArg("download")) { - return "application/octet-stream"; - } else if (filename.endsWith(".htm")) { - return "text/html"; - } else if (filename.endsWith(".html")) { - return "text/html"; - } else if (filename.endsWith(".css")) { - return "text/css"; - } else if (filename.endsWith(".js")) { - return "application/javascript"; - } else if (filename.endsWith(".png")) { - return "image/png"; - } else if (filename.endsWith(".gif")) { - return "image/gif"; - } else if (filename.endsWith(".jpg")) { - return "image/jpeg"; - } else if (filename.endsWith(".ico")) { - return "image/x-icon"; - } else if (filename.endsWith(".xml")) { - return "text/xml"; - } else if (filename.endsWith(".pdf")) { - return "application/x-pdf"; - } else if (filename.endsWith(".zip")) { - return "application/x-zip"; - } else if (filename.endsWith(".gz")) { - return "application/x-gzip"; - } - return "text/plain"; -} - -bool exists(String path){ - bool yes = false; - File file = FILESYSTEM.open(path, "r"); - if(!file.isDirectory()){ - yes = true; - } - file.close(); - return yes; -} - -bool handleFileRead(String path) { - DBG_OUTPUT_PORT.println("handleFileRead: " + path); - if (path.endsWith("/")) { - path += "index.htm"; - } - String contentType = getContentType(path); - String pathWithGz = path + ".gz"; - if (exists(pathWithGz) || exists(path)) { - if (exists(pathWithGz)) { - path += ".gz"; - } - File file = FILESYSTEM.open(path, "r"); - server.streamFile(file, contentType); - file.close(); - return true; - } - return false; -} - -void handleFileUpload() { - if (server.uri() != "/edit") { - return; - } - HTTPUpload& upload = server.upload(); - if (upload.status == UPLOAD_FILE_START) { - String filename = upload.filename; - if (!filename.startsWith("/")) { - filename = "/" + filename; - } - DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename); - fsUploadFile = FILESYSTEM.open(filename, "w"); - filename = String(); - } else if (upload.status == UPLOAD_FILE_WRITE) { - //DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize); - if (fsUploadFile) { - fsUploadFile.write(upload.buf, upload.currentSize); - } - } else if (upload.status == UPLOAD_FILE_END) { - if (fsUploadFile) { - fsUploadFile.close(); - } - DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize); - } -} - -void handleFileDelete() { - if (server.args() == 0) { - return server.send(500, "text/plain", "BAD ARGS"); - } - String path = server.arg(0); - DBG_OUTPUT_PORT.println("handleFileDelete: " + path); - if (path == "/") { - return server.send(500, "text/plain", "BAD PATH"); - } - if (!exists(path)) { - return server.send(404, "text/plain", "FileNotFound"); - } - FILESYSTEM.remove(path); - server.send(200, "text/plain", ""); - path = String(); -} - -void handleFileCreate() { - if (server.args() == 0) { - return server.send(500, "text/plain", "BAD ARGS"); - } - String path = server.arg(0); - DBG_OUTPUT_PORT.println("handleFileCreate: " + path); - if (path == "/") { - return server.send(500, "text/plain", "BAD PATH"); - } - if (exists(path)) { - return server.send(500, "text/plain", "FILE EXISTS"); - } - File file = FILESYSTEM.open(path, "w"); - if (file) { - file.close(); - } else { - return server.send(500, "text/plain", "CREATE FAILED"); - } - server.send(200, "text/plain", ""); - path = String(); -} - -void handleFileList() { - if (!server.hasArg("dir")) { - server.send(500, "text/plain", "BAD ARGS"); - return; - } - - String path = server.arg("dir"); - DBG_OUTPUT_PORT.println("handleFileList: " + path); - - - File root = FILESYSTEM.open(path); - path = String(); - - String output = "["; - if(root.isDirectory()){ - File file = root.openNextFile(); - while(file){ - if (output != "[") { - output += ','; - } - output += "{\"type\":\""; - output += (file.isDirectory()) ? "dir" : "file"; - output += "\",\"name\":\""; - output += String(file.name()).substring(1); - output += "\"}"; - file = root.openNextFile(); - } - } - output += "]"; - server.send(200, "text/json", output); -} - -void setup(void) { - DBG_OUTPUT_PORT.begin(115200); - DBG_OUTPUT_PORT.print("\n"); - DBG_OUTPUT_PORT.setDebugOutput(true); - if (FORMAT_FILESYSTEM) FILESYSTEM.format(); - FILESYSTEM.begin(); - { - File root = FILESYSTEM.open("/"); - File file = root.openNextFile(); - while(file){ - String fileName = file.name(); - size_t fileSize = file.size(); - DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str()); - file = root.openNextFile(); - } - DBG_OUTPUT_PORT.printf("\n"); - } - - - //WIFI INIT - DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid); - if (String(WiFi.SSID()) != String(ssid)) { - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - } - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - DBG_OUTPUT_PORT.print("."); - } - DBG_OUTPUT_PORT.println(""); - DBG_OUTPUT_PORT.print("Connected! IP address: "); - DBG_OUTPUT_PORT.println(WiFi.localIP()); - - MDNS.begin(host); - DBG_OUTPUT_PORT.print("Open http://"); - DBG_OUTPUT_PORT.print(host); - DBG_OUTPUT_PORT.println(".local/edit to see the file browser"); - - - //SERVER INIT - //list directory - server.on("/list", HTTP_GET, handleFileList); - //load editor - server.on("/edit", HTTP_GET, []() { - if (!handleFileRead("/edit.htm")) { - server.send(404, "text/plain", "FileNotFound"); - } - }); - //create file - server.on("/edit", HTTP_PUT, handleFileCreate); - //delete file - server.on("/edit", HTTP_DELETE, handleFileDelete); - //first callback is called after the request has ended with all parsed arguments - //second callback handles file uploads at that location - server.on("/edit", HTTP_POST, []() { - server.send(200, "text/plain", ""); - }, handleFileUpload); - - //called when the url is not defined here - //use it to load content from FILESYSTEM - server.onNotFound([]() { - if (!handleFileRead(server.uri())) { - server.send(404, "text/plain", "FileNotFound"); - } - }); - - //get heap status, analog input value and all GPIO statuses in one json call - server.on("/all", HTTP_GET, []() { - String json = "{"; - json += "\"heap\":" + String(ESP.getFreeHeap()); - json += ", \"analog\":" + String(analogRead(A0)); - json += ", \"gpio\":" + String((uint32_t)(0)); - json += "}"; - server.send(200, "text/json", json); - json = String(); - }); - server.begin(); - DBG_OUTPUT_PORT.println("HTTP server started"); - -} - -void loop(void) { - server.handleClient(); - delay(2);//allow the cpu to switch to other tasks -} diff --git a/lib/WebServer/examples/FSBrowser/data/edit.htm.gz b/lib/WebServer/examples/FSBrowser/data/edit.htm.gz deleted file mode 100644 index 69ce414f47f4b25a70160fc4985b2bd320c9b90a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4116 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 diff --git a/lib/WebServer/examples/FSBrowser/data/graphs.js.gz b/lib/WebServer/examples/FSBrowser/data/graphs.js.gz deleted file mode 100644 index 72435445a7ef86e00fe6c66b3b71c47fdbd53a93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1971 zcmV;k2Tb@MiwFo@s$5k717~t!aAw-*8%!ep`yEM!aI@XpTRzN;M>CH`Bk93rb1y+*nfgVXr6{*O z+qWk+vpIfdbmRB&GiOD~!8-BI8jWLya*xm0`?Ns*u7@$U5b~-2FAtyY?VsHiJWiul z$g}Ci$m17Tz>sgbNBG!p6`3AIdmW5d-8e)$jYf;JJn?*ncK^L=>v4>q7o#|V#AXxo zd(ilSPVW5&A*9`%(ECk!Y+arKfe)*P?pEpRp~jzoF096?>r=Aoz97T=5)|h&-xQIu zg>l28ew8E08a6CBp`}c_5jC)_GP?DY`3f}-jd!TP&)0@aTI6>ySBJ(C^6Vjg_FCi? z4W)y8K|{N+7xu_b?5Q2tp*_aW9rTMaq?DSsrVu+rd~J}hE1ki{8Q_4DpSuf+7N=y0 z&lbmI*d0+cIw7C&8TouX>b8vuC7%rP5n-@XYJN3AXB2Qkjq~PBh|f&&!8{IOB&X(2 zH9yyb7aQ|uSdryo%OvNrl!(4VCh<(%jlKg>o+p(VLEZu+SU49}D`X?;9hV2J6#b@5xoQzr_H2o5@^1L}hMUELzT^@ZjjKOo0;;PSc-R=y16 zZ?GMY#vgK8*szEP4+cZTEn{%f2k_E z27Gde+C~f)Fg^jqZfsd{i2;6w@jVpRk!@);Z(!Q^0V_`j7DaAW9TdZ@cIt4a4szZaa^^aU29qDr;ABm46G?-=na2}Ferp=!isDL5 zZsRR-8*h`__!TcbYL6zj@e8?){|UK0{(#)d@5!y&&_(q^Zq+tjEH`wq+|b2RliLz5 zFqaQCU2wY8dG2R6eWA(3ua&TB6J>0#%pV>3Z zdyofc0!I`ut`SP$kiJ3WHi#-0<433_` z)YOv0bAAFn?dFnNRNv(ebZf$J9m~5X4;HB}|KgQLUj9wtTU0ot`o1rAx!kOPE}Fw8 zS~Q^pvJt_F1Mf4=n%)ASw`5Ik|GTcX!Dzk1YQ^WUvs*ob65M#*8Pl}n#sj~2z;Kp3 z#wt>!wBemdQe1%Fa@<6G*Tw3BO6(?)=)j$C-mo1S$t$oENe=yhLb-k<8_;yI4OzB} zRnJ39Vd(ZadQJ$E_Fo6+T+OyM?w0=_W4@|s1;9o}-pE(zujhQpQ&}rb!wtIwq}*=X z4FSKo$AySPF)Nk{6YIBX;rZfke?Eikq=c+ENp6^52>r$^5|&2ANW^K%_#LYXv7c8e z=vGa#hyxfcmO0B{UVkpiG+<$zvOo;WqJY`6)K6l6B8Kp~K2ezpCd|@8%tl_2EiSSo zFA^|v1uq>WUSe{a+%Cnisy&!<^nU5?i#QLL2@C+6O z9mvZ6hkhC(4R=nr5iF#GH6&3&)`F6DcbF}Sh6I^7IhzV|9hOnAbbP~0)_aTc?G-T zUOp0`CmZu63}>tmJ`@(Gu)SWMGnfEAsi4IU)bR<5*jr~_#!;-Z^EjQ<&VItC4B8MT z8B`^L%rDim_gH$+=1MruU~n)H8CPxfm(bCv7eR<84hDk()HUc0oPGxaFZ!1Rx^{U0 z&VK*U?e~RBheyXbhhXsb4|_d`A?>3*L0l+t&uz4z32}iM0zT**`+0@&e*in8G#?BP F000|7$0Ps% diff --git a/lib/WebServer/examples/FSBrowser/data/index.htm b/lib/WebServer/examples/FSBrowser/data/index.htm deleted file mode 100644 index 9cb560c..0000000 --- a/lib/WebServer/examples/FSBrowser/data/index.htm +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - ESP Monitor - - - - -
- - - - -
-
-
-
- - \ No newline at end of file diff --git a/lib/WebServer/examples/HelloServer/HelloServer.ino b/lib/WebServer/examples/HelloServer/HelloServer.ino deleted file mode 100644 index d807ff0..0000000 --- a/lib/WebServer/examples/HelloServer/HelloServer.ino +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include -#include - -const char* ssid = "........"; -const char* password = "........"; - -WebServer server(80); - -const int led = 13; - -void handleRoot() { - digitalWrite(led, 1); - server.send(200, "text/plain", "hello from esp32!"); - digitalWrite(led, 0); -} - -void handleNotFound() { - digitalWrite(led, 1); - String message = "File Not Found\n\n"; - message += "URI: "; - message += server.uri(); - message += "\nMethod: "; - message += (server.method() == HTTP_GET) ? "GET" : "POST"; - message += "\nArguments: "; - message += server.args(); - message += "\n"; - for (uint8_t i = 0; i < server.args(); i++) { - message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; - } - server.send(404, "text/plain", message); - digitalWrite(led, 0); -} - -void setup(void) { - pinMode(led, OUTPUT); - digitalWrite(led, 0); - Serial.begin(115200); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - Serial.println(""); - - // Wait for connection - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - Serial.println(""); - Serial.print("Connected to "); - Serial.println(ssid); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - - if (MDNS.begin("esp32")) { - Serial.println("MDNS responder started"); - } - - server.on("/", handleRoot); - - server.on("/inline", []() { - server.send(200, "text/plain", "this works as well"); - }); - - server.onNotFound(handleNotFound); - - server.begin(); - Serial.println("HTTP server started"); -} - -void loop(void) { - server.handleClient(); - delay(2);//allow the cpu to switch to other tasks -} diff --git a/lib/WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino b/lib/WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino deleted file mode 100644 index e2af955..0000000 --- a/lib/WebServer/examples/HttpAdvancedAuth/HttpAdvancedAuth.ino +++ /dev/null @@ -1,60 +0,0 @@ -/* - HTTP Advanced Authentication example - Created Mar 16, 2017 by Ahmed El-Sharnoby. - This example code is in the public domain. -*/ - -#include -#include -#include -#include - -const char* ssid = "........"; -const char* password = "........"; - -WebServer server(80); - -const char* www_username = "admin"; -const char* www_password = "esp32"; -// allows you to set the realm of authentication Default:"Login Required" -const char* www_realm = "Custom Auth Realm"; -// the Content of the HTML response in case of Unautherized Access Default:empty -String authFailResponse = "Authentication Failed"; - -void setup() { - Serial.begin(115200); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - if (WiFi.waitForConnectResult() != WL_CONNECTED) { - Serial.println("WiFi Connect Failed! Rebooting..."); - delay(1000); - ESP.restart(); - } - ArduinoOTA.begin(); - - server.on("/", []() { - if (!server.authenticate(www_username, www_password)) - //Basic Auth Method with Custom realm and Failure Response - //return server.requestAuthentication(BASIC_AUTH, www_realm, authFailResponse); - //Digest Auth Method with realm="Login Required" and empty Failure Response - //return server.requestAuthentication(DIGEST_AUTH); - //Digest Auth Method with Custom realm and empty Failure Response - //return server.requestAuthentication(DIGEST_AUTH, www_realm); - //Digest Auth Method with Custom realm and Failure Response - { - return server.requestAuthentication(DIGEST_AUTH, www_realm, authFailResponse); - } - server.send(200, "text/plain", "Login OK"); - }); - server.begin(); - - Serial.print("Open http://"); - Serial.print(WiFi.localIP()); - Serial.println("/ in your browser to see it working"); -} - -void loop() { - ArduinoOTA.handle(); - server.handleClient(); - delay(2);//allow the cpu to switch to other tasks -} diff --git a/lib/WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino b/lib/WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino deleted file mode 100644 index a370a24..0000000 --- a/lib/WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino +++ /dev/null @@ -1,42 +0,0 @@ -#include -#include -#include -#include - -const char* ssid = "........"; -const char* password = "........"; - -WebServer server(80); - -const char* www_username = "admin"; -const char* www_password = "esp32"; - -void setup() { - Serial.begin(115200); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - if (WiFi.waitForConnectResult() != WL_CONNECTED) { - Serial.println("WiFi Connect Failed! Rebooting..."); - delay(1000); - ESP.restart(); - } - ArduinoOTA.begin(); - - server.on("/", []() { - if (!server.authenticate(www_username, www_password)) { - return server.requestAuthentication(); - } - server.send(200, "text/plain", "Login OK"); - }); - server.begin(); - - Serial.print("Open http://"); - Serial.print(WiFi.localIP()); - Serial.println("/ in your browser to see it working"); -} - -void loop() { - ArduinoOTA.handle(); - server.handleClient(); - delay(2);//allow the cpu to switch to other tasks -} diff --git a/lib/WebServer/examples/PathArgServer/PathArgServer.ino b/lib/WebServer/examples/PathArgServer/PathArgServer.ino deleted file mode 100644 index 8482b2d..0000000 --- a/lib/WebServer/examples/PathArgServer/PathArgServer.ino +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include -#include -#include - -#include -#include - -const char *ssid = "........"; -const char *password = "........"; - -WebServer server(80); - -void setup(void) { - Serial.begin(9600); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - Serial.println(""); - - // Wait for connection - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - Serial.println(""); - Serial.print("Connected to "); - Serial.println(ssid); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - - if (MDNS.begin("esp32")) { - Serial.println("MDNS responder started"); - } - - server.on(F("/"), []() { - server.send(200, "text/plain", "hello from esp32!"); - }); - - server.on(UriBraces("/users/{}"), []() { - String user = server.pathArg(0); - server.send(200, "text/plain", "User: '" + user + "'"); - }); - - server.on(UriRegex("^\\/users\\/([0-9]+)\\/devices\\/([0-9]+)$"), []() { - String user = server.pathArg(0); - String device = server.pathArg(1); - server.send(200, "text/plain", "User: '" + user + "' and Device: '" + device + "'"); - }); - - server.begin(); - Serial.println("HTTP server started"); -} - -void loop(void) { - server.handleClient(); - delay(2);//allow the cpu to switch to other tasks -} diff --git a/lib/WebServer/examples/SDWebServer/SDWebServer.ino b/lib/WebServer/examples/SDWebServer/SDWebServer.ino deleted file mode 100644 index 3d0e394..0000000 --- a/lib/WebServer/examples/SDWebServer/SDWebServer.ino +++ /dev/null @@ -1,314 +0,0 @@ -/* - SDWebServer - Example WebServer with SD Card backend for esp8266 - - Copyright (c) 2015 Hristo Gochkov. All rights reserved. - This file is part of the WebServer library for Arduino environment. - - 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 - - Have a FAT Formatted SD Card connected to the SPI port of the ESP8266 - The web root is the SD Card root folder - File extensions with more than 3 charecters are not supported by the SD Library - File Names longer than 8 charecters will be truncated by the SD library, so keep filenames shorter - index.htm is the default index (works on subfolders as well) - - upload the contents of SdRoot to the root of the SDcard and access the editor by going to http://esp8266sd.local/edit - -*/ -#include -#include -#include -#include -#include -#include - -#define DBG_OUTPUT_PORT Serial - -const char* ssid = "**********"; -const char* password = "**********"; -const char* host = "esp32sd"; - -WebServer server(80); - -static bool hasSD = false; -File uploadFile; - - -void returnOK() { - server.send(200, "text/plain", ""); -} - -void returnFail(String msg) { - server.send(500, "text/plain", msg + "\r\n"); -} - -bool loadFromSdCard(String path) { - String dataType = "text/plain"; - if (path.endsWith("/")) { - path += "index.htm"; - } - - if (path.endsWith(".src")) { - path = path.substring(0, path.lastIndexOf(".")); - } else if (path.endsWith(".htm")) { - dataType = "text/html"; - } else if (path.endsWith(".css")) { - dataType = "text/css"; - } else if (path.endsWith(".js")) { - dataType = "application/javascript"; - } else if (path.endsWith(".png")) { - dataType = "image/png"; - } else if (path.endsWith(".gif")) { - dataType = "image/gif"; - } else if (path.endsWith(".jpg")) { - dataType = "image/jpeg"; - } else if (path.endsWith(".ico")) { - dataType = "image/x-icon"; - } else if (path.endsWith(".xml")) { - dataType = "text/xml"; - } else if (path.endsWith(".pdf")) { - dataType = "application/pdf"; - } else if (path.endsWith(".zip")) { - dataType = "application/zip"; - } - - File dataFile = SD.open(path.c_str()); - if (dataFile.isDirectory()) { - path += "/index.htm"; - dataType = "text/html"; - dataFile = SD.open(path.c_str()); - } - - if (!dataFile) { - return false; - } - - if (server.hasArg("download")) { - dataType = "application/octet-stream"; - } - - if (server.streamFile(dataFile, dataType) != dataFile.size()) { - DBG_OUTPUT_PORT.println("Sent less data than expected!"); - } - - dataFile.close(); - return true; -} - -void handleFileUpload() { - if (server.uri() != "/edit") { - return; - } - HTTPUpload& upload = server.upload(); - if (upload.status == UPLOAD_FILE_START) { - if (SD.exists((char *)upload.filename.c_str())) { - SD.remove((char *)upload.filename.c_str()); - } - uploadFile = SD.open(upload.filename.c_str(), FILE_WRITE); - DBG_OUTPUT_PORT.print("Upload: START, filename: "); DBG_OUTPUT_PORT.println(upload.filename); - } else if (upload.status == UPLOAD_FILE_WRITE) { - if (uploadFile) { - uploadFile.write(upload.buf, upload.currentSize); - } - DBG_OUTPUT_PORT.print("Upload: WRITE, Bytes: "); DBG_OUTPUT_PORT.println(upload.currentSize); - } else if (upload.status == UPLOAD_FILE_END) { - if (uploadFile) { - uploadFile.close(); - } - DBG_OUTPUT_PORT.print("Upload: END, Size: "); DBG_OUTPUT_PORT.println(upload.totalSize); - } -} - -void deleteRecursive(String path) { - File file = SD.open((char *)path.c_str()); - if (!file.isDirectory()) { - file.close(); - SD.remove((char *)path.c_str()); - return; - } - - file.rewindDirectory(); - while (true) { - File entry = file.openNextFile(); - if (!entry) { - break; - } - String entryPath = path + "/" + entry.name(); - if (entry.isDirectory()) { - entry.close(); - deleteRecursive(entryPath); - } else { - entry.close(); - SD.remove((char *)entryPath.c_str()); - } - yield(); - } - - SD.rmdir((char *)path.c_str()); - file.close(); -} - -void handleDelete() { - if (server.args() == 0) { - return returnFail("BAD ARGS"); - } - String path = server.arg(0); - if (path == "/" || !SD.exists((char *)path.c_str())) { - returnFail("BAD PATH"); - return; - } - deleteRecursive(path); - returnOK(); -} - -void handleCreate() { - if (server.args() == 0) { - return returnFail("BAD ARGS"); - } - String path = server.arg(0); - if (path == "/" || SD.exists((char *)path.c_str())) { - returnFail("BAD PATH"); - return; - } - - if (path.indexOf('.') > 0) { - File file = SD.open((char *)path.c_str(), FILE_WRITE); - if (file) { - file.write(0); - file.close(); - } - } else { - SD.mkdir((char *)path.c_str()); - } - returnOK(); -} - -void printDirectory() { - if (!server.hasArg("dir")) { - return returnFail("BAD ARGS"); - } - String path = server.arg("dir"); - if (path != "/" && !SD.exists((char *)path.c_str())) { - return returnFail("BAD PATH"); - } - File dir = SD.open((char *)path.c_str()); - path = String(); - if (!dir.isDirectory()) { - dir.close(); - return returnFail("NOT DIR"); - } - dir.rewindDirectory(); - server.setContentLength(CONTENT_LENGTH_UNKNOWN); - server.send(200, "text/json", ""); - WiFiClient client = server.client(); - - server.sendContent("["); - for (int cnt = 0; true; ++cnt) { - File entry = dir.openNextFile(); - if (!entry) { - break; - } - - String output; - if (cnt > 0) { - output = ','; - } - - output += "{\"type\":\""; - output += (entry.isDirectory()) ? "dir" : "file"; - output += "\",\"name\":\""; - output += entry.name(); - output += "\""; - output += "}"; - server.sendContent(output); - entry.close(); - } - server.sendContent("]"); - dir.close(); -} - -void handleNotFound() { - if (hasSD && loadFromSdCard(server.uri())) { - return; - } - String message = "SDCARD Not Detected\n\n"; - message += "URI: "; - message += server.uri(); - message += "\nMethod: "; - message += (server.method() == HTTP_GET) ? "GET" : "POST"; - message += "\nArguments: "; - message += server.args(); - message += "\n"; - for (uint8_t i = 0; i < server.args(); i++) { - message += " NAME:" + server.argName(i) + "\n VALUE:" + server.arg(i) + "\n"; - } - server.send(404, "text/plain", message); - DBG_OUTPUT_PORT.print(message); -} - -void setup(void) { - DBG_OUTPUT_PORT.begin(115200); - DBG_OUTPUT_PORT.setDebugOutput(true); - DBG_OUTPUT_PORT.print("\n"); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - DBG_OUTPUT_PORT.print("Connecting to "); - DBG_OUTPUT_PORT.println(ssid); - - // Wait for connection - uint8_t i = 0; - while (WiFi.status() != WL_CONNECTED && i++ < 20) {//wait 10 seconds - delay(500); - } - if (i == 21) { - DBG_OUTPUT_PORT.print("Could not connect to"); - DBG_OUTPUT_PORT.println(ssid); - while (1) { - delay(500); - } - } - DBG_OUTPUT_PORT.print("Connected! IP address: "); - DBG_OUTPUT_PORT.println(WiFi.localIP()); - - if (MDNS.begin(host)) { - MDNS.addService("http", "tcp", 80); - DBG_OUTPUT_PORT.println("MDNS responder started"); - DBG_OUTPUT_PORT.print("You can now connect to http://"); - DBG_OUTPUT_PORT.print(host); - DBG_OUTPUT_PORT.println(".local"); - } - - - server.on("/list", HTTP_GET, printDirectory); - server.on("/edit", HTTP_DELETE, handleDelete); - server.on("/edit", HTTP_PUT, handleCreate); - server.on("/edit", HTTP_POST, []() { - returnOK(); - }, handleFileUpload); - server.onNotFound(handleNotFound); - - server.begin(); - DBG_OUTPUT_PORT.println("HTTP server started"); - - if (SD.begin(SS)) { - DBG_OUTPUT_PORT.println("SD Card initialized."); - hasSD = true; - } -} - -void loop(void) { - server.handleClient(); - delay(2);//allow the cpu to switch to other tasks -} diff --git a/lib/WebServer/examples/SDWebServer/SdRoot/edit/index.htm b/lib/WebServer/examples/SDWebServer/SdRoot/edit/index.htm deleted file mode 100644 index f535601..0000000 --- a/lib/WebServer/examples/SDWebServer/SdRoot/edit/index.htm +++ /dev/null @@ -1,674 +0,0 @@ - - - - SD Editor - - - - - -
-
-
- - - - diff --git a/lib/WebServer/examples/SDWebServer/SdRoot/index.htm b/lib/WebServer/examples/SDWebServer/SdRoot/index.htm deleted file mode 100644 index 55fe5a6..0000000 --- a/lib/WebServer/examples/SDWebServer/SdRoot/index.htm +++ /dev/null @@ -1,22 +0,0 @@ - - - - - ESP Index - - - - -

ESP8266 Pin Functions

- - - diff --git a/lib/WebServer/examples/SDWebServer/SdRoot/pins.png b/lib/WebServer/examples/SDWebServer/SdRoot/pins.png deleted file mode 100644 index ac7fc0f9cb64d99f9f33b877798c61d02086d00b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177869 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 z 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 Nla*4EtQ9v7`9ICfW -#include -#include - -const char* ssid = "........"; -const char* password = "........"; - -WebServer server(80); - -//Check if header is present and correct -bool is_authentified() { - Serial.println("Enter is_authentified"); - if (server.hasHeader("Cookie")) { - Serial.print("Found cookie: "); - String cookie = server.header("Cookie"); - Serial.println(cookie); - if (cookie.indexOf("ESPSESSIONID=1") != -1) { - Serial.println("Authentification Successful"); - return true; - } - } - Serial.println("Authentification Failed"); - return false; -} - -//login page, also called for disconnect -void handleLogin() { - String msg; - if (server.hasHeader("Cookie")) { - Serial.print("Found cookie: "); - String cookie = server.header("Cookie"); - Serial.println(cookie); - } - if (server.hasArg("DISCONNECT")) { - Serial.println("Disconnection"); - server.sendHeader("Location", "/login"); - server.sendHeader("Cache-Control", "no-cache"); - server.sendHeader("Set-Cookie", "ESPSESSIONID=0"); - server.send(301); - return; - } - if (server.hasArg("USERNAME") && server.hasArg("PASSWORD")) { - if (server.arg("USERNAME") == "admin" && server.arg("PASSWORD") == "admin") { - server.sendHeader("Location", "/"); - server.sendHeader("Cache-Control", "no-cache"); - server.sendHeader("Set-Cookie", "ESPSESSIONID=1"); - server.send(301); - Serial.println("Log in Successful"); - return; - } - msg = "Wrong username/password! try again."; - Serial.println("Log in Failed"); - } - String content = "
To log in, please use : admin/admin
"; - content += "User:
"; - content += "Password:
"; - content += "
" + msg + "
"; - content += "You also can go
here"; - server.send(200, "text/html", content); -} - -//root page can be accessed only if authentification is ok -void handleRoot() { - Serial.println("Enter handleRoot"); - String header; - if (!is_authentified()) { - server.sendHeader("Location", "/login"); - server.sendHeader("Cache-Control", "no-cache"); - server.send(301); - return; - } - String content = "

hello, you successfully connected to esp8266!


"; - if (server.hasHeader("User-Agent")) { - content += "the user agent used is : " + server.header("User-Agent") + "

"; - } - content += "You can access this page until you disconnect"; - server.send(200, "text/html", content); -} - -//no need authentification -void handleNotFound() { - String message = "File Not Found\n\n"; - message += "URI: "; - message += server.uri(); - message += "\nMethod: "; - message += (server.method() == HTTP_GET) ? "GET" : "POST"; - message += "\nArguments: "; - message += server.args(); - message += "\n"; - for (uint8_t i = 0; i < server.args(); i++) { - message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; - } - server.send(404, "text/plain", message); -} - -void setup(void) { - Serial.begin(115200); - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - Serial.println(""); - - // Wait for connection - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - Serial.println(""); - Serial.print("Connected to "); - Serial.println(ssid); - Serial.print("IP address: "); - Serial.println(WiFi.localIP()); - - - server.on("/", handleRoot); - server.on("/login", handleLogin); - server.on("/inline", []() { - server.send(200, "text/plain", "this works without need of authentification"); - }); - - server.onNotFound(handleNotFound); - //here the list of headers to be recorded - const char * headerkeys[] = {"User-Agent", "Cookie"} ; - size_t headerkeyssize = sizeof(headerkeys) / sizeof(char*); - //ask server to track these headers - server.collectHeaders(headerkeys, headerkeyssize); - server.begin(); - Serial.println("HTTP server started"); -} - -void loop(void) { - server.handleClient(); - delay(2);//allow the cpu to switch to other tasks -} diff --git a/lib/WebServer/examples/WebUpdate/WebUpdate.ino b/lib/WebServer/examples/WebUpdate/WebUpdate.ino deleted file mode 100644 index a3ae1f8..0000000 --- a/lib/WebServer/examples/WebUpdate/WebUpdate.ino +++ /dev/null @@ -1,69 +0,0 @@ -/* - To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update -*/ - -#include -#include -#include -#include -#include - -const char* host = "esp32-webupdate"; -const char* ssid = "........"; -const char* password = "........"; - -WebServer server(80); -const char* serverIndex = "
"; - -void setup(void) { - Serial.begin(115200); - Serial.println(); - Serial.println("Booting Sketch..."); - WiFi.mode(WIFI_AP_STA); - WiFi.begin(ssid, password); - if (WiFi.waitForConnectResult() == WL_CONNECTED) { - MDNS.begin(host); - server.on("/", HTTP_GET, []() { - server.sendHeader("Connection", "close"); - server.send(200, "text/html", serverIndex); - }); - server.on("/update", HTTP_POST, []() { - server.sendHeader("Connection", "close"); - server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); - ESP.restart(); - }, []() { - HTTPUpload& upload = server.upload(); - if (upload.status == UPLOAD_FILE_START) { - Serial.setDebugOutput(true); - Serial.printf("Update: %s\n", upload.filename.c_str()); - if (!Update.begin()) { //start with max available size - Update.printError(Serial); - } - } else if (upload.status == UPLOAD_FILE_WRITE) { - if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { - Update.printError(Serial); - } - } else if (upload.status == UPLOAD_FILE_END) { - if (Update.end(true)) { //true to set the size to the current progress - Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); - } else { - Update.printError(Serial); - } - Serial.setDebugOutput(false); - } else { - Serial.printf("Update Failed Unexpectedly (likely broken connection): status=%d\n", upload.status); - } - }); - server.begin(); - MDNS.addService("http", "tcp", 80); - - Serial.printf("Ready! Open http://%s.local in your browser\n", host); - } else { - Serial.println("WiFi Failed"); - } -} - -void loop(void) { - server.handleClient(); - delay(2);//allow the cpu to switch to other tasks -} diff --git a/lib/WebServer/keywords.txt b/lib/WebServer/keywords.txt deleted file mode 100644 index df20ff7..0000000 --- a/lib/WebServer/keywords.txt +++ /dev/null @@ -1,38 +0,0 @@ -####################################### -# Syntax Coloring Map For Ultrasound -####################################### - -####################################### -# Datatypes (KEYWORD1) -####################################### - -WebServer KEYWORD1 -WebServerSecure KEYWORD1 -HTTPMethod KEYWORD1 - -####################################### -# Methods and Functions (KEYWORD2) -####################################### - -begin KEYWORD2 -handleClient KEYWORD2 -on KEYWORD2 -addHandler KEYWORD2 -uri KEYWORD2 -method KEYWORD2 -client KEYWORD2 -send KEYWORD2 -arg KEYWORD2 -argName KEYWORD2 -args KEYWORD2 -hasArg KEYWORD2 -onNotFound KEYWORD2 - -####################################### -# Constants (LITERAL1) -####################################### - -HTTP_GET LITERAL1 -HTTP_POST LITERAL1 -HTTP_ANY LITERAL1 -CONTENT_LENGTH_UNKNOWN LITERAL1 diff --git a/lib/WebServer/library.properties b/lib/WebServer/library.properties deleted file mode 100644 index a2ac5f5..0000000 --- a/lib/WebServer/library.properties +++ /dev/null @@ -1,9 +0,0 @@ -name=WebServer -version=1.0 -author=Ivan Grokhotkov -maintainer=Ivan Grokhtkov -sentence=Simple web server library -paragraph=The library supports HTTP GET and POST requests, provides argument parsing, handles one client at a time. -category=Communication -url= -architectures=esp32 diff --git a/lib/WebServer/src/EthClient.h b/lib/WebServer/src/EthClient.h deleted file mode 100644 index c5c46c3..0000000 --- a/lib/WebServer/src/EthClient.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include - -class EthClient -{ -public: - virtual uint8_t connected() = 0; - virtual int available() = 0; - virtual unsigned long getTimeout(void) = 0; - virtual int setTimeout(uint32_t seconds) = 0; - virtual int read() = 0; - virtual size_t write(const char *buffer, size_t size) = 0; - virtual size_t write_P(PGM_P buf, size_t size) = 0; - virtual size_t write(Stream &stream) = 0; - virtual String readStringUntil(char terminator) = 0; - virtual size_t readBytes(char *buffer, size_t length) = 0; - virtual IPAddress localIP() = 0; - virtual void stop() = 0; - virtual void flush() = 0; -}; diff --git a/lib/WebServer/src/EthServer.h b/lib/WebServer/src/EthServer.h deleted file mode 100644 index 3f6f4c0..0000000 --- a/lib/WebServer/src/EthServer.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include -#include "EthClient.h" - -class EthServer -{ -public: - EthServer(IPAddress address, int port) { } - - EthServer(int port) { } - - virtual EthClient* available() = 0; - virtual void discardClient() = 0; - - virtual void close() = 0; - virtual void begin(const int port = 80) = 0; - virtual void setNoDelay(const bool value) = 0; -}; \ No newline at end of file diff --git a/lib/WebServer/src/HTTP_Method.h b/lib/WebServer/src/HTTP_Method.h deleted file mode 100644 index 66d53bf..0000000 --- a/lib/WebServer/src/HTTP_Method.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef _HTTP_Method_H_ -#define _HTTP_Method_H_ - -#include "http_parser.h" - -typedef enum http_method HTTPMethod; -#define HTTP_ANY (HTTPMethod)(255) - -#endif /* _HTTP_Method_H_ */ diff --git a/lib/WebServer/src/Parsing.cpp b/lib/WebServer/src/Parsing.cpp deleted file mode 100644 index 9145244..0000000 --- a/lib/WebServer/src/Parsing.cpp +++ /dev/null @@ -1,589 +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 -#include -#include "WiFiServer.h" -#include "WiFiClient.h" -#include "WebServer.h" -#include "detail/mimetable.h" - -#ifndef WEBSERVER_MAX_POST_ARGS -#define WEBSERVER_MAX_POST_ARGS 32 -#endif - -#define __STR(a) #a -#define _STR(a) __STR(a) -const char * _http_method_str[] = { -#define XX(num, name, string) _STR(name), - HTTP_METHOD_MAP(XX) -#undef XX -}; - -static const char Content_Type[] PROGMEM = "Content-Type"; -static const char filename[] PROGMEM = "filename"; - -static char* readBytesWithTimeout(EthClient* 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(EthClient* 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) { - log_e("Invalid request: %s", req.c_str()); - 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_ANY; - size_t num_methods = sizeof(_http_method_str) / sizeof(const char *); - for (size_t i=0; inext()) { - 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()); - - log_v("headerName: %s", headerName.c_str()); - log_v("headerValue: %s", headerValue.c_str()); - - if (headerName.equalsIgnoreCase(FPSTR(Content_Type))){ - using namespace mime; - if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))){ - isForm = false; - } else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))){ - isForm = false; - isEncoded = true; - } else if (headerValue.startsWith(F("multipart/"))){ - boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1); - boundaryStr.replace("\"",""); - isForm = true; - } - } else if (headerName.equalsIgnoreCase(F("Content-Length"))){ - contentLength = headerValue.toInt(); - } else if (headerName.equalsIgnoreCase(F("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(isEncoded){ - //url encoded form - if (searchStr != "") searchStr += '&'; - searchStr += plainBuf; - } - _parseArguments(searchStr); - if(!isEncoded){ - //plain post json or other data - RequestArgument& arg = _currentArgs[_currentArgCount++]; - arg.key = F("plain"); - arg.value = String(plainBuf); - } - - log_v("Plain: %s", plainBuf); - 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()); - - log_v("headerName: %s", headerName.c_str()); - log_v("headerValue: %s", headerValue.c_str()); - - if (headerName.equalsIgnoreCase("Host")){ - _hostHeader = headerValue; - } - } - _parseArguments(searchStr); - } - client->flush(); - - log_v("Request: %s", url.c_str()); - log_v(" Arguments: %s", searchStr.c_str()); - - 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) { - log_v("args: %s", data.c_str()); - 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; - } - log_v("args count: %d", _currentArgCount); - - _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); - log_v("pos %d =@%d &@%d", pos, equal_sign_index, next_arg_index); - if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) { - log_e("arg missing value: %d", iarg); - if (next_arg_index == -1) - break; - pos = next_arg_index + 1; - continue; - } - RequestArgument& arg = _currentArgs[iarg]; - arg.key = urlDecode(data.substring(pos, equal_sign_index)); - arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index)); - log_v("arg %d key: %s value: %s", iarg, arg.key.c_str(), arg.value.c_str()); - ++iarg; - if (next_arg_index == -1) - break; - pos = next_arg_index + 1; - } - _currentArgCount = iarg; - log_v("args count: %d", _currentArgCount); - -} - -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; -} - -int WebServer::_uploadReadByte(EthClient* client){ - int res = client->read(); - if(res < 0) { - // keep trying until you either read a valid byte or timeout - unsigned long startMillis = millis(); - long timeoutIntervalMillis = client->getTimeout(); - boolean timedOut = false; - for(;;) { - if (!client->connected()) return -1; - // loosely modeled after blinkWithoutDelay pattern - while(!timedOut && !client->available() && client->connected()){ - delay(2); - timedOut = millis() - startMillis >= timeoutIntervalMillis; - } - - res = client->read(); - if(res >= 0) { - return res; // exit on a valid read - } - // NOTE: it is possible to get here and have all of the following - // assertions hold true - // - // -- client->available() > 0 - // -- client->connected == true - // -- res == -1 - // - // a simple retry strategy overcomes this which is to say the - // assertion is not permanent, but the reason that this works - // is elusive, and possibly indicative of a more subtle underlying - // issue - - timedOut = millis() - startMillis >= timeoutIntervalMillis; - if(timedOut) { - return res; // exit on a timeout - } - } - } - - return res; -} - -bool WebServer::_parseForm(EthClient* client, String boundary, uint32_t len){ - (void) len; - log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len); - 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)){ - if(_postArgs) delete[] _postArgs; - _postArgs = new RequestArgument[WEBSERVER_MAX_POST_ARGS]; - _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(F("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; - log_v("PostArg FileName: %s",argFilename.c_str()); - //use GET to set the filename if uploading using blob - if (argFilename == F("blob") && hasArg(FPSTR(filename))) - argFilename = arg(FPSTR(filename)); - } - log_v("PostArg Name: %s", argName.c_str()); - using namespace mime; - argType = FPSTR(mimeTable[txt].mimeType); - line = client->readStringUntil('\r'); - client->readStringUntil('\n'); - if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))){ - argType = line.substring(line.indexOf(':')+2); - //skip next line - client->readStringUntil('\r'); - client->readStringUntil('\n'); - } - log_v("PostArg Type: %s", argType.c_str()); - if (!argIsFile){ - while(1){ - line = client->readStringUntil('\r'); - client->readStringUntil('\n'); - if (line.startsWith("--"+boundary)) break; - if (argValue.length() > 0) argValue += "\n"; - argValue += line; - } - log_v("PostArg Value: %s", argValue.c_str()); - - RequestArgument& arg = _postArgs[_postArgsLen++]; - arg.key = argName; - arg.value = argValue; - - if (line == ("--"+boundary+"--")){ - log_v("Done Parsing POST"); - break; - } else if (_postArgsLen >= WEBSERVER_MAX_POST_ARGS) { - log_e("Too many PostArgs (max: %d) in request.", WEBSERVER_MAX_POST_ARGS); - return false; - } - } else { - _currentUpload.reset(new HTTPUpload()); - _currentUpload->status = UPLOAD_FILE_START; - _currentUpload->name = argName; - _currentUpload->filename = argFilename; - _currentUpload->type = argType; - _currentUpload->totalSize = 0; - _currentUpload->currentSize = 0; - log_v("Start File: %s Type: %s", _currentUpload->filename.c_str(), _currentUpload->type.c_str()); - if(_currentHandler && _currentHandler->canUpload(_currentUri)) - _currentHandler->upload(*this, _currentUri, *_currentUpload); - _currentUpload->status = UPLOAD_FILE_WRITE; - int argByte = _uploadReadByte(client); -readfile: - - while(argByte != 0x0D){ - if(argByte < 0) return _parseFormUploadAborted(); - _uploadWriteByte(argByte); - argByte = _uploadReadByte(client); - } - - argByte = _uploadReadByte(client); - if(argByte < 0) return _parseFormUploadAborted(); - if (argByte == 0x0A){ - argByte = _uploadReadByte(client); - if(argByte < 0) return _parseFormUploadAborted(); - if ((char)argByte != '-'){ - //continue reading the file - _uploadWriteByte(0x0D); - _uploadWriteByte(0x0A); - goto readfile; - } else { - argByte = _uploadReadByte(client); - if(argByte < 0) return _parseFormUploadAborted(); - if ((char)argByte != '-'){ - //continue reading the file - _uploadWriteByte(0x0D); - _uploadWriteByte(0x0A); - _uploadWriteByte((uint8_t)('-')); - goto readfile; - } - } - - uint8_t endBuf[boundary.length()]; - uint32_t i = 0; - while(i < boundary.length()){ - argByte = _uploadReadByte(client); - if(argByte < 0) return _parseFormUploadAborted(); - if ((char)argByte == 0x0D){ - _uploadWriteByte(0x0D); - _uploadWriteByte(0x0A); - _uploadWriteByte((uint8_t)('-')); - _uploadWriteByte((uint8_t)('-')); - uint32_t j = 0; - while(j < i){ - _uploadWriteByte(endBuf[j++]); - } - goto readfile; - } - endBuf[i++] = (uint8_t)argByte; - } - - 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); - log_v("End File: %s Type: %s Size: %d", _currentUpload->filename.c_str(), _currentUpload->type.c_str(), _currentUpload->totalSize); - line = client->readStringUntil(0x0D); - client->readStringUntil(0x0A); - if (line == "--"){ - log_v("Done Parsing POST"); - 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 = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount)?(WEBSERVER_MAX_POST_ARGS - _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; - _postArgs=nullptr; - _postArgsLen = 0; - } - return true; - } - log_e("Error: line: %s", line.c_str()); - 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; -} diff --git a/lib/WebServer/src/Uri.h b/lib/WebServer/src/Uri.h deleted file mode 100644 index d924a0b..0000000 --- a/lib/WebServer/src/Uri.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef URI_H -#define URI_H - -#include -#include - -class Uri { - - protected: - const String _uri; - - public: - Uri(const char *uri) : _uri(uri) {} - Uri(const String &uri) : _uri(uri) {} - Uri(const __FlashStringHelper *uri) : _uri(String(uri)) {} - virtual ~Uri() {} - - virtual Uri* clone() const { - return new Uri(_uri); - }; - - virtual void initPathArgs(__attribute__((unused)) std::vector &pathArgs) {} - - virtual bool canHandle(const String &requestUri, __attribute__((unused)) std::vector &pathArgs) { - return _uri == requestUri; - } -}; - -#endif diff --git a/lib/WebServer/src/WebServer.cpp b/lib/WebServer/src/WebServer.cpp deleted file mode 100644 index eed640b..0000000 --- a/lib/WebServer/src/WebServer.cpp +++ /dev/null @@ -1,705 +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 -#include -#include -#include "WiFiClient.h" -#include "WebServer.h" -#ifdef WEBSERVER_ENABLE_STATIC_CONTENT - #include "FS.h" -#endif -#include "detail/RequestHandlersImpl.h" -#include "mbedtls/md5.h" - - -static const char AUTHORIZATION_HEADER[] = "Authorization"; -static const char qop_auth[] PROGMEM = "qop=auth"; -static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\""; -static const char WWW_Authenticate[] = "WWW-Authenticate"; -static const char Content_Length[] = "Content-Length"; - - -//WebServer::WebServer(IPAddress addr, int port) -WebServer::WebServer(EthServer* server) -: _corsEnabled(false) -, _server(server) -, _currentMethod(HTTP_ANY) -, _currentVersion(0) -, _currentStatus(HC_NONE) -, _statusChange(0) -, _nullDelay(true) -, _currentHandler(nullptr) -, _firstHandler(nullptr) -, _lastHandler(nullptr) -, _currentArgCount(0) -, _currentArgs(nullptr) -, _postArgsLen(0) -, _postArgs(nullptr) -, _headerKeysCount(0) -, _currentHeaders(nullptr) -, _contentLength(0) -, _chunked(false) -{ -} - -WebServer::~WebServer() { - _server->close(); - if (_currentHeaders) - delete[]_currentHeaders; - RequestHandler* handler = _firstHandler; - while (handler) { - RequestHandler* next = handler->next(); - delete handler; - handler = next; - } - if(_server != nullptr) - { - delete _server; - _server = nullptr; - } -} - -void WebServer::begin() { - close(); - _server->begin(); - _server->setNoDelay(true); -} - -void WebServer::begin(uint16_t port) { - close(); - _server->begin(port); - _server->setNoDelay(true); -} - -String WebServer::_extractParam(String& authReq,const String& param,const char delimit){ - int _begin = authReq.indexOf(param); - if (_begin == -1) - return ""; - return authReq.substring(_begin+param.length(),authReq.indexOf(delimit,_begin+param.length())); -} - -static String md5str(String &in){ - char out[33] = {0}; - mbedtls_md5_context _ctx; - uint8_t i; - uint8_t * _buf = (uint8_t*)malloc(16); - if(_buf == NULL) - return String(out); - memset(_buf, 0x00, 16); - mbedtls_md5_init(&_ctx); - #if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)) - mbedtls_md5_starts_ret(&_ctx); - mbedtls_md5_update_ret(&_ctx, (const uint8_t *)in.c_str(), in.length()); - mbedtls_md5_finish_ret(&_ctx, _buf); - #else - mbedtls_md5_starts(&_ctx); - mbedtls_md5_update(&_ctx, (const uint8_t *)in.c_str(), in.length()); - mbedtls_md5_finish(&_ctx, _buf); - #endif - for(i = 0; i < 16; i++) { - sprintf(out + (i * 2), "%02x", _buf[i]); - } - out[32] = 0; - free(_buf); - return String(out); -} - -bool WebServer::authenticate(const char * username, const char * password){ - if(hasHeader(FPSTR(AUTHORIZATION_HEADER))) { - String authReq = header(FPSTR(AUTHORIZATION_HEADER)); - if(authReq.startsWith(F("Basic"))){ - authReq = authReq.substring(6); - authReq.trim(); - char toencodeLen = strlen(username)+strlen(password)+1; - char *toencode = new char[toencodeLen + 1]; - if(toencode == NULL){ - authReq = ""; - return false; - } - char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; - if(encoded == NULL){ - authReq = ""; - delete[] toencode; - return false; - } - sprintf(toencode, "%s:%s", username, password); - if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equalsConstantTime(encoded)) { - authReq = ""; - delete[] toencode; - delete[] encoded; - return true; - } - delete[] toencode; - delete[] encoded; - } else if(authReq.startsWith(F("Digest"))) { - authReq = authReq.substring(7); - log_v("%s", authReq.c_str()); - String _username = _extractParam(authReq,F("username=\""),'\"'); - if(!_username.length() || _username != String(username)) { - authReq = ""; - return false; - } - // extracting required parameters for RFC 2069 simpler Digest - String _realm = _extractParam(authReq, F("realm=\""),'\"'); - String _nonce = _extractParam(authReq, F("nonce=\""),'\"'); - String _uri = _extractParam(authReq, F("uri=\""),'\"'); - String _response = _extractParam(authReq, F("response=\""),'\"'); - String _opaque = _extractParam(authReq, F("opaque=\""),'\"'); - - if((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) { - authReq = ""; - return false; - } - if((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) { - authReq = ""; - return false; - } - // parameters for the RFC 2617 newer Digest - String _nc,_cnonce; - if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { - _nc = _extractParam(authReq, F("nc="), ','); - _cnonce = _extractParam(authReq, F("cnonce=\""),'\"'); - } - String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password)); - log_v("Hash of user:realm:pass=%s", _H1.c_str()); - String _H2 = ""; - if(_currentMethod == HTTP_GET){ - _H2 = md5str(String(F("GET:")) + _uri); - }else if(_currentMethod == HTTP_POST){ - _H2 = md5str(String(F("POST:")) + _uri); - }else if(_currentMethod == HTTP_PUT){ - _H2 = md5str(String(F("PUT:")) + _uri); - }else if(_currentMethod == HTTP_DELETE){ - _H2 = md5str(String(F("DELETE:")) + _uri); - }else{ - _H2 = md5str(String(F("GET:")) + _uri); - } - log_v("Hash of GET:uri=%s", _H2.c_str()); - String _responsecheck = ""; - if(authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) { - _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2); - } else { - _responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2); - } - log_v("The Proper response=%s", _responsecheck.c_str()); - if(_response == _responsecheck){ - authReq = ""; - return true; - } - } - authReq = ""; - } - return false; -} - -String WebServer::_getRandomHexString() { - char buffer[33]; // buffer to hold 32 Hex Digit + /0 - int i; - for(i = 0; i < 4; i++) { - sprintf (buffer + (i*8), "%08x", esp_random()); - } - return String(buffer); -} - -void WebServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, const String& authFailMsg) { - if(realm == NULL) { - _srealm = String(F("Login Required")); - } else { - _srealm = String(realm); - } - if(mode == BASIC_AUTH) { - sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\""))); - } else { - _snonce=_getRandomHexString(); - _sopaque=_getRandomHexString(); - sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Digest realm=\"")) +_srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String(F("\""))); - } - using namespace mime; - send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg); -} - -void WebServer::on(const Uri &uri, WebServer::THandlerFunction handler) { - on(uri, HTTP_ANY, handler); -} - -void WebServer::on(const Uri &uri, HTTPMethod method, WebServer::THandlerFunction fn) { - on(uri, method, fn, _fileUploadHandler); -} - -void WebServer::on(const Uri &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; - } -} - -#ifdef WEBSERVER_ENABLE_STATIC_CONTENT -void WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) { - _addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header)); -} -#endif - -void WebServer::handleClient() { - if (_currentStatus == HC_NONE) { - EthClient* client = _server->available(); - if (!client->connected()) { - if (_nullDelay) { - delay(1); - } - return; - } - log_v("New client"); - - _currentClient = client; - _currentStatus = HC_WAIT_READ; - _statusChange = millis(); - } - - bool keepCurrentClient = false; - bool callYield = false; - - if (_currentClient->connected()) { - switch (_currentStatus) { - case HC_NONE: - // No-op to avoid C++ compiler warning - break; - case HC_WAIT_READ: - // Wait for data from client to become available - if (_currentClient->available()) { - if (_parseRequest(_currentClient)) { - // because HTTP_MAX_SEND_WAIT is expressed in milliseconds, - // it must be divided by 1000 - _currentClient->setTimeout(HTTP_MAX_SEND_WAIT / 1000); - - _contentLength = CONTENT_LENGTH_NOT_SET; - _handleRequest(); - } - } else { // !_currentClient->available() - if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) { - keepCurrentClient = true; - } - callYield = true; - } - break; - case HC_WAIT_CLOSE: - // Wait for client to close the connection - if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) { - keepCurrentClient = true; - callYield = true; - } - } - } - - // TODO - if (!keepCurrentClient) { - _server->discardClient(); - _currentClient = _server->available(); - _currentStatus = HC_NONE; - _currentUpload.reset(); - } - - if (callYield) { - yield(); - } -} - -void WebServer::close() { - _server->close(); - _currentStatus = HC_NONE; - if(!_headerKeysCount) - collectHeaders(0, 0); -} - -void WebServer::stop() { - close(); -} - -void WebServer::sendHeader(const String& name, const String& value, bool first) { - String headerLine = name; - headerLine += F(": "); - headerLine += value; - headerLine += "\r\n"; - - if (first) { - _responseHeaders = headerLine + _responseHeaders; - } - else { - _responseHeaders += headerLine; - } -} - -void WebServer::setContentLength(const size_t contentLength) { - _contentLength = contentLength; -} - -void WebServer::enableDelay(boolean value) { - _nullDelay = value; -} - -void WebServer::enableCORS(boolean value) { - _corsEnabled = value; -} - -void WebServer::enableCrossOrigin(boolean value) { - enableCORS(value); -} - -void WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) { - response = String(F("HTTP/1.")) + String(_currentVersion) + ' '; - response += String(code); - response += ' '; - response += _responseCodeToString(code); - response += "\r\n"; - - using namespace mime; - if (!content_type) - content_type = mimeTable[html].mimeType; - - sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true); - if (_contentLength == CONTENT_LENGTH_NOT_SET) { - sendHeader(String(FPSTR(Content_Length)), String(contentLength)); - } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) { - sendHeader(String(FPSTR(Content_Length)), String(_contentLength)); - } else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client - //let's do chunked - _chunked = true; - sendHeader(String(F("Accept-Ranges")),String(F("none"))); - sendHeader(String(F("Transfer-Encoding")),String(F("chunked"))); - } - if (_corsEnabled) { - sendHeader(String(FPSTR("Access-Control-Allow-Origin")), String("*")); - sendHeader(String(FPSTR("Access-Control-Allow-Methods")), String("*")); - sendHeader(String(FPSTR("Access-Control-Allow-Headers")), String("*")); - } - sendHeader(String(F("Connection")), String(F("close"))); - - response += _responseHeaders; - response += "\r\n"; - _responseHeaders = ""; -} - -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()); - _currentClientWrite(header.c_str(), header.length()); - if(content.length()) - sendContent(content); -} - -void WebServer::send(int code, const char *content_type, const char *content, size_t contentLength) -{ - String header; - _prepareHeader(header, code, content_type, contentLength); - _currentClientWrite(header.c_str(), header.length()); - if(contentLength) - sendContent(content, contentLength); -} - -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); - _currentClientWrite(header.c_str(), header.length()); - sendContent_P(content); -} - -void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) { - _chunked = true; - 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) { - sendContent(content.c_str(), content.length()); -} - -void WebServer::sendContent(const char* content, size_t contentLength) { - const char * footer = "\r\n"; - if(_chunked) { - char * chunkSize = (char *)malloc(11); - if(chunkSize){ - sprintf(chunkSize, "%x%s", contentLength, footer); - _currentClientWrite(chunkSize, strlen(chunkSize)); - free(chunkSize); - } - } - _currentClientWrite(content, contentLength); - if(_chunked){ - _currentClient->write(footer, 2); - if (contentLength == 0) { - _chunked = false; - } - } -} - -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); - _currentClientWrite(chunkSize, strlen(chunkSize)); - free(chunkSize); - } - } - _currentClientWrite_P(content, size); - if(_chunked){ - _currentClient->write(footer, 2); - if (size == 0) { - _chunked = false; - } - } -} - - -void WebServer::_streamFileCore(const size_t fileSize, const String & fileName, const String & contentType) -{ - using namespace mime; - setContentLength(fileSize); - if (fileName.endsWith(String(FPSTR(mimeTable[gz].endsWith))) && - contentType != String(FPSTR(mimeTable[gz].mimeType)) && - contentType != String(FPSTR(mimeTable[none].mimeType))) { - sendHeader(F("Content-Encoding"), F("gzip")); - } - send(200, contentType, ""); -} - -String WebServer::pathArg(unsigned int i) { - if (_currentHandler != nullptr) - return _currentHandler->pathArg(i); - return ""; -} - -String WebServer::arg(String name) { - for (int j = 0; j < _postArgsLen; ++j) { - if ( _postArgs[j].key == name ) - return _postArgs[j].value; - } - for (int i = 0; i < _currentArgCount; ++i) { - if ( _currentArgs[i].key == name ) - return _currentArgs[i].value; - } - return ""; -} - -String WebServer::arg(int i) { - if (i < _currentArgCount) - return _currentArgs[i].value; - return ""; -} - -String WebServer::argName(int i) { - if (i < _currentArgCount) - return _currentArgs[i].key; - return ""; -} - -int WebServer::args() { - return _currentArgCount; -} - -bool WebServer::hasArg(String name) { - for (int j = 0; j < _postArgsLen; ++j) { - if (_postArgs[j].key == name) - return true; - } - 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 ""; -} - -void WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) { - _headerKeysCount = headerKeysCount + 1; - if (_currentHeaders) - delete[]_currentHeaders; - _currentHeaders = new RequestArgument[_headerKeysCount]; - _currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER); - for (int i = 1; i < _headerKeysCount; i++){ - _currentHeaders[i].key = headerKeys[i-1]; - } -} - -String WebServer::header(int i) { - if (i < _headerKeysCount) - return _currentHeaders[i].value; - return ""; -} - -String WebServer::headerName(int i) { - if (i < _headerKeysCount) - return _currentHeaders[i].key; - return ""; -} - -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){ - log_e("request handler not found"); - } - else { - handled = _currentHandler->handle(*this, _currentMethod, _currentUri); - if (!handled) { - log_e("request handler failed to handle request"); - } - } - if (!handled && _notFoundHandler) { - _notFoundHandler(); - handled = true; - } - if (!handled) { - using namespace mime; - send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri); - handled = true; - } - if (handled) { - _finalizeResponse(); - } - _currentUri = ""; -} - - -void WebServer::_finalizeResponse() { - if (_chunked) { - sendContent(""); - } -} - -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 F(""); - } -} diff --git a/lib/WebServer/src/WebServer.h b/lib/WebServer/src/WebServer.h deleted file mode 100644 index bf34baa..0000000 --- a/lib/WebServer/src/WebServer.h +++ /dev/null @@ -1,215 +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 -#include -#include -#include "HTTP_Method.h" -#include "Uri.h" - -enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END, - UPLOAD_FILE_ABORTED }; -enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE }; -enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH }; - -#define HTTP_DOWNLOAD_UNIT_SIZE 1436 - -#ifndef HTTP_UPLOAD_BUFLEN -#define HTTP_UPLOAD_BUFLEN 1436 -#endif - -#define HTTP_MAX_DATA_WAIT 5000 //ms to wait for the client to send the request -#define HTTP_MAX_POST_WAIT 5000 //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" -#include -#include - -namespace fs { -class FS; -} - -class WebServer -{ -public: - WebServer(EthServer* server); - virtual ~WebServer(); - - virtual void begin(); - virtual void begin(uint16_t port); - virtual void handleClient(); - - virtual void close(); - void stop(); - - bool authenticate(const char * username, const char * password); - void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") ); - - typedef std::function THandlerFunction; - void on(const Uri &uri, THandlerFunction handler); - void on(const Uri &uri, HTTPMethod method, THandlerFunction fn); - void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn); - void addHandler(RequestHandler* handler); -#ifdef WEBSERVER_ENABLE_STATIC_CONTENT - void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL ); -#endif - 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; } - virtual EthClient* client() { return _currentClient; } - HTTPUpload& upload() { return *_currentUpload; } - - String pathArg(unsigned int i); // get request path argument by number - 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(const char* headerKeys[], const size_t headerKeysCount); // 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, const char* content_type, const char* content, size_t contentLength); - 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 enableDelay(boolean value); - void enableCORS(boolean value = true); - void enableCrossOrigin(boolean value = true); - - void setContentLength(const size_t contentLength); - void sendHeader(const String& name, const String& value, bool first = false); - void sendContent(const String& content); - void sendContent(const char* content, size_t contentLength); - void sendContent_P(PGM_P content); - void sendContent_P(PGM_P content, size_t size); - - static String urlDecode(const String& text); - - template - size_t streamFile(T &file, const String& contentType) { - _streamFileCore(file.size(), file.name(), contentType); - return _currentClient->write(file); - } - -protected: - virtual size_t _currentClientWrite(const char* b, size_t l) { return _currentClient->write( b, l ); } - virtual size_t _currentClientWrite_P(PGM_P b, size_t l) { return _currentClient->write_P( b, l ); } - void _addRequestHandler(RequestHandler* handler); - void _handleRequest(); - void _finalizeResponse(); - bool _parseRequest(EthClient* client); - void _parseArguments(String data); - static String _responseCodeToString(int code); - bool _parseForm(EthClient* client, String boundary, uint32_t len); - bool _parseFormUploadAborted(); - void _uploadWriteByte(uint8_t b); - int _uploadReadByte(EthClient* client); - void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength); - bool _collectHeader(const char* headerName, const char* headerValue); - - void _streamFileCore(const size_t fileSize, const String & fileName, const String & contentType); - - String _getRandomHexString(); - // for extracting Auth parameters - String _extractParam(String& authReq,const String& param,const char delimit = '"'); - - struct RequestArgument { - String key; - String value; - }; - - boolean _corsEnabled; - EthServer* _server; - - EthClient* _currentClient = nullptr; - HTTPMethod _currentMethod; - String _currentUri; - uint8_t _currentVersion; - HTTPClientStatus _currentStatus; - unsigned long _statusChange; - boolean _nullDelay; - - RequestHandler* _currentHandler; - RequestHandler* _firstHandler; - RequestHandler* _lastHandler; - THandlerFunction _notFoundHandler; - THandlerFunction _fileUploadHandler; - - int _currentArgCount; - RequestArgument* _currentArgs; - int _postArgsLen; - RequestArgument* _postArgs; - - std::unique_ptr _currentUpload; - - int _headerKeysCount; - RequestArgument* _currentHeaders; - size_t _contentLength; - String _responseHeaders; - - String _hostHeader; - bool _chunked; - - String _snonce; // Store noance and opaque for future comparison - String _sopaque; - String _srealm; // Store the Auth realm between Calls - -}; - - -#endif //ESP8266WEBSERVER_H diff --git a/lib/WebServer/src/detail/RequestHandler.h b/lib/WebServer/src/detail/RequestHandler.h deleted file mode 100644 index 871ae5c..0000000 --- a/lib/WebServer/src/detail/RequestHandler.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef REQUESTHANDLER_H -#define REQUESTHANDLER_H - -#include -#include - -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; - -protected: - std::vector pathArgs; - -public: - const String& pathArg(unsigned int i) { - assert(i < pathArgs.size()); - return pathArgs[i]; - } -}; - -#endif //REQUESTHANDLER_H diff --git a/lib/WebServer/src/detail/RequestHandlersImpl.h b/lib/WebServer/src/detail/RequestHandlersImpl.h deleted file mode 100644 index 6dd45d9..0000000 --- a/lib/WebServer/src/detail/RequestHandlersImpl.h +++ /dev/null @@ -1,152 +0,0 @@ -#ifndef REQUESTHANDLERSIMPL_H -#define REQUESTHANDLERSIMPL_H - -#include "RequestHandler.h" -#include "mimetable.h" -#include "WString.h" -#include "Uri.h" - -using namespace mime; - -class FunctionRequestHandler : public RequestHandler { -public: - FunctionRequestHandler(WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const Uri &uri, HTTPMethod method) - : _fn(fn) - , _ufn(ufn) - , _uri(uri.clone()) - , _method(method) - { - _uri->initPathArgs(pathArgs); - } - - ~FunctionRequestHandler() { - delete _uri; - } - - bool canHandle(HTTPMethod requestMethod, String requestUri) override { - if (_method != HTTP_ANY && _method != requestMethod) - return false; - - return _uri->canHandle(requestUri, pathArgs); - } - - 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; - Uri *_uri; - HTTPMethod _method; -}; - -#ifdef WEBSERVER_ENABLE_STATIC_CONTENT -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); - log_v("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header); - _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; - - log_v("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str()); - - 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); - } - log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile); - - 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(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) { - String pathWithGz = path + FPSTR(mimeTable[gz].endsWith); - if(_fs.exists(pathWithGz)) - path += FPSTR(mimeTable[gz].endsWith); - } - - File f = _fs.open(path, "r"); - if (!f || !f.available()) - 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 - -#endif //REQUESTHANDLERSIMPL_H diff --git a/lib/WebServer/src/detail/mimetable.cpp b/lib/WebServer/src/detail/mimetable.cpp deleted file mode 100644 index 563556a..0000000 --- a/lib/WebServer/src/detail/mimetable.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "mimetable.h" -#include "pgmspace.h" - -namespace mime -{ - -// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules -const Entry mimeTable[maxType] = -{ - { ".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" } -}; - -} diff --git a/lib/WebServer/src/detail/mimetable.h b/lib/WebServer/src/detail/mimetable.h deleted file mode 100644 index 191356c..0000000 --- a/lib/WebServer/src/detail/mimetable.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef __MIMETABLE_H__ -#define __MIMETABLE_H__ - - -namespace mime -{ - -enum type -{ - html, - htm, - css, - txt, - js, - json, - png, - gif, - jpg, - ico, - svg, - ttf, - otf, - woff, - woff2, - eot, - sfnt, - xml, - pdf, - zip, - gz, - appcache, - none, - maxType -}; - -struct Entry -{ - const char endsWith[16]; - const char mimeType[32]; -}; - - -extern const Entry mimeTable[maxType]; -} - - -#endif diff --git a/lib/WebServer/src/hardware/W5500EthClient.cpp b/lib/WebServer/src/hardware/W5500EthClient.cpp deleted file mode 100644 index 8d81487..0000000 --- a/lib/WebServer/src/hardware/W5500EthClient.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "W5500EthClient.h" - -W5500EthClient::W5500EthClient(EthernetClient *wifiClient) - : _ethClient(wifiClient) -{ - -} - -W5500EthClient::~W5500EthClient() -{ - _ethClient = nullptr; -} - -uint8_t W5500EthClient::connected() -{ - return _ethClient->connected(); -} - -int W5500EthClient::setTimeout(uint32_t seconds) -{ - _ethClient->setTimeout(seconds); - return 0; -} - -size_t W5500EthClient::write(const char *buffer, size_t size) -{ - if(size == 0) - { - return 0; - } - - const size_t chunkSize = 2048; // W5100.SSIZE in socket.cpp - - uint32_t index = 0; - uint32_t bytesLeft = 0; - size_t written = 0; - - do - { - bytesLeft = size - index; - if(bytesLeft >= chunkSize) - { - _ethClient->write(&buffer[index], chunkSize); - index = index + chunkSize; - written = written + chunkSize; - } - else - { - _ethClient->write(&buffer[index], bytesLeft); - index = index + bytesLeft; - written = written + bytesLeft; - } - } while (bytesLeft > 0); - - return written; -// return _ethClient->write(buffer, size); -} - -IPAddress W5500EthClient::localIP() -{ - return Ethernet.localIP(); -} - -void W5500EthClient::stop() -{ - _ethClient->stop(); -} - -size_t W5500EthClient::write_P(const char *buf, size_t size) -{ - return _ethClient->write(buf, size); -} - -int W5500EthClient::available() -{ - return _ethClient->available(); -} - -String W5500EthClient::readStringUntil(char terminator) -{ - return _ethClient->readStringUntil(terminator); -} - -size_t W5500EthClient::readBytes(char *buffer, size_t length) -{ - return _ethClient->readBytes(buffer, length); -} - -void W5500EthClient::flush() -{ - _ethClient->flush(); -} - -int W5500EthClient::read() -{ - return _ethClient->read(); -} - -unsigned long W5500EthClient::getTimeout(void) -{ - return _ethClient->getTimeout(); -} - -size_t W5500EthClient::write(Stream &stream) -{ - uint8_t * buf = (uint8_t *)malloc(1360); - if(!buf){ - return 0; - } - size_t toRead = 0, toWrite = 0, written = 0; - size_t available = stream.available(); - while(available){ - toRead = (available > 1360)?1360:available; - toWrite = stream.readBytes(buf, toRead); - written += _ethClient->write(buf, toWrite); - available = stream.available(); - } - free(buf); - return written; -} diff --git a/lib/WebServer/src/hardware/W5500EthClient.h b/lib/WebServer/src/hardware/W5500EthClient.h deleted file mode 100644 index d6779a8..0000000 --- a/lib/WebServer/src/hardware/W5500EthClient.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include -#include "EthClient.h" - -class W5500EthClient : public EthClient -{ -public: - explicit W5500EthClient(EthernetClient* wifiClient); - virtual ~W5500EthClient(); - - uint8_t connected() override; - int available() override; - unsigned long getTimeout(void) override; - int setTimeout(uint32_t seconds) override; - int read() override; - size_t write(const char *buffer, size_t size) override; - size_t write(Stream &stream) override; - size_t write_P(const char *buf, size_t size) override; - String readStringUntil(char terminator) override; - size_t readBytes(char *buffer, size_t length) override; - IPAddress localIP() override; - void stop() override; - void flush() override; - -private: - EthernetClient* _ethClient; -}; \ No newline at end of file diff --git a/lib/WebServer/src/hardware/W5500EthServer.cpp b/lib/WebServer/src/hardware/W5500EthServer.cpp deleted file mode 100644 index 1e4958b..0000000 --- a/lib/WebServer/src/hardware/W5500EthServer.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "W5500EthServer.h" - - -W5500EthServer::W5500EthServer(IPAddress address, int port) - : EthServer(address, port), - _ethServer(address, port) -{ - -} - -W5500EthServer::W5500EthServer(int port) - : EthServer(port), - _ethServer(port) -{ - -} - -void W5500EthServer::close() -{ -// _ethServer.close(); -} - -void W5500EthServer::begin(const int port) -{ - _ethServer.begin(port); -} - -void W5500EthServer::setNoDelay(const bool value) -{ -// _ethServer.setNoDelay(value); -} - -EthClient* W5500EthServer::available() -{ - if(_W5500EthClient != nullptr) - { - delete _W5500EthClient; - _W5500EthClient = nullptr; - } - - _ethClient = _ethServer.available(); - _W5500EthClient = new W5500EthClient(&_ethClient); - return _W5500EthClient; -} - - -void W5500EthServer::discardClient() -{ - if(_W5500EthClient != nullptr) - { - delete _W5500EthClient; - _W5500EthClient = nullptr; - } - - _ethClient = EthernetClient(); -} - - -// EthernetServerImpl -void EthernetServerImpl::begin(uint16_t port) -{ - EthernetServer::begin(); -} - -EthernetServerImpl::EthernetServerImpl(int address, int port) -: EthernetServer(port) -{ - -} - -EthernetServerImpl::EthernetServerImpl(int port) -: EthernetServer(port) -{ - -} diff --git a/lib/WebServer/src/hardware/W5500EthServer.h b/lib/WebServer/src/hardware/W5500EthServer.h deleted file mode 100644 index 29c40a4..0000000 --- a/lib/WebServer/src/hardware/W5500EthServer.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "EthServer.h" -#include "W5500EthClient.h" -#include -#include - -class EthernetServerImpl : public EthernetServer -{ -public: - EthernetServerImpl(int address, int port); - explicit EthernetServerImpl(int port); - - virtual void begin(uint16_t port); -}; - -class W5500EthServer : public EthServer -{ -public: - W5500EthServer(IPAddress address, int port); - explicit W5500EthServer(int port); - - virtual EthClient* available(); - virtual void discardClient(); - - virtual void begin(const int port = 80); - virtual void close(); - - virtual void setNoDelay(const bool value); - -private: - EthernetServerImpl _ethServer; - EthernetClient _ethClient; - W5500EthClient* _W5500EthClient = nullptr; -}; diff --git a/lib/WebServer/src/hardware/WifiEthClient.cpp b/lib/WebServer/src/hardware/WifiEthClient.cpp deleted file mode 100644 index e3b60e6..0000000 --- a/lib/WebServer/src/hardware/WifiEthClient.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "WifiEthClient.h" - -WifiEthClient::WifiEthClient(WiFiClient *wifiClient) -: _wifiClient(wifiClient) -{ - -} - -WifiEthClient::~WifiEthClient() -{ - _wifiClient = nullptr; -} - -uint8_t WifiEthClient::connected() -{ - return _wifiClient->connected(); -} - -int WifiEthClient::setTimeout(uint32_t seconds) -{ - _wifiClient->setTimeout(seconds); - return seconds; -} - -size_t WifiEthClient::write(const char *buffer, size_t size) -{ - return _wifiClient->write(buffer, size); -} - -IPAddress WifiEthClient::localIP() -{ - return _wifiClient->localIP(); -} - -void WifiEthClient::stop() -{ - _wifiClient->stop(); -} - -size_t WifiEthClient::write_P(const char *buf, size_t size) -{ - return _wifiClient->write_P(buf, size); -} - -int WifiEthClient::available() -{ - return _wifiClient->available(); -} - -String WifiEthClient::readStringUntil(char terminator) -{ - return _wifiClient->readStringUntil(terminator); -} - -size_t WifiEthClient::readBytes(char *buffer, size_t length) -{ - return _wifiClient->readBytes(buffer, length); -} - -void WifiEthClient::flush() -{ - _wifiClient->flush(); -} - -int WifiEthClient::read() -{ - return _wifiClient->read(); -} - -unsigned long WifiEthClient::getTimeout(void) -{ - return _wifiClient->getTimeout(); -} - -size_t WifiEthClient::write(Stream &stream) -{ - return _wifiClient->write(stream); -} diff --git a/lib/WebServer/src/hardware/WifiEthClient.h b/lib/WebServer/src/hardware/WifiEthClient.h deleted file mode 100644 index d97b554..0000000 --- a/lib/WebServer/src/hardware/WifiEthClient.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include -#include "EthClient.h" - -class WifiEthClient : public EthClient -{ -public: - explicit WifiEthClient(WiFiClient* wifiClient); - virtual ~WifiEthClient(); - - uint8_t connected() override; - int available() override; - unsigned long getTimeout(void) override; - int setTimeout(uint32_t seconds) override; - int read() override; - size_t write(const char *buffer, size_t size) override; - size_t write(Stream &stream) override; - size_t write_P(const char *buf, size_t size) override; - String readStringUntil(char terminator) override; - size_t readBytes(char *buffer, size_t length) override; - IPAddress localIP() override; - void stop() override; - void flush() override; - -private: - WiFiClient* _wifiClient; -}; \ No newline at end of file diff --git a/lib/WebServer/src/hardware/WifiEthServer.cpp b/lib/WebServer/src/hardware/WifiEthServer.cpp deleted file mode 100644 index a349288..0000000 --- a/lib/WebServer/src/hardware/WifiEthServer.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "WifiEthServer.h" - - -WifiEthServer::WifiEthServer(IPAddress address, int port) -: EthServer(address, port), - _wifiServer(address, port) -{ - -} - -WifiEthServer::WifiEthServer(int port) -: EthServer(port), - _wifiServer(port) -{ - -} - -void WifiEthServer::close() -{ - _wifiServer.close(); -} - -void WifiEthServer::begin(const int port) -{ - _wifiServer.begin(port); -} - -void WifiEthServer::setNoDelay(const bool value) -{ - _wifiServer.setNoDelay(value); -} - -EthClient* WifiEthServer::available() -{ - if(_wifiEthClient != nullptr) - { - delete _wifiEthClient; - _wifiEthClient = nullptr; - } - - _wifiClient = _wifiServer.accept(); - _wifiEthClient = new WifiEthClient(&_wifiClient); - return _wifiEthClient; -} - - -void WifiEthServer::discardClient() -{ - if(_wifiEthClient != nullptr) - { - delete _wifiEthClient; - _wifiEthClient = nullptr; - } - - _wifiClient = WiFiClient(); -} diff --git a/lib/WebServer/src/hardware/WifiEthServer.h b/lib/WebServer/src/hardware/WifiEthServer.h deleted file mode 100644 index 3840b6a..0000000 --- a/lib/WebServer/src/hardware/WifiEthServer.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#include "EthServer.h" -#include "WifiEthClient.h" -#include - -class WifiEthServer : public EthServer -{ -public: - WifiEthServer(IPAddress address, int port); - explicit WifiEthServer(int port); - - virtual EthClient* available(); - virtual void discardClient(); - - virtual void begin(const int port = 80); - virtual void close(); - - virtual void setNoDelay(const bool value); - -private: - WiFiServer _wifiServer; - WiFiClient _wifiClient; - WifiEthClient* _wifiEthClient = nullptr; -}; diff --git a/lib/WebServer/src/uri/UriBraces.h b/lib/WebServer/src/uri/UriBraces.h deleted file mode 100644 index 4a6049b..0000000 --- a/lib/WebServer/src/uri/UriBraces.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef URI_BRACES_H -#define URI_BRACES_H - -#include "Uri.h" - -class UriBraces : public Uri { - - public: - explicit UriBraces(const char *uri) : Uri(uri) {}; - explicit UriBraces(const String &uri) : Uri(uri) {}; - - Uri* clone() const override final { - return new UriBraces(_uri); - }; - - void initPathArgs(std::vector &pathArgs) override final { - int numParams = 0, start = 0; - do { - start = _uri.indexOf("{}", start); - if (start > 0) { - numParams++; - start += 2; - } - } while (start > 0); - pathArgs.resize(numParams); - } - - bool canHandle(const String &requestUri, std::vector &pathArgs) override final { - if (Uri::canHandle(requestUri, pathArgs)) - return true; - - size_t uriLength = _uri.length(); - unsigned int pathArgIndex = 0; - unsigned int requestUriIndex = 0; - for (unsigned int i = 0; i < uriLength; i++, requestUriIndex++) { - char uriChar = _uri[i]; - char requestUriChar = requestUri[requestUriIndex]; - - if (uriChar == requestUriChar) - continue; - if (uriChar != '{') - return false; - - i += 2; // index of char after '}' - if (i >= uriLength) { - // there is no char after '}' - pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex); - return pathArgs[pathArgIndex].indexOf("/") == -1; // path argument may not contain a '/' - } - else - { - char charEnd = _uri[i]; - int uriIndex = requestUri.indexOf(charEnd, requestUriIndex); - if (uriIndex < 0) - return false; - pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex, uriIndex); - requestUriIndex = (unsigned int) uriIndex; - } - pathArgIndex++; - } - - return requestUriIndex >= requestUri.length(); - } -}; - -#endif diff --git a/lib/WebServer/src/uri/UriGlob.h b/lib/WebServer/src/uri/UriGlob.h deleted file mode 100644 index 1e222cb..0000000 --- a/lib/WebServer/src/uri/UriGlob.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef URI_GLOB_H -#define URI_GLOB_H - -#include "Uri.h" -#include - -class UriGlob : public Uri { - - public: - explicit UriGlob(const char *uri) : Uri(uri) {}; - explicit UriGlob(const String &uri) : Uri(uri) {}; - - Uri* clone() const override final { - return new UriGlob(_uri); - }; - - bool canHandle(const String &requestUri, __attribute__((unused)) std::vector &pathArgs) override final { - return fnmatch(_uri.c_str(), requestUri.c_str(), 0) == 0; - } -}; - -#endif diff --git a/lib/WebServer/src/uri/UriRegex.h b/lib/WebServer/src/uri/UriRegex.h deleted file mode 100644 index 0570201..0000000 --- a/lib/WebServer/src/uri/UriRegex.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef URI_REGEX_H -#define URI_REGEX_H - -#include "Uri.h" -#include - -class UriRegex : public Uri { - - public: - explicit UriRegex(const char *uri) : Uri(uri) {}; - explicit UriRegex(const String &uri) : Uri(uri) {}; - - Uri* clone() const override final { - return new UriRegex(_uri); - }; - - void initPathArgs(std::vector &pathArgs) override final { - std::regex rgx((_uri + "|").c_str()); - std::smatch matches; - std::string s{""}; - std::regex_search(s, matches, rgx); - pathArgs.resize(matches.size() - 1); - } - - bool canHandle(const String &requestUri, std::vector &pathArgs) override final { - if (Uri::canHandle(requestUri, pathArgs)) - return true; - - unsigned int pathArgIndex = 0; - std::regex rgx(_uri.c_str()); - std::smatch matches; - std::string s(requestUri.c_str()); - if (std::regex_search(s, matches, rgx)) { - for (size_t i = 1; i < matches.size(); ++i) { // skip first - pathArgs[pathArgIndex] = String(matches[i].str().c_str()); - pathArgIndex++; - } - return true; - } - return false; - } -}; - -#endif diff --git a/lib/WiFiManager/WiFiManager.cpp b/lib/WiFiManager/WiFiManager.cpp index 32ea7a8..83ff73c 100644 --- a/lib/WiFiManager/WiFiManager.cpp +++ b/lib/WiFiManager/WiFiManager.cpp @@ -11,7 +11,6 @@ */ #include "WiFiManager.h" -#include "hardware/WifiEthServer.h" #if defined(ESP8266) || defined(ESP32) @@ -292,6 +291,11 @@ boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) { DEBUG_WM(F("AutoConnect")); #endif + // no getter for autoreconnectpolicy before this + // https://github.com/esp8266/Arduino/pull/4359 + // so we must force it on else, if not connectimeout then waitforconnectionresult gets stuck endless loop + WiFi_autoReconnect(); + // bool wifiIsSaved = getWiFiIsSaved(); bool wifiIsSaved = true; // workaround until I can check esp32 wifiisinit and has nvs @@ -334,11 +338,6 @@ boolean WiFiManager::autoConnect(char const *apName, char const *apPassword) { _usermode = WIFI_STA; // When using autoconnect , assume the user wants sta mode on permanently. - // no getter for autoreconnectpolicy before this - // https://github.com/esp8266/Arduino/pull/4359 - // so we must force it on else, if not connectimeout then waitforconnectionresult gets stuck endless loop - WiFi_autoReconnect(); - #ifdef ESP8266 if(_hostname != ""){ setupHostname(true); @@ -629,56 +628,46 @@ boolean WiFiManager::configPortalHasTimeout(){ void WiFiManager::setupHTTPServer(){ - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(F("Starting Web Portal")); - #endif - - if(_httpPort != 80) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE,F("http server started with custom port: "),_httpPort); // @todo not showing ip - #endif - } - - server.reset(new WM_WebServer(new WifiEthServer(80))); - // This is not the safest way to reset the webserver, it can cause crashes on callbacks initilized before this and since its a shared pointer... - - if ( _webservercallback != NULL) { - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE,F("[CB] _webservercallback calling")); - #endif - _webservercallback(); // @CALLBACK - } - // @todo add a new callback maybe, after webserver started, callback cannot override handlers, but can grab them first + server.reset(new WM_WebServer(_httpPort)); /* Setup httpd callbacks, web pages: root, wifi config pages, SO captive portal detectors and not found. */ + server->on(String(FPSTR(R_wifi)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleWifi, this,std::placeholders::_1,true)); + server->on(String(FPSTR(R_wifinoscan)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleWifi, this,std::placeholders::_1,false)); + server->on(String(FPSTR(R_erase)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleErase, this,std::placeholders::_1,false)); + { + using namespace std::placeholders; + server->on(String(FPSTR(R_root)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleRoot, this,_1)); + server->on(String(FPSTR(R_wifisave)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleWifiSave, this,_1)); + server->on(String(FPSTR(R_info)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleInfo, this,_1)); + server->on(String(FPSTR(R_param)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleParam, this,_1)); + server->on(String(FPSTR(R_paramsave)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleParamSave, this,_1)); + server->on(String(FPSTR(R_restart)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleReset, this,_1)); + server->on(String(FPSTR(R_exit)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleExit, this,_1)); + server->on(String(FPSTR(R_close)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleClose, this,_1)); + server->on(String(FPSTR(R_status)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleWiFiStatus, this,_1)); + server->onNotFound (std::bind(&WiFiManager::handleNotFound, this, _1)); - // G macro workaround for Uri() bug https://github.com/esp8266/Arduino/issues/7102 - server->on(WM_G(R_root), std::bind(&WiFiManager::handleRoot, this)); - server->on(WM_G(R_wifi), std::bind(&WiFiManager::handleWifi, this, true)); - server->on(WM_G(R_wifinoscan), std::bind(&WiFiManager::handleWifi, this, false)); - server->on(WM_G(R_wifisave), std::bind(&WiFiManager::handleWifiSave, this)); - server->on(WM_G(R_info), std::bind(&WiFiManager::handleInfo, this)); - server->on(WM_G(R_param), std::bind(&WiFiManager::handleParam, this)); - server->on(WM_G(R_paramsave), std::bind(&WiFiManager::handleParamSave, this)); - server->on(WM_G(R_restart), std::bind(&WiFiManager::handleReset, this)); - server->on(WM_G(R_exit), std::bind(&WiFiManager::handleExit, this)); - server->on(WM_G(R_close), std::bind(&WiFiManager::handleClose, this)); - server->on(WM_G(R_erase), std::bind(&WiFiManager::handleErase, this, false)); - server->on(WM_G(R_status), std::bind(&WiFiManager::handleWiFiStatus, this)); - server->onNotFound (std::bind(&WiFiManager::handleNotFound, this)); - - server->on(WM_G(R_update), std::bind(&WiFiManager::handleUpdate, this)); - server->on(WM_G(R_updatedone), HTTP_POST, std::bind(&WiFiManager::handleUpdateDone, this), std::bind(&WiFiManager::handleUpdating, this)); - + server->on(String(FPSTR(R_update)).c_str(), HTTP_ANY, std::bind(&WiFiManager::handleUpdate, this, _1)); + server->on(String(FPSTR(R_updatedone)).c_str(), HTTP_POST,std::bind(&WiFiManager::handleUpdateDone, this, _1), std::bind(&WiFiManager::handleUpdating, this, _1,_2,_3,_4,_5,_6)); + } server->begin(); // Web server start - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE,F("HTTP server started")); - #endif +} + +void WiFiManager::teardownHTTPServer(){ + } void WiFiManager::setupDNSD(){ dnsServer.reset(new DNSServer()); + if(_httpPort != 80) { + #ifdef WM_DEBUG_LEVEL + DEBUG_WM(WM_DEBUG_VERBOSE,F("http server starting with custom port: "),_httpPort); // @todo not showing ip + #endif + } + + setupHTTPServer(); + /* Setup the DNS server redirecting all the domains to the apIP */ dnsServer->setErrorReplyCode(DNSReplyCode::NoError); #ifdef WM_DEBUG_LEVEL @@ -824,7 +813,7 @@ boolean WiFiManager::startConfigPortal(char const *apName, char const *apPasswo if(!configPortalActive) break; - yield(); // watchdog + vTaskDelay( 50 / portTICK_PERIOD_MS); } #ifdef WM_DEBUG_LEVEL @@ -881,7 +870,11 @@ uint8_t WiFiManager::processConfigPortal(){ } //HTTP handler + #ifndef WM_ASYNCWEBSERVER server->handleClient(); + #endif + + if(_rebootNeeded) reboot(); // Waiting for save... if(connect) { @@ -978,21 +971,27 @@ bool WiFiManager::shutdownConfigPortal(){ dnsServer->processNextRequest(); } + #ifndef WM_ASYNCWEBSERVER //HTTP handler server->handleClient(); + #endif // @todo what is the proper way to shutdown and free the server up // debug - many open issues aobut port not clearing for use with other servers + #ifdef WM_ASYNCWEBSERVER + server->end(); + #else server->stop(); + #endif server.reset(); + dnsServer->stop(); // free heap ? + dnsServer.reset(); + WiFi.scanDelete(); // free wifi scan results if(!configPortalActive) return false; - dnsServer->stop(); // free heap ? - dnsServer.reset(); - // turn off AP // @todo bug workaround // https://github.com/esp8266/Arduino/issues/3793 @@ -1126,7 +1125,7 @@ bool WiFiManager::wifiConnectNew(String ssid, String pass,bool connect){ if (_findBestRSSI) { WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL); - + #ifdef WM_DEBUG_LEVEL DEBUG_WM(F("find best RSSI: TRUE")); #endif @@ -1206,7 +1205,7 @@ bool WiFiManager::wifiConnectDefault(){ DEBUG_WM(F("Connecting to SAVED AP:"),ssid); DEBUG_WM(WM_DEBUG_DEV,F("Using Password:"),pass); #endif - + ret = WiFi_enableSTA(true,storeSTAmode); delay(500); // THIS DELAY ? @@ -1424,8 +1423,10 @@ String WiFiManager::getHTTPHead(String title){ return page; } -void WiFiManager::HTTPSend(const String &content){ - server->send(200, FPSTR(HTTP_HEAD_CT), content); +void WiFiManager::HTTPSend(AsyncWebServerRequest *request, String page){ + AsyncWebServerResponse *response = request->beginResponse(200,FPSTR(HTTP_HEAD_CT), page); + response->addHeader(FPSTR(HTTP_HEAD_CL), String(page.length())); + request->send(response); } /** @@ -1440,32 +1441,17 @@ void WiFiManager::handleRequest() { // bool authenticate(const char * username, const char * password); // bool authenticateDigest(const String& username, const String& H1); // void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String("") ); - - // 2.3 NO AUTH available - bool testauth = false; - if(!testauth) return; - - DEBUG_WM(WM_DEBUG_DEV,F("DOING AUTH")); - bool res = server->authenticate("admin","12345"); - if(!res){ - #ifndef WM_NOAUTH - server->requestAuthentication(HTTPAuthMethod::BASIC_AUTH); // DIGEST_AUTH - #endif - DEBUG_WM(WM_DEBUG_DEV,F("AUTH FAIL")); - } } /** * HTTPD CALLBACK root or redirect to captive portal */ -void WiFiManager::handleRoot() { +void WiFiManager::handleRoot(AsyncWebServerRequest *request) { #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Root")); #endif - if (_hasCredentials && !server->authenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } - if (captivePortal()) return; // If captive portal redirect instead of displaying the page + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + if (captivePortal(request)) return; // If captive portal redirect instead of displaying the page handleRequest(); String page = getHTTPHead(_title); // @token options @todo replace options with title String str = FPSTR(HTTP_ROOT_MAIN); // @todo custom title @@ -1477,7 +1463,7 @@ void WiFiManager::handleRoot() { reportStatus(page); page += FPSTR(HTTP_END); - HTTPSend(page); + HTTPSend(request,page); if(_preloadwifiscan) WiFi_scanNetworks(_scancachetime,true); // preload wifiscan throttled, async // @todo buggy, captive portals make a query on every page load, causing this to run every time in addition to the real page load // I dont understand why, when you are already in the captive portal, I guess they want to know that its still up and not done or gone @@ -1487,20 +1473,18 @@ void WiFiManager::handleRoot() { /** * HTTPD CALLBACK Wifi config page handler */ -void WiFiManager::handleWifi(boolean scan) { +void WiFiManager::handleWifi(AsyncWebServerRequest *request,bool scan = true) { #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Wifi")); #endif - if (_hasCredentials && !server->authenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); handleRequest(); String page = getHTTPHead(FPSTR(S_titlewifi)); // @token titlewifi if (scan) { #ifdef WM_DEBUG_LEVEL - // DEBUG_WM(WM_DEBUG_DEV,"refresh flag:",server->hasArg(F("refresh"))); + // DEBUG_WM(WM_DEBUG_DEV,"refresh flag:",request->hasArg(F("refresh"))); #endif - WiFi_scanNetworks(server->hasArg(F("refresh")),false); //wifiscan, force if arg refresh + WiFi_scanNetworks(request->hasArg(F("refresh")),false); //wifiscan, force if arg refresh page += getScanItemOut(); } String pitem = ""; @@ -1536,7 +1520,7 @@ void WiFiManager::handleWifi(boolean scan) { reportStatus(page); page += FPSTR(HTTP_END); - HTTPSend(page); + HTTPSend(request,page); #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_DEV,F("Sent config page")); @@ -1546,13 +1530,11 @@ void WiFiManager::handleWifi(boolean scan) { /** * HTTPD CALLBACK Wifi param page handler */ -void WiFiManager::handleParam(){ +void WiFiManager::handleParam(AsyncWebServerRequest *request){ #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Param")); #endif - if (_hasCredentials && !server->authenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); handleRequest(); String page = getHTTPHead(FPSTR(S_titleparam)); // @token titlewifi @@ -1568,7 +1550,7 @@ void WiFiManager::handleParam(){ reportStatus(page); page += FPSTR(HTTP_END); - HTTPSend(page); + HTTPSend(request,page); #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_DEV,F("Sent param page")); @@ -1623,17 +1605,9 @@ bool WiFiManager::WiFi_scanNetworks(bool force,bool async){ // DEBUG_WM(WM_DEBUG_DEV,"scanNetworks force:",force == true); #endif - // if 0 networks, rescan @note this was a kludge, now disabling to test real cause ( maybe wifi not init etc) - // enable only if preload failed? - if(_numNetworks == 0 && _autoforcerescan){ - DEBUG_WM(WM_DEBUG_DEV,"NO APs found forcing new scan"); - force = true; - } - - // if scan is empty or stale (last scantime > _scancachetime), this avoids fast reloading wifi page and constant scan delayed page loads appearing to freeze. - if(!_lastscan || (_lastscan>0 && (millis()-_lastscan > _scancachetime))){ - force = true; - } + force = false; + async = true; + force = _lastscan == 0; if(force){ int8_t res; @@ -1933,38 +1907,34 @@ String WiFiManager::getParamOut(){ return page; } -void WiFiManager::handleWiFiStatus(){ +void WiFiManager::handleWiFiStatus(AsyncWebServerRequest *request){ #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP WiFi status ")); #endif - if (_hasCredentials && !server->authenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); handleRequest(); String page; // String page = "{\"result\":true,\"count\":1}"; #ifdef WM_JSTEST page = FPSTR(HTTP_JS); #endif - HTTPSend(page); + HTTPSend(request,page); } /** * HTTPD CALLBACK save form and redirect to WLAN config page again */ -void WiFiManager::handleWifiSave() { +void WiFiManager::handleWifiSave(AsyncWebServerRequest *request) { #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP WiFi save ")); - DEBUG_WM(WM_DEBUG_DEV,F("Method:"),server->method() == HTTP_GET ? (String)FPSTR(S_GET) : (String)FPSTR(S_POST)); + DEBUG_WM(WM_DEBUG_DEV,F("Method:"),request->method() == HTTP_GET ? (String)FPSTR(S_GET) : (String)FPSTR(S_POST)); #endif - if (_hasCredentials && !server->authenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); handleRequest(); //SAVE/connect here - _ssid = server->arg(F("s")).c_str(); - _pass = server->arg(F("p")).c_str(); + _ssid = request->arg(F("s")).c_str(); + _pass = request->arg(F("p")).c_str(); if(_ssid == "" && _pass != ""){ _ssid = WiFi_SSID(true); // password change, placeholder ssid, @todo compare pass to old?, confirm ssid is clean @@ -1976,44 +1946,44 @@ void WiFiManager::handleWifiSave() { #ifdef WM_DEBUG_LEVEL String requestinfo = "SERVER_REQUEST\n----------------\n"; requestinfo += "URI: "; - requestinfo += server->uri(); + requestinfo += request->url(); requestinfo += "\nMethod: "; - requestinfo += (server->method() == HTTP_GET) ? "GET" : "POST"; + requestinfo += (request->method() == HTTP_GET) ? "GET" : "POST"; requestinfo += "\nArguments: "; - requestinfo += server->args(); + requestinfo += request->args(); requestinfo += "\n"; - for (uint8_t i = 0; i < server->args(); i++) { - requestinfo += " " + server->argName(i) + ": " + server->arg(i) + "\n"; + for (uint8_t i = 0; i < request->args(); i++) { + requestinfo += " " + request->argName(i) + ": " + request->arg(i) + "\n"; } DEBUG_WM(WM_DEBUG_MAX,requestinfo); #endif // set static ips from server args - if (server->arg(FPSTR(S_ip)) != "") { - //_sta_static_ip.fromString(server->arg(FPSTR(S_ip)); - String ip = server->arg(FPSTR(S_ip)); + if (request->arg(FPSTR(S_ip)) != "") { + //_sta_static_ip.fromString(request->arg(FPSTR(S_ip)); + String ip = request->arg(FPSTR(S_ip)); optionalIPFromString(&_sta_static_ip, ip.c_str()); #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_DEV,F("static ip:"),ip); #endif } - if (server->arg(FPSTR(S_gw)) != "") { - String gw = server->arg(FPSTR(S_gw)); + if (request->arg(FPSTR(S_gw)) != "") { + String gw = request->arg(FPSTR(S_gw)); optionalIPFromString(&_sta_static_gw, gw.c_str()); #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_DEV,F("static gateway:"),gw); #endif } - if (server->arg(FPSTR(S_sn)) != "") { - String sn = server->arg(FPSTR(S_sn)); + if (request->arg(FPSTR(S_sn)) != "") { + String sn = request->arg(FPSTR(S_sn)); optionalIPFromString(&_sta_static_sn, sn.c_str()); #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_DEV,F("static netmask:"),sn); #endif } - if (server->arg(FPSTR(S_dns)) != "") { - String dns = server->arg(FPSTR(S_dns)); + if (request->arg(FPSTR(S_dns)) != "") { + String dns = request->arg(FPSTR(S_dns)); optionalIPFromString(&_sta_static_dns, dns.c_str()); #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_DEV,F("static DNS:"),dns); @@ -2024,7 +1994,7 @@ void WiFiManager::handleWifiSave() { _presavewificallback(); // @CALLBACK } - if(_paramsInWifi) doParamSave(); + if(_paramsInWifi) doParamSave(request); String page; @@ -2040,8 +2010,8 @@ void WiFiManager::handleWifiSave() { if(_showBack) page += FPSTR(HTTP_BACKBTN); page += FPSTR(HTTP_END); - server->sendHeader(FPSTR(HTTP_HEAD_CORS), FPSTR(HTTP_HEAD_CORS_ALLOW_ALL)); // @HTTPHEAD send cors - HTTPSend(page); + //server->sendHeader(FPSTR(HTTP_HEAD_CORS), FPSTR(HTTP_HEAD_CORS_ALLOW_ALL)); // @HTTPHEAD send cors + HTTPSend(request,page); #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_DEV,F("Sent wifi save page")); @@ -2050,34 +2020,32 @@ void WiFiManager::handleWifiSave() { connect = true; //signal ready to connect/reset process in processConfigPortal } -void WiFiManager::handleParamSave() { +void WiFiManager::handleParamSave(AsyncWebServerRequest *request) { #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Param save ")); #endif #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV,F("Method:"),server->method() == HTTP_GET ? (String)FPSTR(S_GET) : (String)FPSTR(S_POST)); + DEBUG_WM(WM_DEBUG_DEV,F("Method:"),request->method() == HTTP_GET ? (String)FPSTR(S_GET) : (String)FPSTR(S_POST)); #endif - if (_hasCredentials && !server->authenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); handleRequest(); - doParamSave(); + doParamSave(request); String page = getHTTPHead(FPSTR(S_titleparamsaved)); // @token titleparamsaved page += FPSTR(HTTP_PARAMSAVED); if(_showBack) page += FPSTR(HTTP_BACKBTN); page += FPSTR(HTTP_END); - HTTPSend(page); + HTTPSend(request,page); #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_DEV,F("Sent param save page")); #endif } -void WiFiManager::doParamSave(){ +void WiFiManager::doParamSave(AsyncWebServerRequest *request){ // @todo use new callback for before paramsaves, is this really needed? if ( _presaveparamscallback != NULL) { _presaveparamscallback(); // @CALLBACK @@ -2100,10 +2068,10 @@ void WiFiManager::doParamSave(){ //read parameter from server String name = (String)FPSTR(S_parampre)+(String)i; String value; - if(server->hasArg(name)) { - value = server->arg(name); + if(request->hasArg(name.c_str())) { + value = request->arg(name); } else { - value = server->arg(_params[i]->getID()); + value = request->arg(_params[i]->getID()); } //store it in params array @@ -2126,14 +2094,12 @@ void WiFiManager::doParamSave(){ /** * HTTPD CALLBACK info page */ -void WiFiManager::handleInfo() { +void WiFiManager::handleInfo(AsyncWebServerRequest *request) { #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Info")); #endif - if (_hasCredentials && !server->authenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } - handleRequest(); + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + //handleRequest(); String page = getHTTPHead(FPSTR(S_titleinfo)); // @token titleinfo reportStatus(page); @@ -2230,7 +2196,7 @@ void WiFiManager::handleInfo() { page += FPSTR(HTTP_HELP); page += FPSTR(HTTP_END); - HTTPSend(page); + HTTPSend(request,page); #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_DEV,F("Sent info page")); @@ -2474,19 +2440,18 @@ String WiFiManager::getInfoData(String id){ /** * HTTPD CALLBACK exit, closes configportal if blocking, if non blocking undefined */ -void WiFiManager::handleExit() { +void WiFiManager::handleExit(AsyncWebServerRequest *request) { #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Exit")); #endif - if (_hasCredentials && !server->authenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); handleRequest(); String page = getHTTPHead(FPSTR(S_titleexit)); // @token titleexit page += FPSTR(S_exiting); // @token exiting // ('Logout', 401, {'WWW-Authenticate': 'Basic realm="Login required"'}) - server->sendHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); // @HTTPHEAD send cache - HTTPSend(page); + AsyncWebServerResponse *response = request->beginResponse(200,FPSTR(HTTP_HEAD_CT), page); + response->addHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); + request->send(response); delay(2000); abort = true; } @@ -2494,25 +2459,22 @@ void WiFiManager::handleExit() { /** * HTTPD CALLBACK reset page */ -void WiFiManager::handleReset() { +void WiFiManager::handleReset(AsyncWebServerRequest *request) { #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP Reset")); #endif - if (_hasCredentials && !server->authenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); handleRequest(); String page = getHTTPHead(FPSTR(S_titlereset)); //@token titlereset page += FPSTR(S_resetting); //@token resetting page += FPSTR(HTTP_END); - HTTPSend(page); + HTTPSend(request,page); #ifdef WM_DEBUG_LEVEL DEBUG_WM(F("RESETTING ESP")); #endif - delay(1000); - reboot(); + _rebootNeeded = true; } /** @@ -2522,13 +2484,11 @@ void WiFiManager::handleReset() { // void WiFiManager::handleErase() { // handleErase(false); // } -void WiFiManager::handleErase(boolean opt) { +void WiFiManager::handleErase(AsyncWebServerRequest *request,bool opt = false) { #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_NOTIFY,F("<- HTTP Erase")); #endif - if (_hasCredentials && !server->authenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); handleRequest(); String page = getHTTPHead(FPSTR(S_titleerase)); // @token titleerase @@ -2543,43 +2503,40 @@ void WiFiManager::handleErase(boolean opt) { } page += FPSTR(HTTP_END); - HTTPSend(page); + HTTPSend(request,page); if(ret){ - delay(2000); - #ifdef WM_DEBUG_LEVEL - DEBUG_WM(F("RESETTING ESP")); - #endif - reboot(); + _rebootNeeded = true; } } /** * HTTPD CALLBACK 404 */ -void WiFiManager::handleNotFound() { - if (captivePortal()) return; // If captive portal redirect instead of displaying the page +void WiFiManager::handleNotFound(AsyncWebServerRequest *request) { + if (captivePortal(request)) return; // If captive portal redirect instead of displaying the page handleRequest(); String message = FPSTR(S_notfound); // @token notfound bool verbose404 = false; // show info in 404 body, uri,method, args if(verbose404){ message += FPSTR(S_uri); // @token uri - message += server->uri(); + message += request->url(); message += FPSTR(S_method); // @token method - message += ( server->method() == HTTP_GET ) ? FPSTR(S_GET) : FPSTR(S_POST); + message += ( request->method() == HTTP_GET ) ? FPSTR(S_GET) : FPSTR(S_POST); message += FPSTR(S_args); // @token args - message += server->args(); + message += request->args(); message += F("\n"); - for ( uint8_t i = 0; i < server->args(); i++ ) { - message += " " + server->argName ( i ) + ": " + server->arg ( i ) + "\n"; + for ( uint8_t i = 0; i < request->args(); i++ ) { + message += " " + request->argName ( i ) + ": " + request->arg ( i ) + "\n"; } } - server->sendHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); // @HTTPHEAD send cache - server->sendHeader(F("Pragma"), F("no-cache")); - server->sendHeader(F("Expires"), F("-1")); - server->send ( 404, FPSTR(HTTP_HEAD_CT2), message ); + AsyncWebServerResponse *response = request->beginResponse(404,FPSTR(HTTP_HEAD_CT2), message); + response->addHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); + response->addHeader(F("Pragma"), F("no-cache")); + response->addHeader(F("Expires"), F("-1")); + request->send(response); } /** @@ -2587,15 +2544,15 @@ void WiFiManager::handleNotFound() { * Redirect to captive portal if we got a request for another domain. * Return true in that case so the page handler do not try to handle the request again. */ -boolean WiFiManager::captivePortal() { +boolean WiFiManager::captivePortal(AsyncWebServerRequest *request) { if(!_enableCaptivePortal || !configPortalActive) return false; // skip redirections if cp not enabled or not in ap mode - String serverLoc = toStringIp(server->client()->localIP()); + String serverLoc = toStringIp(request->client()->localIP()); #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_DEV,"-> " + server->hostHeader()); - DEBUG_WM(WM_DEBUG_DEV,"serverLoc " + serverLoc); + //DEBUG_WM(WM_DEBUG_DEV,"-> " + request->host()); + //DEBUG_WM(WM_DEBUG_DEV,"serverLoc " + serverLoc); #endif // fallback for ipv6 bug @@ -2607,16 +2564,16 @@ boolean WiFiManager::captivePortal() { } if(_httpPort != 80) serverLoc += ":" + (String)_httpPort; // add port if not default - bool doredirect = serverLoc != server->hostHeader(); // redirect if hostheader not server ip, prevent redirect loops + bool doredirect = serverLoc != request->host(); // redirect if hostheader not server ip, prevent redirect loops if (doredirect) { #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_VERBOSE,F("<- Request redirected to captive portal")); DEBUG_WM(WM_DEBUG_DEV,"serverLoc " + serverLoc); #endif - server->sendHeader(F("Location"), (String)F("http://") + serverLoc, true); // @HTTPHEAD send redirect - server->send ( 302, FPSTR(HTTP_HEAD_CT2), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. - server->client()->stop(); // Stop is needed because we sent no content length + AsyncWebServerResponse *response = request->beginResponse(302,FPSTR(HTTP_HEAD_CT2), ""); + response->addHeader(F("Location"), (String)F("http://") + serverLoc); + request->send(response); return true; } return false; @@ -2628,11 +2585,9 @@ void WiFiManager::stopCaptivePortal(){ } // HTTPD CALLBACK, handle close, stop captive portal, if not enabled undefined -void WiFiManager::handleClose(){ +void WiFiManager::handleClose(AsyncWebServerRequest *request){ DEBUG_WM(WM_DEBUG_VERBOSE,F("Disabling Captive Portal")); - if (_hasCredentials && !server->authenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); stopCaptivePortal(); #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_VERBOSE,F("<- HTTP close")); @@ -2640,7 +2595,7 @@ void WiFiManager::handleClose(){ handleRequest(); String page = getHTTPHead(FPSTR(S_titleclose)); // @token titleclose page += FPSTR(S_closing); // @token closing - HTTPSend(page); + HTTPSend(request,page); } void WiFiManager::reportStatus(String &page){ @@ -2735,7 +2690,10 @@ bool WiFiManager::disconnect(){ void WiFiManager::reboot(){ #ifdef WM_DEBUG_LEVEL DEBUG_WM(F("Restarting")); + _debugPort.flush(); #endif + delay(2000); // time for serial and web flsuh + shutdownConfigPortal(); ESP.restart(); } @@ -4046,14 +4004,12 @@ void WiFiManager::WiFi_autoReconnect(){ } // Called when /update is requested -void WiFiManager::handleUpdate() { +void WiFiManager::handleUpdate(AsyncWebServerRequest *request) { #ifdef WM_DEBUG_LEVEL DEBUG_WM(WM_DEBUG_VERBOSE,F("<- Handle update")); #endif - if (_hasCredentials && !server->authenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } - if (captivePortal()) return; // If captive portal redirect instead of displaying the page + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + if (captivePortal(request)) return; // If captive portal redirect instead of displaying the page String page = getHTTPHead(_title); // @token options String str = FPSTR(HTTP_ROOT_MAIN); str.replace(FPSTR(T_t), _title); @@ -4063,12 +4019,12 @@ void WiFiManager::handleUpdate() { page += FPSTR(HTTP_UPDATE); page += FPSTR(HTTP_END); - HTTPSend(page); + HTTPSend(request,page); } // upload via /u POST -void WiFiManager::handleUpdating(){ +void WiFiManager::handleUpdating(AsyncWebServerRequest *request,String filename, size_t index, uint8_t *data, size_t len, bool final){ // @todo // cannot upload files in captive portal, file select is not allowed, show message with link or hide // cannot upload if softreset after upload, maybe check for hard reset at least for dev, ERROR[11]: Invalid bootstrapping state, reset ESP8266 before updating @@ -4084,13 +4040,10 @@ void WiFiManager::handleUpdating(){ bool error = false; unsigned long _configPortalTimeoutSAV = _configPortalTimeout; // store cp timeout _configPortalTimeout = 0; // disable timeout - - // handler for the file upload, get's the sketch bytes, and writes - // them through the Update object - HTTPUpload& upload = server->upload(); + bool otadebug = false; // UPLOAD START - if (upload.status == UPLOAD_FILE_START) { + if (!index) { // if(_debug) Serial.setDebugOutput(true); uint32_t maxSketchSpace; @@ -4109,7 +4062,7 @@ void WiFiManager::handleUpdating(){ #endif #ifdef WM_DEBUG_LEVEL - DEBUG_WM(WM_DEBUG_VERBOSE,"[OTA] Update file: ", upload.filename.c_str()); + DEBUG_WM(WM_DEBUG_VERBOSE,"[OTA] Update file: ", filename.c_str()); #endif // Update.onProgress(THandlerFunction_Progress fn); @@ -4124,11 +4077,14 @@ void WiFiManager::handleUpdating(){ error = true; Update.end(); // Not sure the best way to abort, I think client will keep sending.. } + #ifdef ESP8266 + Update.runAsync(true); // tell the updaterClass to run in async mode + #endif } // UPLOAD WRITE - else if (upload.status == UPLOAD_FILE_WRITE) { + if (indexauthenticate(_credUser, _credPassword)) { - return server->requestAuthentication(); - } + // if (captivePortal(request)) return; // If captive portal redirect instead of displaying the page + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); String page = getHTTPHead(FPSTR(S_options)); // @token options String str = FPSTR(HTTP_ROOT_MAIN); str.replace(FPSTR(T_t),_title); @@ -4188,11 +4141,12 @@ void WiFiManager::handleUpdateDone() { } page += FPSTR(HTTP_END); - HTTPSend(page); + HTTPSend(request,page); - delay(1000); // send page + // delay(1000); // send page if (!Update.hasError()) { - ESP.restart(); + //ESP.restart(); + _rebootNeeded = true; } } diff --git a/lib/WiFiManager/WiFiManager.h b/lib/WiFiManager/WiFiManager.h index 6263434..9f0eac5 100644 --- a/lib/WiFiManager/WiFiManager.h +++ b/lib/WiFiManager/WiFiManager.h @@ -1,9 +1,9 @@ /** * WiFiManager.h - * + * * WiFiManager, a library for the ESP8266/Arduino platform * for configuration of WiFi credentials using a Captive Portal - * + * * @author Creator tzapu * @author tablatronix * @version 0.0.0 @@ -24,14 +24,14 @@ // #define WM_MDNS // includes MDNS, also set MDNS with sethostname // #define WM_FIXERASECONFIG // use erase flash fix -// #define WM_ERASE_NVS // esp32 erase(true) will erase NVS +// #define WM_ERASE_NVS // esp32 erase(true) will erase NVS // #define WM_RTC // esp32 info page will include reset reasons // #define WM_JSTEST // build flag for enabling js xhr tests // #define WIFI_MANAGER_OVERRIDE_STRINGS // build flag for using own strings include #ifdef ARDUINO_ESP8266_RELEASE_2_3_0 -#warning "ARDUINO_ESP8266_RELEASE_2_3_0, some WM features disabled" +#warning "ARDUINO_ESP8266_RELEASE_2_3_0, some WM features disabled" // @todo check failing on platform = espressif8266@1.7.3 #define WM_NOASYNC // esp8266 no async scan wifi #define WM_NOCOUNTRY // esp8266 no country @@ -62,6 +62,7 @@ // autoreconnect is WORKING https://github.com/espressif/arduino-esp32/issues/653#issuecomment-405604766 #define WM_WEBSERVERSHIM // use webserver shim lib +#define WM_ASYNCWEBSERVER // use async webserver #define WM_G(string_literal) (String(FPSTR(string_literal)).c_str()) @@ -71,31 +72,42 @@ #include "user_interface.h" } #include - #include + + #ifdef WM_ASYNCWEBSERVER + #include + #include + #else + #include + #endif #ifdef WM_MDNS #include #endif - #define WIFI_getChipId() ESP.getChipId() + #define WIFI_getChipId() ESP.getChipId() #define WM_WIFIOPEN ENC_TYPE_NONE #elif defined(ESP32) #include - #include + #include #include - + #define WIFI_getChipId() (uint32_t)ESP.getEfuseMac() #define WM_WIFIOPEN WIFI_AUTH_OPEN - #ifndef WEBSERVER_H - #ifdef WM_WEBSERVERSHIM - #include - #else - #include - // Forthcoming official ? probably never happening - // https://github.com/esp8266/ESPWebServer + #ifdef WM_ASYNCWEBSERVER + #include + #include + #else + #ifndef WEBSERVER_H + #ifdef WM_WEBSERVERSHIM + #include + #else + #include + // Forthcoming official ? probably never happening + // https://github.com/esp8266/ESPWebServer + #endif #endif #endif @@ -142,7 +154,7 @@ // prep string concat vars #define WM_STRING2(x) #x -#define WM_STRING(x) WM_STRING2(x) +#define WM_STRING(x) WM_STRING2(x) // #include #ifdef ESP_IDF_VERSION @@ -150,7 +162,7 @@ // #pragma message "ESP_IDF_VERSION_MINOR = " WM_STRING(ESP_IDF_VERSION_MINOR) // #pragma message "ESP_IDF_VERSION_PATCH = " WM_STRING(ESP_IDF_VERSION_PATCH) #define VER_IDF_STR WM_STRING(ESP_IDF_VERSION_MAJOR) "." WM_STRING(ESP_IDF_VERSION_MINOR) "." WM_STRING(ESP_IDF_VERSION_PATCH) -#else +#else #define VER_IDF_STR "Unknown" #endif @@ -179,7 +191,7 @@ #define VER_ARDUINO_STR "Unknown" #endif #endif -#else +#else #define VER_ARDUINO_STR "Unknown" #endif @@ -197,7 +209,7 @@ class WiFiManagerParameter { public: - /** + /** Create custom parameters that can be added to the WiFiManager setup web page @id is used for HTTP queries and must not contain spaces nor other special characters */ @@ -228,7 +240,7 @@ class WiFiManagerParameter { char *_value; int _length; int _labelPlacement; - + const char *_customHTML; friend class WiFiManager; }; @@ -263,8 +275,8 @@ class WiFiManager //manually stop the config portal if started manually, stop immediatly if non blocking, flag abort if blocking bool stopConfigPortal(); - - //manually start the web portal, autoconnect does this automatically on connect failure + + //manually start the web portal, autoconnect does this automatically on connect failure void startWebPortal(); //manually stop the web portal if started manually @@ -279,7 +291,7 @@ class WiFiManager // erase wifi credentials void resetSettings(); - + // reset wifi scan void resetScan(); @@ -342,13 +354,13 @@ class WiFiManager // sets number of retries for autoconnect, force retry after wait failure exit void setConnectRetries(uint8_t numRetries); // default 1 - + //sets timeout for which to attempt connecting on saves, useful if there are bugs in esp waitforconnectloop void setSaveConnectTimeout(unsigned long seconds); - + // lets you disable automatically connecting after save from webportal void setSaveConnect(bool connect = true); - + // toggle debug output void setDebugOutput(boolean debug); void setDebugOutput(boolean debug, String prefix); // log line prefix, default "*wm:" @@ -356,24 +368,24 @@ class WiFiManager //set min quality percentage to include in scan, defaults to 8% if not specified void setMinimumSignalQuality(int quality = 8); - + //sets a custom ip /gateway /subnet configuration void setAPStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn); - + //sets config for a static IP void setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn); - + //sets config for a static IP with DNS void setSTAStaticIPConfig(IPAddress ip, IPAddress gw, IPAddress sn, IPAddress dns); - + //if this is set, it will exit after config, even if connection is unsuccessful. void setBreakAfterConfig(boolean shouldBreak); - - // if this is set, portal will be blocking and wait until save or exit, + + // if this is set, portal will be blocking and wait until save or exit, // is false user must manually `process()` to handle config portal, // setConfigPortalTimeout is ignored in this mode, user is responsible for closing configportal void setConfigPortalBlocking(boolean shouldBlock); - + //add custom html at inside for all pages void setCustomHeadElement(const char* html); @@ -382,40 +394,40 @@ class WiFiManager //if this is true, remove duplicated Access Points - defaut true void setRemoveDuplicateAPs(boolean removeDuplicates); - + //setter for ESP wifi.persistent so we can remember it and restore user preference, as WIFi._persistent is protected void setRestorePersistent(boolean persistent); - + //if true, always show static net inputs, IP, subnet, gateway, else only show if set via setSTAStaticIPConfig void setShowStaticFields(boolean alwaysShow); - + //if true, always show static dns, esle only show if set via setSTAStaticIPConfig void setShowDnsFields(boolean alwaysShow); - + // toggle showing the saved wifi password in wifi form, could be a security issue. void setShowPassword(boolean show); - + //if false, disable captive portal redirection void setCaptivePortalEnable(boolean enabled); - + //if false, timeout captive portal even if a STA client connected to softAP (false), suggest disabling if captiveportal is open void setAPClientCheck(boolean enabled); - - //if true, reset timeout when webclient connects (true), suggest disabling if captiveportal is open + + //if true, reset timeout when webclient connects (true), suggest disabling if captiveportal is open void setWebPortalClientCheck(boolean enabled); - + // if true, enable autoreconnecting void setWiFiAutoReconnect(boolean enabled); - + // if true, wifiscan will show percentage instead of quality icons, until we have better templating void setScanDispPerc(boolean enabled); - + // if true (default) then start the config portal from autoConnect if connection failed void setEnableConfigPortal(boolean enable); // if true (default) then stop the config portal from autoConnect when wifi is saved void setDisableConfigPortal(boolean enable); - + // if true then find the AP with the best RSSI for the given SSID void setFindBestRSSI(boolean enabled); @@ -431,10 +443,10 @@ class WiFiManager // set ap channel void setWiFiAPChannel(int32_t channel); - + // set ap hidden void setWiFiAPHidden(bool hidden); // default false - + // clean connect, always disconnect before connecting void setCleanConnect(bool enable); // default false @@ -442,7 +454,7 @@ class WiFiManager // see _menutokens for ids void setMenu(std::vector& menu); void setMenu(const char* menu[], uint8_t size); - + // set the webapp title, default WiFiManager void setTitle(String title); @@ -451,10 +463,10 @@ class WiFiManager // get last connection result, includes autoconnect and wifisave uint8_t getLastConxResult(); - + // get a status as string - String getWLStatusString(uint8_t status); - String getWLStatusString(); + String getWLStatusString(uint8_t status); + String getWLStatusString(); // get wifi mode as string String getModeString(uint8_t mode); @@ -462,7 +474,7 @@ class WiFiManager // check if the module has a saved ap to connect to bool getWiFiIsSaved(); - // helper to get saved password, if persistent get stored, else get current if connected + // helper to get saved password, if persistent get stored, else get current if connected String getWiFiPass(bool persistent = true); // helper to get saved ssid, if persistent get stored, else get current if connected @@ -476,7 +488,7 @@ class WiFiManager // helper for html String htmlEntities(String str, bool whitespace = false); - + // set the country code for wifi settings, CN void setCountry(String cc); @@ -488,13 +500,13 @@ class WiFiManager // get default ap esp uses , esp_chipid etc String getDefaultAPName(); - + // set port of webserver, 80 void setHttpPort(uint16_t port); // check if config portal is active (true) bool getConfigPortalActive(); - + // check if web portal is active (true) bool getWebPortalActive(); @@ -508,11 +520,19 @@ class WiFiManager std::unique_ptr dnsServer; #if defined(ESP32) && defined(WM_WEBSERVERSHIM) + #ifdef WM_ASYNCWEBSERVER + using WM_WebServer = AsyncWebServer; + #else using WM_WebServer = WebServer; + #endif #else + #ifdef WM_ASYNCWEBSERVER + using WM_WebServer = AsyncWebServer; + #else using WM_WebServer = ESP8266WebServer; + #endif #endif - + std::unique_ptr server; protected: @@ -552,7 +572,7 @@ class WiFiManager unsigned long _configPortalTimeout = 0; // ms close config portal loop if set (depending on _cp/webClientCheck options) unsigned long _connectTimeout = 0; // ms stop trying to connect to ap if set unsigned long _saveTimeout = 0; // ms stop trying to connect to ap on saves, in case bugs in esp waitforconnectresult - + WiFiMode_t _usermode = WIFI_STA; // Default user mode String _wifissidprefix = FPSTR(S_ssidpre); // auto apname prefix prefix+chipid int _cpclosedelay = 2000; // delay before wifisave, prevents captive portal from closing to fast. @@ -587,7 +607,7 @@ class WiFiManager boolean _removeDuplicateAPs = true; // remove dup aps from wifiscan boolean _showPassword = false; // show or hide saved password on wifi form, might be a security issue! boolean _shouldBreakAfterConfig = false; // stop configportal on save failure - boolean _configPortalIsBlocking = true; // configportal enters blocking loop + boolean _configPortalIsBlocking = true; // configportal enters blocking loop boolean _enableCaptivePortal = true; // enable captive portal redirection boolean _userpersistent = true; // users preffered persistence to restore boolean _wifiAutoReconnect = true; // there is no platform getter for this, we must assume its true and make it so @@ -609,7 +629,9 @@ class WiFiManager String _title = FPSTR(S_brand); // app title - default WiFiManager // internal options - + + bool _rebootNeeded = false; // async reboot flag + // wifiscan notes // currently disabled due to issues with caching, sometimes first scan is empty esp32 wifi not init yet race, or portals hit server nonstop flood // The following are background wifi scanning optimizations @@ -620,12 +642,12 @@ class WiFiManager // async enables asyncronous scans, so they do not block anything // the refresh button bypasses cache // no aps found is problematic as scans are always going to want to run, leading to page load delays - // + // // These settings really only make sense with _preloadwifiscan true // but not limited to, we could run continuous background scans on various page hits, or xhr hits // which would be better coupled with asyncscan // atm preload is only done on root hit and startcp - // + // // preload scanning causes AP to delay showing for users, but also caches and lets the cp load faster once its open // my scan takes 7-10 seconds public: @@ -636,7 +658,7 @@ public: protected: boolean _autoforcerescan = false; // automatically force rescan if scan networks is 0, ignoring cache - + boolean _disableIpFields = false; // modify function of setShow_X_Fields(false), forces ip fields off instead of default show if set, eg. _staShowStaticFields=-1 String _wificountry = ""; // country code, @todo define in strings lang @@ -650,7 +672,9 @@ protected: void setupConfigPortal(); bool shutdownConfigPortal(); bool setupHostname(bool restart); - + void setupHTTPServer(); + void teardownHTTPServer(); + #ifdef NO_EXTRA_4K_HEAP boolean _tryWPS = false; // try WPS on save failure, unsupported void startWPS(); @@ -658,7 +682,6 @@ protected: bool startAP(); void setupDNSD(); - void setupHTTPServer(); uint8_t connectWifi(String ssid, String pass, bool connect = true); bool setSTAConfig(); @@ -667,38 +690,36 @@ protected: uint8_t waitForConnectResult(); uint8_t waitForConnectResult(uint32_t timeout); void updateConxResult(uint8_t status); - - // webserver handlers public: - void handleNotFound(); bool WiFi_scanNetworks(bool force,bool async); protected: - void HTTPSend(const String &content); - void handleRoot(); - void handleWifi(boolean scan); - void handleWifiSave(); - void handleInfo(); - void handleReset(); + // webserver handlers + void handleRoot(AsyncWebServerRequest *request); + void handleWifi(AsyncWebServerRequest *request,bool scan); + void handleWifiSave(AsyncWebServerRequest *request); + void handleInfo(AsyncWebServerRequest *request); + void handleReset(AsyncWebServerRequest *request); + void handleNotFound(AsyncWebServerRequest *request); + void handleExit(AsyncWebServerRequest *request); + void handleClose(AsyncWebServerRequest *request); + // void handleErase(AsyncWebServerRequest *request); + void handleErase(AsyncWebServerRequest *request, bool opt); + void handleParam(AsyncWebServerRequest *request); + void handleWiFiStatus(AsyncWebServerRequest *request); + void handleParamSave(AsyncWebServerRequest *request); + void doParamSave(AsyncWebServerRequest *request); - void handleExit(); - void handleClose(); - // void handleErase(); - void handleErase(boolean opt); - void handleParam(); - void handleWiFiStatus(); void handleRequest(); - void handleParamSave(); - void doParamSave(); + void HTTPSend(AsyncWebServerRequest *request, String page); - boolean captivePortal(); + boolean captivePortal(AsyncWebServerRequest *request); boolean configPortalHasTimeout(); uint8_t processConfigPortal(); void stopCaptivePortal(); - // OTA Update handler - void handleUpdate(); - void handleUpdating(); - void handleUpdateDone(); - + // OTA Update handler + void handleUpdate(AsyncWebServerRequest *request); + void handleUpdating(AsyncWebServerRequest *request,String filename, size_t index, uint8_t *data, size_t len, bool final); + void handleUpdateDone(AsyncWebServerRequest *request); // wifi platform abstractions bool WiFi_Mode(WiFiMode_t m); @@ -737,12 +758,12 @@ protected: #define WM_DISCONWORKAROUND #endif - #else + #else #define WM_NOCOUNTRY #endif #ifdef WM_NOCOUNTRY - #warning "ESP32 set country unavailable" + #warning "ESP32 set country unavailable" #endif @@ -783,9 +804,9 @@ protected: boolean portalTimeoutResult = false; boolean portalAbortResult = false; - boolean storeSTAmode = true; // option store persistent STA mode in connectwifi + boolean storeSTAmode = true; // option store persistent STA mode in connectwifi int timer = 0; // timer for debug throttle for numclients, and portal timeout messages - + // WiFiManagerParameter int _paramsCount = 0; int _max_params; @@ -798,7 +819,7 @@ protected: // build debuglevel support // @todo use DEBUG_ESP_x? - + // Set default debug level #ifndef WM_DEBUG_LEVEL #define WM_DEBUG_LEVEL WM_DEBUG_NOTIFY @@ -811,7 +832,7 @@ protected: #ifdef WM_DEBUG_LEVEL uint8_t _debugLevel = (uint8_t)WM_DEBUG_LEVEL; - #else + #else uint8_t _debugLevel = 0; // default debug level #endif @@ -843,7 +864,7 @@ protected: std::function _resetcallback; std::function _preotaupdatecallback; std::function _configportaltimeoutcallback; - + bool _hasCredentials = false; char _credUser[31] = {0}; char _credPassword[31] = {0}; diff --git a/pio_package.py b/pio_package_post.py similarity index 87% rename from pio_package.py rename to pio_package_post.py index 4115b8e..b7b5d1d 100644 --- a/pio_package.py +++ b/pio_package_post.py @@ -76,14 +76,4 @@ env.AddPostAction("$BUILD_DIR/bootloader.bin", copy_files) if env.GetProjectOption("custom_build") == 'debug': env.AddPostAction("$BUILD_DIR/firmware.elf", copy_files) -env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_bin) - -regex = r"\#define NUKI_HUB_DATE \"(.*)\"" -content_new = "" - -with open ('src/Config.h', 'r' ) as readfile: - file_content = readfile.read() - content_new = re.sub(regex, "#define NUKI_HUB_DATE \"unknownbuilddate\"", file_content, flags = re.M) - -with open('src/Config.h', 'w') as writefile: - writefile.write(content_new) \ No newline at end of file +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", merge_bin) \ No newline at end of file diff --git a/pio_package_pre.py b/pio_package_pre.py index 8b7152e..1cddbd0 100644 --- a/pio_package_pre.py +++ b/pio_package_pre.py @@ -12,23 +12,52 @@ def recursive_purge(dir, pattern): regex = r"\#define NUKI_HUB_DATE \"(.*)\"" content_new = "" +file_content = "" with open ('src/Config.h', 'r' ) as readfile: file_content = readfile.read() content_new = re.sub(regex, "#define NUKI_HUB_DATE \"" + datetime.now(timezone.utc).strftime("%Y-%m-%d") + "\"", file_content, flags = re.M) -with open('src/Config.h', 'w') as writefile: - writefile.write(content_new) +if content_new != file_content: + with open('src/Config.h', 'w') as writefile: + writefile.write(content_new) recursive_purge("managed_components", ".component_hash") -if env.get('BOARD_MCU') == "esp32": - board = "esp32dev" -else: - board = env.get('BOARD_MCU') +board = env.get('BOARD_MCU') if os.path.exists("sdkconfig." + board): - os.remove("sdkconfig." + board) + f1 = 0; + f2 = 0; + f3 = 0; + f4 = os.path.getmtime("sdkconfig." + board) + + if os.path.exists("sdkconfig.defaults." + board): + f1 = os.path.getmtime("sdkconfig.defaults." + board) + + if os.path.exists("sdkconfig.release.defaults"): + f2 = os.path.getmtime("sdkconfig.release.defaults") + + if os.path.exists("sdkconfig.defaults"): + f3 = os.path.getmtime("sdkconfig.defaults") + + if(f1 > f4 or f2 > f4 or f3 > f4): + os.remove("sdkconfig." + board) if os.path.exists("sdkconfig." + board + "_dbg"): - os.remove("sdkconfig." + board + "_dbg") \ No newline at end of file + f1 = 0; + f2 = 0; + f3 = 0; + f4 = os.path.getmtime("sdkconfig." + board + "_dbg") + + if os.path.exists("sdkconfig.defaults." + board): + f1 = os.path.getmtime("sdkconfig.defaults." + board) + + if os.path.exists("sdkconfig.debug.defaults"): + f2 = os.path.getmtime("sdkconfig.debug.defaults") + + if os.path.exists("sdkconfig.defaults"): + f3 = os.path.getmtime("sdkconfig.defaults") + + if(f1 > f4 or f2 > f4 or f3 > f4): + os.remove("sdkconfig." + board + "_dbg") \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 742e18a..22a50fd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = esp32dev +default_envs = esp32 boards_dir = boards [env] @@ -47,7 +47,8 @@ build_flags = -DNUKI_USE_LATEST_NIMBLE -DNUKI_NO_WDT_RESET -DNUKI_MUTEX_RECURSIVE - -DNUKI_64BIT_TIME + -DNUKI_64BIT_TIME + -DETH_SPI_SUPPORTS_NO_IRQ -Wno-ignored-qualifiers -Wno-missing-field-initializers -Wno-type-limits @@ -69,44 +70,58 @@ monitor_filters = esp32_exception_decoder time -[env:esp32dev] +[env:esp32] board = esp32dev board_build.cmake_extra_args = -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.release.defaults" extra_scripts = pre:pio_package_pre.py - post:pio_package.py + post:pio_package_post.py build_flags = ${env.build_flags} -DCONFIG_ASYNC_TCP_QUEUE_SIZE=128 - -DCONFIG_ASYNC_TCP_STACK_SIZE=4096 + -DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DWS_MAX_QUEUED_MESSAGES=128 -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE -DCONFIG_NIMBLE_CPP_LOG_LEVEL=0 -DCONFIG_BT_NIMBLE_LOG_LEVEL=0 [env:esp32-c3] -extends = env:esp32dev +extends = env:esp32 board = esp32-c3-devkitc-02 [env:esp32-s3] -extends = env:esp32dev +extends = env:esp32 board = nuki-esp32-s3 [env:esp32-c6] -extends = env:esp32dev +extends = env:esp32 board = esp32-c6-devkitm-1 +[env:esp32-h2] +extends = env:esp32 +board = esp32-h2-devkitm-1 +board_build.cmake_extra_args = + -DNUKI_TARGET_H2=y + -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.debug.defaults" +lib_ignore = + BLE + BluetoothSerial + SimpleBLE + WiFiProv + NimBLE-Arduino + ESPAsyncTCP-esphome + AsyncTCP_RP2040W + WiFiManager + [env:esp32-solo1] -extends = env:esp32dev +extends = env:esp32 board = esp32-solo1 board_build.cmake_extra_args = -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.release.defaults;sdkconfig.defaults.esp32-solo1" -build_flags = - ${env:esp32dev.build_flags} -[env:esp32dev_dbg] -extends = env:esp32dev +[env:esp32_dbg] +extends = env:esp32 custom_build = debug board_build.cmake_extra_args = -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.debug.defaults" @@ -123,7 +138,7 @@ build_flags = ;-DDEBUG_NUKI_HEX_DATA -DDEBUG_NUKI_READABLE_DATA -DCONFIG_ASYNC_TCP_QUEUE_SIZE=1024 - -DCONFIG_ASYNC_TCP_STACK_SIZE=8192 + -DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DWS_MAX_QUEUED_MESSAGES=512 [env:esp32-c3_dbg] @@ -144,7 +159,7 @@ build_flags = ;-DDEBUG_NUKI_HEX_DATA -DDEBUG_NUKI_READABLE_DATA -DCONFIG_ASYNC_TCP_QUEUE_SIZE=1024 - -DCONFIG_ASYNC_TCP_STACK_SIZE=8192 + -DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DWS_MAX_QUEUED_MESSAGES=512 [env:esp32-c6_dbg] @@ -165,7 +180,29 @@ build_flags = ;-DDEBUG_NUKI_HEX_DATA -DDEBUG_NUKI_READABLE_DATA -DCONFIG_ASYNC_TCP_QUEUE_SIZE=1024 - -DCONFIG_ASYNC_TCP_STACK_SIZE=8192 + -DCONFIG_ASYNC_TCP_STACK_SIZE=24576 + -DWS_MAX_QUEUED_MESSAGES=512 + +[env:esp32-h2_dbg] +extends = env:esp32-h2 +custom_build = debug +board_build.cmake_extra_args = + -DNUKI_TARGET_H2=y + -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.debug.defaults" +build_flags = + ${env.build_flags} + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG + -DCONFIG_NIMBLE_CPP_LOG_LEVEL=0 + -DCONFIG_BT_NIMBLE_LOG_LEVEL=0 + -DDEBUG_NUKIHUB + -DDEBUG_SENSE_NUKI + -DDEBUG_NUKI_COMMAND + -DDEBUG_NUKI_CONNECT + -DDEBUG_NUKI_COMMUNICATION + ;-DDEBUG_NUKI_HEX_DATA + -DDEBUG_NUKI_READABLE_DATA + -DCONFIG_ASYNC_TCP_QUEUE_SIZE=1024 + -DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DWS_MAX_QUEUED_MESSAGES=512 [env:esp32-s3_dbg] @@ -186,7 +223,7 @@ build_flags = ;-DDEBUG_NUKI_HEX_DATA -DDEBUG_NUKI_READABLE_DATA -DCONFIG_ASYNC_TCP_QUEUE_SIZE=1024 - -DCONFIG_ASYNC_TCP_STACK_SIZE=8192 + -DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DWS_MAX_QUEUED_MESSAGES=512 [env:esp32-solo1_dbg] @@ -207,5 +244,5 @@ build_flags = ;-DDEBUG_NUKI_HEX_DATA -DDEBUG_NUKI_READABLE_DATA -DCONFIG_ASYNC_TCP_QUEUE_SIZE=1024 - -DCONFIG_ASYNC_TCP_STACK_SIZE=8192 + -DCONFIG_ASYNC_TCP_STACK_SIZE=24576 -DWS_MAX_QUEUED_MESSAGES=512 \ No newline at end of file diff --git a/resources/how-to-flash.txt b/resources/how-to-flash.txt index 1f03099..6d11dbe 100644 --- a/resources/how-to-flash.txt +++ b/resources/how-to-flash.txt @@ -50,6 +50,13 @@ e000 boot_app0.bin 10000 nuki_hub_esp32c6.bin 280000 nuki_hub_updater_esp32c6.bin +ESP32-H2 +e000 boot_app0.bin +0 bootloader.bin +8000 nuki_hub.partitions.bin +10000 nuki_hub_esp32h2.bin +280000 nuki_hub_updater_esp32h2.bin + ESP32-SOLO1 e000 boot_app0.bin 1000 bootloader.bin @@ -85,6 +92,10 @@ esptool.py --chip esp32c3 --port /dev/ttyUSB0 --baud 921600 --before default_res esptool.py --chip esp32c6 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq keep --flash_size detect 0xe000 boot_app0.bin 0x0 bootloader.bin 0x10000 nuki_hub_esp32c6.bin 0x280000 nuki_hub_updater_esp32c6.bin 0x8000 nuki_hub.partitions.bin +## ESP32-H2 + +esptool.py --chip esp32c6 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq keep --flash_size detect 0xe000 boot_app0.bin 0x0 bootloader.bin 0x10000 nuki_hub_esp32h2.bin 0x280000 nuki_hub_updater_esp32h2.bin 0x8000 nuki_hub.partitions.bin + ## ESP32-SOLO1 esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq keep --flash_size detect 0xe000 boot_app0.bin 0x1000 bootloader.bin 0x10000 nuki_hub_esp32-solo1.bin 0x280000 nuki_hub_updater_esp32-solo1.bin 0x8000 nuki_hub.partitions.bin diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 1577e67..5020dd1 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -65,8 +65,6 @@ CONFIG_BT_NIMBLE_MSYS_BUF_FROM_HEAP=n CONFIG_BT_NIMBLE_50_FEATURE_SUPPORT=n CONFIG_IEEE802154_ENABLED=n CONFIG_ARDUINO_SELECTIVE_COMPILATION=y -CONFIG_ARDUINO_SELECTIVE_HTTPClient=n -CONFIG_ARDUINO_SELECTIVE_NetworkClientSecure=n CONFIG_ARDUINO_SELECTIVE_WebServer=n CONFIG_ARDUINO_SELECTIVE_WiFiProv=n CONFIG_ARDUINO_SELECTIVE_BLE=n @@ -75,4 +73,11 @@ CONFIG_ARDUINO_SELECTIVE_SimpleBLE=n CONFIG_HEAP_TASK_TRACKING=n CONFIG_LOG_COLORS=n CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=n -CONFIG_LOG_MAXIMUM_LEVEL=4 \ No newline at end of file +CONFIG_LOG_MAXIMUM_LEVEL=4 +CONFIG_ETH_ENABLED=y +CONFIG_ETH_USE_SPI_ETHERNET=y +CONFIG_ETH_SPI_ETHERNET_W5500=y +CONFIG_ETH_SPI_ETHERNET_DM9051=y +CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL=y +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 483bc0c..db8e0c9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,5 +2,8 @@ # without default 'CMakeLists.txt' file. FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*) - +if(DEFINED NUKI_TARGET_H2) + list(REMOVE_ITEM app_sources "${CMAKE_SOURCE_DIR}/src/networkDevices/WifiDevice.cpp") + list(REMOVE_ITEM app_sources "${CMAKE_SOURCE_DIR}/src/networkDevices/WifiDevice.h") +endif() idf_component_register(SRCS ${app_sources}) diff --git a/src/Config.h b/src/Config.h index 7b73ef4..c6eb3c2 100644 --- a/src/Config.h +++ b/src/Config.h @@ -4,7 +4,7 @@ #define NUKI_HUB_VERSION "9.01" #define NUKI_HUB_BUILD "unknownbuildnr" -#define NUKI_HUB_DATE "2024-08-12" +#define NUKI_HUB_DATE "unknownbuilddate" #define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest" #define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json" @@ -48,6 +48,19 @@ #define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32c6.bin" #define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32c6.bin" #define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32c6.bin" +#elif defined(CONFIG_IDF_TARGET_ESP32H2) +#define GITHUB_LATEST_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32h2.bin" +#define GITHUB_LATEST_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_updater_esp32h2.bin" +#define GITHUB_BETA_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/beta/nuki_hub_esp32h2.bin" +#define GITHUB_BETA_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/beta/nuki_hub_updater_esp32h2.bin" +#define GITHUB_MASTER_RELEASE_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/master/nuki_hub_esp32h2.bin" +#define GITHUB_MASTER_UPDATER_BINARY_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/master/nuki_hub_updater_esp32h2.bin" +#define GITHUB_LATEST_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/nuki_hub_esp32h2.bin" +#define GITHUB_LATEST_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/nuki_hub_updater_esp32h2.bin" +#define GITHUB_BETA_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_esp32h2.bin" +#define GITHUB_BETA_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/beta/nuki_hub_updater_esp32h2.bin" +#define GITHUB_MASTER_RELEASE_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_esp32h2.bin" +#define GITHUB_MASTER_UPDATER_BINARY_URL_DBG (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/debug/master/nuki_hub_updater_esp32h2.bin" #else #if defined(CONFIG_FREERTOS_UNICORE) #define GITHUB_LATEST_RELEASE_BINARY_URL "https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/nuki_hub_esp32-solo1.bin" diff --git a/src/Gpio.cpp b/src/Gpio.cpp index 2a55284..a877e44 100644 --- a/src/Gpio.cpp +++ b/src/Gpio.cpp @@ -6,6 +6,9 @@ #include "PreferencesKeys.h" #include "RestartReason.h" #include "Gpio2Go.h" +#include "networkDevices/LAN8720Definitions.h" +#include "networkDevices/DM9051Definitions.h" +#include "networkDevices/W5500Definitions.h" Gpio* Gpio::_inst = nullptr; int64_t Gpio::_debounceTs = 0; @@ -97,6 +100,8 @@ void Gpio::init() case PinRole::GeneralInputPullUp: Gpio2Go::configurePin(entry.pin, PinMode::InputPullup, InterruptMode::Change, 300); break; + case PinRole::Ethernet: + break; default: break; } @@ -112,6 +117,7 @@ const std::vector& Gpio::availablePins() const void Gpio::loadPinConfiguration() { + Log->println("Load GPIO configuration"); size_t storedLength = _preferences->getBytesLength(preference_gpio_configuration); if(storedLength == 0) { @@ -133,32 +139,162 @@ void Gpio::loadPinConfiguration() _pinConfiguration.clear(); _pinConfiguration.reserve(numEntries); + std::vector disabledPins = getDisabledPins(); + for(int i=0; i < numEntries; i++) { PinEntry entry; entry.pin = serialized[i * 2]; - entry.role = (PinRole) serialized[(i * 2 + 1)]; - if(entry.role != PinRole::Disabled) + Log->print(F("Pin ")); + Log->println(entry.pin); + + if(std::find(disabledPins.begin(), disabledPins.end(), entry.pin) == disabledPins.end()) { - _pinConfiguration.push_back(entry); + if(entry.role == PinRole::Ethernet) entry.role = PinRole::Disabled; + entry.role = (PinRole) serialized[(i * 2 + 1)]; + Log->println("Not found in Ethernet disabled pins"); + Log->print(F("Role: ")); + Log->println(getRoleDescription(entry.role)); } + else + { + entry.role = PinRole::Ethernet; + Log->println("Found in Ethernet disabled pins"); + Log->print(F("Role: ")); + Log->println(getRoleDescription(entry.role)); + } + if(entry.role != PinRole::Disabled) _pinConfiguration.push_back(entry); } } +const std::vector Gpio::getDisabledPins() const +{ + std::vector disabledPins; + + switch(_preferences->getInt(preference_network_hardware, 0)) + { + case 2: + disabledPins.push_back(ETH_PHY_CS_GENERIC_W5500); + disabledPins.push_back(ETH_PHY_IRQ_GENERIC_W5500); + disabledPins.push_back(ETH_PHY_RST_GENERIC_W5500); + disabledPins.push_back(ETH_PHY_SPI_SCK_GENERIC_W5500); + disabledPins.push_back(ETH_PHY_SPI_MISO_GENERIC_W5500); + disabledPins.push_back(ETH_PHY_SPI_MOSI_GENERIC_W5500); + break; + case 3: + disabledPins.push_back(ETH_PHY_CS_M5_W5500); + disabledPins.push_back(ETH_PHY_IRQ_M5_W5500); + disabledPins.push_back(ETH_PHY_RST_M5_W5500); + disabledPins.push_back(ETH_PHY_SPI_SCK_M5_W5500); + disabledPins.push_back(ETH_PHY_SPI_MISO_M5_W5500); + disabledPins.push_back(ETH_PHY_SPI_MOSI_M5_W5500); + break; + case 10: + disabledPins.push_back(ETH_PHY_CS_M5_W5500_S3); + disabledPins.push_back(ETH_PHY_IRQ_M5_W5500); + disabledPins.push_back(ETH_PHY_RST_M5_W5500); + disabledPins.push_back(ETH_PHY_SPI_SCK_M5_W5500_S3); + disabledPins.push_back(ETH_PHY_SPI_MISO_M5_W5500_S3); + disabledPins.push_back(ETH_PHY_SPI_MOSI_M5_W5500_S3); + break; + case 9: + disabledPins.push_back(ETH_PHY_CS_ETH01EVO); + disabledPins.push_back(ETH_PHY_IRQ_ETH01EVO); + disabledPins.push_back(ETH_PHY_RST_ETH01EVO); + disabledPins.push_back(ETH_PHY_SPI_SCK_ETH01EVO); + disabledPins.push_back(ETH_PHY_SPI_MISO_ETH01EVO); + disabledPins.push_back(ETH_PHY_SPI_MOSI_ETH01EVO); + break; + case 6: + disabledPins.push_back(ETH_PHY_CS_M5_W5500); + disabledPins.push_back(ETH_PHY_IRQ_M5_W5500); + disabledPins.push_back(ETH_PHY_RST_M5_W5500); + disabledPins.push_back(ETH_PHY_SPI_SCK_M5_W5500); + disabledPins.push_back(ETH_PHY_SPI_MISO_M5_W5500); + disabledPins.push_back(ETH_PHY_SPI_MOSI_M5_W5500); + break; + case 11: + disabledPins.push_back(_preferences->getInt(preference_network_custom_cs, -1)); + disabledPins.push_back(_preferences->getInt(preference_network_custom_irq, -1)); + disabledPins.push_back(_preferences->getInt(preference_network_custom_rst, -1)); + disabledPins.push_back(_preferences->getInt(preference_network_custom_sck, -1)); + disabledPins.push_back(_preferences->getInt(preference_network_custom_miso, -1)); + disabledPins.push_back(_preferences->getInt(preference_network_custom_mosi, -1)); + disabledPins.push_back(_preferences->getInt(preference_network_custom_pwr, -1)); + disabledPins.push_back(_preferences->getInt(preference_network_custom_mdc, -1)); + disabledPins.push_back(_preferences->getInt(preference_network_custom_mdio, -1)); + break; + #if defined(CONFIG_IDF_TARGET_ESP32) + case 4: + disabledPins.push_back(12); + disabledPins.push_back(ETH_RESET_PIN_LAN8720); + disabledPins.push_back(ETH_PHY_MDC_LAN8720); + disabledPins.push_back(ETH_PHY_MDIO_LAN8720); + break; + case 5: + disabledPins.push_back(16); + disabledPins.push_back(ETH_RESET_PIN_LAN8720); + disabledPins.push_back(ETH_PHY_MDC_LAN8720); + disabledPins.push_back(ETH_PHY_MDIO_LAN8720); + break; + case 8: + disabledPins.push_back(5); + disabledPins.push_back(ETH_RESET_PIN_LAN8720); + disabledPins.push_back(ETH_PHY_MDC_LAN8720); + disabledPins.push_back(ETH_PHY_MDIO_LAN8720); + break; + case 7: + disabledPins.push_back(-1); + disabledPins.push_back(ETH_RESET_PIN_LAN8720); + disabledPins.push_back(ETH_PHY_MDC_LAN8720); + disabledPins.push_back(ETH_PHY_MDIO_LAN8720); + break; + #endif + default: + break; + } + + Log->print(F("GPIO Ethernet disabled pins:")); + for_each_n(disabledPins.begin(), disabledPins.size(), + [](int x) { Log->print(" "); Log->print(x); }); + Log->println(); + return disabledPins; +} + void Gpio::savePinConfiguration(const std::vector &pinConfiguration) { + Log->println("Save GPIO configuration"); int8_t serialized[std::max(pinConfiguration.size() * 2, _preferences->getBytesLength(preference_gpio_configuration))]; memset(serialized, 0, sizeof(serialized)); + std::vector disabledPins = getDisabledPins(); + int len = pinConfiguration.size(); for(int i=0; i < len; i++) { const auto& entry = pinConfiguration[i]; + Log->print(F("Pin ")); + Log->println(entry.pin); - if(entry.role != PinRole::Disabled) + if(std::find(disabledPins.begin(), disabledPins.end(), entry.pin) != disabledPins.end()) { serialized[i * 2] = entry.pin; - serialized[i * 2 + 1] = (int8_t) entry.role; + serialized[i * 2 + 1] = (int8_t)PinRole::Ethernet; + Log->println("Found in Ethernet disabled pins"); + Log->print(F("Role: ")); + Log->println(getRoleDescription(PinRole::Ethernet)); + + } + else + { + if(entry.role != PinRole::Disabled && entry.role != PinRole::Ethernet) + { + serialized[i * 2] = entry.pin; + serialized[i * 2 + 1] = (int8_t) entry.role; + Log->println("Not found in Ethernet disabled pins"); + Log->print(F("Role: ")); + Log->println(getRoleDescription(entry.role)); + } } } @@ -228,6 +364,8 @@ String Gpio::getRoleDescription(PinRole role) const return "General input (Pull-down)"; case PinRole::GeneralInputPullUp: return "General input (Pull-up)"; + case PinRole::Ethernet: + return "Ethernet"; default: return "Unknown"; } diff --git a/src/Gpio.h b/src/Gpio.h index d04a39c..a3458a9 100644 --- a/src/Gpio.h +++ b/src/Gpio.h @@ -26,7 +26,8 @@ enum class PinRole OutputHighRtoOrCmActive, GeneralOutput, GeneralInputPullDown, - GeneralInputPullUp + GeneralInputPullUp, + Ethernet }; enum class GpioAction @@ -66,6 +67,7 @@ public: const std::vector& availablePins() const; const std::vector& pinConfiguration() const; + const std::vector getDisabledPins() const; const PinRole getPinRole(const int& pin) const; String getRoleDescription(PinRole role) const; @@ -79,7 +81,22 @@ private: void IRAM_ATTR notify(const GpioAction& action, const int& pin); static void inputCallback(const int & pin); + #if defined(CONFIG_IDF_TARGET_ESP32C3) + //Based on https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf + const std::vector _availablePins = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 18, 19, 20, 21 }; + #elif defined(CONFIG_IDF_TARGET_ESP32S3) + //Based on https://github.com/atomic14/esp32-s3-pinouts?tab=readme-ov-file and https://docs.espressif.com/projects/esp-idf/en/v5.3/esp32s3/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf + const std::vector _availablePins = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48 }; + #elif defined(CONFIG_IDF_TARGET_ESP32C6) + //Based on https://docs.espressif.com/projects/esp-idf/en/v5.3/esp32c6/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf + const std::vector _availablePins = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23 }; + #elif defined(CONFIG_IDF_TARGET_ESP32H2) + //Based on https://docs.espressif.com/projects/esp-idf/en/v5.3/esp32h2/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_en.pdf + const std::vector _availablePins = { 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 22, 23, 24, 25, 26, 27 }; + #else + //Based on https://randomnerdtutorials.com/esp32-pinout-reference-gpios/ const std::vector _availablePins = { 2, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32, 33 }; + #endif const std::vector _allRoles = { PinRole::Disabled, @@ -101,7 +118,8 @@ private: PinRole::OutputHighRtoOrCmActive, PinRole::GeneralInputPullDown, PinRole::GeneralInputPullUp, - PinRole::GeneralOutput + PinRole::GeneralOutput, + PinRole::Ethernet }; std::vector _pinConfiguration; diff --git a/src/NukiNetwork.cpp b/src/NukiNetwork.cpp index 34ec21f..d64b73f 100644 --- a/src/NukiNetwork.cpp +++ b/src/NukiNetwork.cpp @@ -1,15 +1,14 @@ #include "NukiNetwork.h" #include "PreferencesKeys.h" -#include "networkDevices/W5500Device.h" -#include "networkDevices/WifiDevice.h" #include "Logger.h" #include "Config.h" #include "RestartReason.h" #include #include -#if defined(CONFIG_IDF_TARGET_ESP32) -#include "networkDevices/EthLan8720Device.h" +#ifndef CONFIG_IDF_TARGET_ESP32H2 +#include "networkDevices/WifiDevice.h" #endif +#include "networkDevices/EthernetDevice.h" #ifndef NUKI_HUB_UPDATER #include @@ -18,6 +17,9 @@ NukiNetwork* NukiNetwork::_inst = nullptr; RTC_NOINIT_ATTR char WiFi_fallbackDetect[14]; +extern bool forceEnableWebServer; +extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start"); +extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end"); #ifndef NUKI_HUB_UPDATER NukiNetwork::NukiNetwork(Preferences *preferences, PresenceDetection* presenceDetection, Gpio* gpio, const String& maintenancePathPrefix, char* buffer, size_t bufferSize) @@ -39,6 +41,7 @@ NukiNetwork::NukiNetwork(Preferences *preferences) _inst = this; _hostname = _preferences->getString(preference_hostname); + _webEnabled = _preferences->getBool(preference_webserver_enabled, true); #ifndef NUKI_HUB_UPDATER memset(_maintenancePathPrefix, 0, sizeof(_maintenancePathPrefix)); @@ -65,20 +68,34 @@ NukiNetwork::NukiNetwork(Preferences *preferences) void NukiNetwork::setupDevice() { _ipConfiguration = new IPConfiguration(_preferences); + int hardwareDetect = _preferences->getInt(preference_network_hardware, 0); + Log->print(F("Hardware detect : ")); + Log->println(hardwareDetect); - int hardwareDetect = _preferences->getInt(preference_network_hardware); - - Log->print(F("Hardware detect : ")); Log->println(hardwareDetect); + _firstBootAfterDeviceChange = _preferences->getBool(preference_ntw_reconfigure, false); if(hardwareDetect == 0) { + #ifndef CONFIG_IDF_TARGET_ESP32H2 hardwareDetect = 1; + #else + hardwareDetect = 11; + _preferences->putInt(preference_network_custom_addr, 1); + _preferences->putInt(preference_network_custom_cs, 8); + _preferences->putInt(preference_network_custom_irq, 9); + _preferences->putInt(preference_network_custom_rst, 10); + _preferences->putInt(preference_network_custom_sck, 11); + _preferences->putInt(preference_network_custom_miso, 12); + _preferences->putInt(preference_network_custom_mosi, 13); + _preferences->putBool(preference_ntw_reconfigure, true); + #endif _preferences->putInt(preference_network_hardware, hardwareDetect); } if(strcmp(WiFi_fallbackDetect, "wifi_fallback") == 0) { - if(_preferences->getBool(preference_network_wifi_fallback_disabled)) + #ifndef CONFIG_IDF_TARGET_ESP32H2 + if(_preferences->getBool(preference_network_wifi_fallback_disabled) && !_firstBootAfterDeviceChange) { Log->println(F("Failed to connect to network. Wi-Fi fallback is disabled, rebooting.")); memset(WiFi_fallbackDetect, 0, sizeof(WiFi_fallbackDetect)); @@ -88,6 +105,14 @@ void NukiNetwork::setupDevice() Log->println(F("Switching to Wi-Fi device as fallback.")); _networkDeviceType = NetworkDeviceType::WiFi; + #else + int custEth = _preferences->getInt(preference_network_custom_phy, 0); + + if(custEth<3) custEth++; + else custEth = 0; + _preferences->putInt(preference_network_custom_phy, custEth); + _preferences->putBool(preference_ntw_reconfigure, true); + #endif } else { @@ -99,14 +124,13 @@ void NukiNetwork::setupDevice() _networkDeviceType = NetworkDeviceType::WiFi; break; case 2: - Log->print(F("Generic W5500")); + Log->println(F("Generic W5500")); _networkDeviceType = NetworkDeviceType::W5500; break; case 3: Log->println(F("W5500 on M5Stack Atom POE")); - _networkDeviceType = NetworkDeviceType::W5500; + _networkDeviceType = NetworkDeviceType::W5500M5; break; - #if defined(CONFIG_IDF_TARGET_ESP32) case 4: Log->println(F("Olimex ESP32-POE / ESP-POE-ISO")); _networkDeviceType = NetworkDeviceType::Olimex_LAN8720; @@ -127,7 +151,23 @@ void NukiNetwork::setupDevice() Log->println(F("GL-S10")); _networkDeviceType = NetworkDeviceType::GL_S10; break; - #endif + case 9: + Log->println(F("ETH01-Evo")); + _networkDeviceType = NetworkDeviceType::ETH01_Evo; + break; + case 10: + Log->println(F("W5500 on M5Stack Atom POE S3")); + _networkDeviceType = NetworkDeviceType::W5500M5S3; + break; + case 11: + Log->println(F("Custom LAN Module")); + if(_preferences->getInt(preference_network_custom_phy, 0) > 0) _networkDeviceType = NetworkDeviceType::CUSTOM; + else + { + Log->println(F("Custom LAN Module not setup correctly, falling back to Wi-Fi")); + _networkDeviceType = NetworkDeviceType::WiFi; + } + break; default: Log->println(F("Unknown hardware selected, falling back to Wi-Fi.")); _networkDeviceType = NetworkDeviceType::WiFi; @@ -138,31 +178,207 @@ void NukiNetwork::setupDevice() switch (_networkDeviceType) { case NetworkDeviceType::W5500: - _device = new W5500Device(_hostname, _preferences, _ipConfiguration, hardwareDetect); + _device = new EthernetDevice(_hostname, _preferences, _ipConfiguration, "Generic W5500", + ETH_PHY_ADDR_W5500, + ETH_PHY_CS_GENERIC_W5500, + ETH_PHY_IRQ_GENERIC_W5500, + ETH_PHY_RST_GENERIC_W5500, + ETH_PHY_SPI_SCK_GENERIC_W5500, + ETH_PHY_SPI_MISO_GENERIC_W5500, + ETH_PHY_SPI_MOSI_GENERIC_W5500, + ETH_PHY_SPI_FREQ_MHZ, + ETH_PHY_W5500); + break; + case NetworkDeviceType::W5500M5: + _device = new EthernetDevice(_hostname, _preferences, _ipConfiguration, "M5Stack Atom POE", + ETH_PHY_ADDR_W5500, + ETH_PHY_CS_M5_W5500, + ETH_PHY_IRQ_M5_W5500, + ETH_PHY_RST_M5_W5500, + ETH_PHY_SPI_SCK_M5_W5500, + ETH_PHY_SPI_MISO_M5_W5500, + ETH_PHY_SPI_MOSI_M5_W5500, + ETH_PHY_SPI_FREQ_MHZ, + ETH_PHY_W5500); + break; + case NetworkDeviceType::W5500M5S3: + _device = new EthernetDevice(_hostname, _preferences, _ipConfiguration, "M5Stack Atom POE S3", + ETH_PHY_ADDR_W5500, + ETH_PHY_CS_M5_W5500_S3, + ETH_PHY_IRQ_M5_W5500, + ETH_PHY_RST_M5_W5500, + ETH_PHY_SPI_SCK_M5_W5500_S3, + ETH_PHY_SPI_MISO_M5_W5500_S3, + ETH_PHY_SPI_MOSI_M5_W5500_S3, + ETH_PHY_SPI_FREQ_MHZ, + ETH_PHY_W5500); + break; + case NetworkDeviceType::ETH01_Evo: + _device = new EthernetDevice(_hostname, _preferences, _ipConfiguration, "ETH01-Evo", + ETH_PHY_ADDR_ETH01EVO, + ETH_PHY_CS_ETH01EVO, + ETH_PHY_IRQ_ETH01EVO, + ETH_PHY_RST_ETH01EVO, + ETH_PHY_SPI_SCK_ETH01EVO, + ETH_PHY_SPI_MISO_ETH01EVO, + ETH_PHY_SPI_MOSI_ETH01EVO, + ETH_PHY_SPI_FREQ_MHZ, + ETH_PHY_TYPE_DM9051); + break; + case NetworkDeviceType::M5STACK_PoESP32_Unit: + _device = new EthernetDevice(_hostname, _preferences, _ipConfiguration, "M5STACK PoESP32 Unit", + ETH_PHY_ADDR_W5500, + ETH_PHY_CS_M5_W5500, + ETH_PHY_IRQ_M5_W5500, + ETH_PHY_RST_M5_W5500, + ETH_PHY_SPI_SCK_M5_W5500, + ETH_PHY_SPI_MISO_M5_W5500, + ETH_PHY_SPI_MOSI_M5_W5500, + ETH_PHY_SPI_FREQ_MHZ, + ETH_PHY_W5500); + break; + case NetworkDeviceType::CUSTOM: + { + int custPHY = _preferences->getInt(preference_network_custom_phy, 0); + + if(custPHY >= 1 && custPHY <= 3) + { + std::string custName; + eth_phy_type_t custEthtype; + + switch(custPHY) + { + case 1: + custName = "Custom (W5500)"; + custEthtype = ETH_PHY_W5500; + break; + case 2: + custName = "Custom (DN9051)"; + custEthtype = ETH_PHY_DM9051; + break; + case 3: + custName = "Custom (KSZ8851SNL)"; + custEthtype = ETH_PHY_KSZ8851; + break; + default: + custName = "Custom (W5500)"; + custEthtype = ETH_PHY_W5500; + break; + } + + _device = new EthernetDevice(_hostname, _preferences, _ipConfiguration, custName, + _preferences->getInt(preference_network_custom_addr, -1), + _preferences->getInt(preference_network_custom_cs, -1), + _preferences->getInt(preference_network_custom_irq, -1), + _preferences->getInt(preference_network_custom_rst, -1), + _preferences->getInt(preference_network_custom_sck, -1), + _preferences->getInt(preference_network_custom_miso, -1), + _preferences->getInt(preference_network_custom_mosi, -1), + ETH_PHY_SPI_FREQ_MHZ, + custEthtype); + } + #if defined(CONFIG_IDF_TARGET_ESP32) + else if(custPHY >= 4 && custPHY <= 9) + { + std::string custName; + eth_phy_type_t custEthtype; + eth_clock_mode_t custCLK; + + switch(custPHY) + { + case 4: + custName = "Custom (LAN8720)"; + custEthtype = ETH_PHY_TYPE_LAN8720; + break; + case 5: + custName = "Custom (RTL8201)"; + custEthtype = ETH_PHY_RTL8201; + break; + case 6: + custName = "Custom (TLK110)"; + custEthtype = ETH_PHY_TLK110; + break; + case 7: + custName = "Custom (DP83848)"; + custEthtype = ETH_PHY_DP83848; + break; + case 8: + custName = "Custom (KSZ8041)"; + custEthtype = ETH_PHY_KSZ8041; + break; + case 9: + custName = "Custom (KSZ8081)"; + custEthtype = ETH_PHY_KSZ8081; + break; + default: + custName = "Custom (LAN8720)"; + custEthtype = ETH_PHY_TYPE_LAN8720; + break; + } + + int custCLKpref = _preferences->getInt(preference_network_custom_clk, 0); + + switch(custCLKpref) + { + case 0: + custCLK = ETH_CLOCK_GPIO0_IN; + break; + case 2: + custCLK = ETH_CLOCK_GPIO16_OUT; + break; + case 3: + custCLK = ETH_CLOCK_GPIO17_OUT; + break; + default: + custCLK = ETH_CLOCK_GPIO17_OUT; + break; + } + _device = new EthernetDevice(_hostname, _preferences, _ipConfiguration, custName, _preferences->getInt(preference_network_custom_addr, -1), _preferences->getInt(preference_network_custom_pwr, -1), _preferences->getInt(preference_network_custom_mdc, -1), _preferences->getInt(preference_network_custom_mdio, -1), custEthtype, custCLK); + } + #endif + #ifndef CONFIG_IDF_TARGET_ESP32H2 + else + { + _device = new WifiDevice(_hostname, _preferences, _ipConfiguration); + } + #endif + } break; #if defined(CONFIG_IDF_TARGET_ESP32) case NetworkDeviceType::Olimex_LAN8720: - _device = new EthLan8720Device(_hostname, _preferences, _ipConfiguration, "Olimex (LAN8720)", ETH_PHY_ADDR, 12, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_TYPE, ETH_CLOCK_GPIO17_OUT); + _device = new EthernetDevice(_hostname, _preferences, _ipConfiguration, "Olimex (LAN8720)", ETH_PHY_ADDR_LAN8720, 12, ETH_PHY_MDC_LAN8720, ETH_PHY_MDIO_LAN8720, ETH_PHY_TYPE_LAN8720, ETH_CLOCK_GPIO17_OUT); break; case NetworkDeviceType::WT32_LAN8720: - _device = new EthLan8720Device(_hostname, _preferences, _ipConfiguration, "WT32-ETH01", 1, 16); - break; - case NetworkDeviceType::M5STACK_PoESP32_Unit: - _device = new EthLan8720Device(_hostname, _preferences, _ipConfiguration, "M5STACK PoESP32 Unit", 1, 5, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_IP101); + _device = new EthernetDevice(_hostname, _preferences, _ipConfiguration, "WT32-ETH01", 1, 16); break; case NetworkDeviceType::GL_S10: - _device = new EthLan8720Device(_hostname, _preferences, _ipConfiguration, "GL-S10", 1, 5, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_IP101, ETH_CLOCK_GPIO0_IN); + _device = new EthernetDevice(_hostname, _preferences, _ipConfiguration, "GL-S10", 1, 5, ETH_PHY_MDC_LAN8720, ETH_PHY_MDIO_LAN8720, ETH_PHY_IP101, ETH_CLOCK_GPIO0_IN); break; case NetworkDeviceType::LilyGO_T_ETH_POE: - _device = new EthLan8720Device(_hostname, _preferences, _ipConfiguration, "LilyGO T-ETH-POE", 0, -1, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_TYPE, ETH_CLOCK_GPIO17_OUT); + _device = new EthernetDevice(_hostname, _preferences, _ipConfiguration, "LilyGO T-ETH-POE", 0, -1, ETH_PHY_MDC_LAN8720, ETH_PHY_MDIO_LAN8720, ETH_PHY_TYPE_LAN8720, ETH_CLOCK_GPIO17_OUT); break; #endif + #ifndef CONFIG_IDF_TARGET_ESP32H2 case NetworkDeviceType::WiFi: _device = new WifiDevice(_hostname, _preferences, _ipConfiguration); break; default: _device = new WifiDevice(_hostname, _preferences, _ipConfiguration); break; + #else + default: + _device = new EthernetDevice(_hostname, _preferences, _ipConfiguration, "Custom (W5500)", + _preferences->getInt(preference_network_custom_addr, -1), + _preferences->getInt(preference_network_custom_cs, -1), + _preferences->getInt(preference_network_custom_irq, -1), + _preferences->getInt(preference_network_custom_rst, -1), + _preferences->getInt(preference_network_custom_sck, -1), + _preferences->getInt(preference_network_custom_miso, -1), + _preferences->getInt(preference_network_custom_mosi, -1), + ETH_PHY_SPI_FREQ_MHZ, + ETH_PHY_W5500); + break; + #endif } #ifndef NUKI_HUB_UPDATER @@ -243,6 +459,7 @@ void NukiNetwork::initialize() _rssiPublishInterval = _preferences->getInt(preference_rssi_publish_interval, 0) * 1000; _hostname = _preferences->getString(preference_hostname, ""); _discoveryTopic = _preferences->getString(preference_mqtt_hass_discovery, ""); + _mqttPort = _preferences->getInt(preference_mqtt_broker_port, 1883); if(_hostname == "") { @@ -370,10 +587,8 @@ bool NukiNetwork::update() _device->mqttDisconnect(true); } - if(_restartOnDisconnect && (esp_timer_get_time() / 1000) > 60000) - { - restartEsp(RestartReason::RestartOnDisconnectWatchdog); - } + if(!_webEnabled) forceEnableWebServer = true; + if(_restartOnDisconnect && (esp_timer_get_time() / 1000) > 60000) restartEsp(RestartReason::RestartOnDisconnectWatchdog); Log->println(F("Network not connected. Trying reconnect.")); ReconnectStatus reconnectStatus = _device->reconnect(true); @@ -415,6 +630,13 @@ bool NukiNetwork::update() return false; } _mqttConnectCounter = 0; + if(forceEnableWebServer && !_webEnabled) + { + forceEnableWebServer = false; + delay(200); + restartEsp(RestartReason::ReconfigureWebServer); + } + else if(!_webEnabled) forceEnableWebServer = false; delay(2000); } @@ -422,17 +644,17 @@ bool NukiNetwork::update() { if(_networkTimeout > 0 && (ts - _lastConnectedTs > _networkTimeout * 1000) && ts > 60000) { + if(!_webEnabled) forceEnableWebServer = true; Log->println("Network timeout has been reached, restarting ..."); delay(200); restartEsp(RestartReason::NetworkTimeoutWatchdog); } - delay(2000); return false; } _lastConnectedTs = ts; - + #if PRESENCE_DETECTION_ENABLED if(_presenceDetection != nullptr && (_lastPresenceTs == 0 || (ts - _lastPresenceTs) > 3000)) { @@ -487,7 +709,8 @@ bool NukiNetwork::update() NetworkClientSecure *client = new NetworkClientSecure; if (client) { - client->setDefaultCACertBundle(); + //client->setDefaultCACertBundle(); + client->setCACertBundle(x509_crt_imported_bundle_bin_start, x509_crt_imported_bundle_bin_end - x509_crt_imported_bundle_bin_start); { HTTPClient https; https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); @@ -505,10 +728,10 @@ bool NukiNetwork::update() { String currentVersion = NUKI_HUB_VERSION; - if(atof(doc["release"]["version"]) >= atof(currentVersion.c_str())) _latestVersion = doc["release"]["version"]; - else if(currentVersion.indexOf("beta") > 0) _latestVersion = doc["beta"]["version"]; - else if(currentVersion.indexOf("master") > 0) _latestVersion = doc["master"]["version"]; - else _latestVersion = doc["release"]["version"]; + if(atof(doc["release"]["version"]) >= atof(currentVersion.c_str())) _latestVersion = doc["release"]["fullversion"]; + else if(currentVersion.indexOf("beta") > 0) _latestVersion = doc["beta"]["fullversion"]; + else if(currentVersion.indexOf("master") > 0) _latestVersion = doc["master"]["fullversion"]; + else _latestVersion = doc["release"]["fullversion"]; publishString(_maintenancePathPrefix, mqtt_topic_info_nuki_hub_latest, _latestVersion, true); @@ -592,7 +815,6 @@ void NukiNetwork::onMqttDisconnect(const espMqttClientTypes::DisconnectReason &r bool NukiNetwork::reconnect() { _mqttConnectionState = 0; - int port = _preferences->getInt(preference_mqtt_broker_port, 1883); while (!_device->mqttConnected() && (esp_timer_get_time() / 1000) > _nextReconnect) { @@ -618,7 +840,7 @@ bool NukiNetwork::reconnect() } _device->setWill(_mqttConnectionStateTopic, 1, true, _lastWillPayload); - _device->mqttSetServer(_mqttBrokerAddr, port); + _device->mqttSetServer(_mqttBrokerAddr, _mqttPort); _device->mqttConnect(); int64_t timeout = (esp_timer_get_time() / 1000) + 60000; diff --git a/src/NukiNetwork.h b/src/NukiNetwork.h index 2f50112..9823df8 100644 --- a/src/NukiNetwork.h +++ b/src/NukiNetwork.h @@ -19,11 +19,15 @@ enum class NetworkDeviceType { WiFi, W5500, + W5500M5, + W5500M5S3, Olimex_LAN8720, WT32_LAN8720, M5STACK_PoESP32_Unit, LilyGO_T_ETH_POE, - GL_S10 + GL_S10, + ETH01_Evo, + CUSTOM }; #define JSON_BUFFER_SIZE 1024 @@ -124,6 +128,8 @@ private: std::vector> _reconnectedCallbacks; NetworkDeviceType _networkDeviceType = (NetworkDeviceType)-1; + bool _firstBootAfterDeviceChange = false; + bool _webEnabled = true; #ifndef NUKI_HUB_UPDATER static void onMqttDataReceivedCallback(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total); @@ -161,10 +167,11 @@ private: int _mqttConnectionState = 0; int _mqttConnectCounter = 0; + int _mqttPort = 1883; long _mqttConnectedTs = -1; bool _connectReplyReceived = false; bool _firstDisconnected = true; - + int64_t _nextReconnect = 0; char _mqttBrokerAddr[101] = {0}; char _mqttUser[31] = {0}; diff --git a/src/NukiNetworkLock.cpp b/src/NukiNetworkLock.cpp index 12a4cff..6b73614 100644 --- a/src/NukiNetworkLock.cpp +++ b/src/NukiNetworkLock.cpp @@ -1,5 +1,4 @@ #include "NukiNetworkLock.h" -#include // https://github.com/tzapu/WiFiManager #include "Arduino.h" #include "Config.h" #include "MqttTopics.h" @@ -9,6 +8,8 @@ #include #include +extern bool forceEnableWebServer; + NukiNetworkLock::NukiNetworkLock(NukiNetwork* network, Preferences* preferences, char* buffer, size_t bufferSize) : _network(network), _preferences(preferences), @@ -79,7 +80,7 @@ void NukiNetworkLock::initialize() _network->subscribe(_mqttPath, mqtt_topic_webserver_action); _network->initTopic(_mqttPath, mqtt_topic_webserver_action, "--"); - _network->initTopic(_mqttPath, mqtt_topic_webserver_state, (_preferences->getBool(preference_webserver_enabled, true) ? "1" : "0")); + _network->initTopic(_mqttPath, mqtt_topic_webserver_state, (_preferences->getBool(preference_webserver_enabled, true) || forceEnableWebServer ? "1" : "0")); _network->initTopic(_mqttPath, mqtt_topic_query_config, "0"); _network->initTopic(_mqttPath, mqtt_topic_query_lockstate, "0"); @@ -230,14 +231,14 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const if(strcmp(value, "1") == 0) { - if(_preferences->getBool(preference_webserver_enabled, true)) return; + if(_preferences->getBool(preference_webserver_enabled, true) || forceEnableWebServer) return; Log->println(F("Webserver enabled, restarting.")); _preferences->putBool(preference_webserver_enabled, true); } else if (strcmp(value, "0") == 0) { - if(!_preferences->getBool(preference_webserver_enabled, true)) return; + if(!_preferences->getBool(preference_webserver_enabled, true) && !forceEnableWebServer) return; Log->println(F("Webserver disabled, restarting.")); _preferences->putBool(preference_webserver_enabled, false); } @@ -245,7 +246,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const publishString(mqtt_topic_webserver_action, "--", true); _network->clearWifiFallback(); delay(200); - restartEsp(RestartReason::RequestedViaMqtt); + restartEsp(RestartReason::ReconfigureWebServer); } else if(comparePrefixedPath(topic, mqtt_topic_lock_log_rolling_last)) { @@ -1366,7 +1367,10 @@ void NukiNetworkLock::publishHASSConfig(char *deviceType, const char *baseTopic, { _network->removeHASSConfigTopic((char*)"binary_sensor", (char*)"door_sensor", uidString); } + + #ifndef CONFIG_IDF_TARGET_ESP32H2 _network->publishHASSWifiRssiConfig(deviceType, baseTopic, name, uidString); + #endif if(publishAuthData) { diff --git a/src/NukiNetworkLock.h b/src/NukiNetworkLock.h index 22240b0..d5da822 100644 --- a/src/NukiNetworkLock.h +++ b/src/NukiNetworkLock.h @@ -1,8 +1,9 @@ #pragma once #include "networkDevices/NetworkDevice.h" +#ifndef CONFIG_IDF_TARGET_ESP32H2 #include "networkDevices/WifiDevice.h" -#include "networkDevices/W5500Device.h" +#endif #include #include #include diff --git a/src/NukiNetworkOpener.h b/src/NukiNetworkOpener.h index d137295..be1cb31 100644 --- a/src/NukiNetworkOpener.h +++ b/src/NukiNetworkOpener.h @@ -1,8 +1,6 @@ #pragma once #include "networkDevices/NetworkDevice.h" -#include "networkDevices/WifiDevice.h" -#include "networkDevices/W5500Device.h" #include #include #include "NukiConstants.h" diff --git a/src/NukiOpenerWrapper.cpp b/src/NukiOpenerWrapper.cpp index 0dac01b..65d457e 100644 --- a/src/NukiOpenerWrapper.cpp +++ b/src/NukiOpenerWrapper.cpp @@ -82,38 +82,43 @@ void NukiOpenerWrapper::initialize() if(_nrOfRetries < 0 || _nrOfRetries == 200) { + Log->println("Invalid nrOfRetries, revert to default (3)"); _nrOfRetries = 3; _preferences->putInt(preference_command_nr_of_retries, _nrOfRetries); } - - if(_retryDelay <= 100) + if(_retryDelay < 100) { + Log->println("Invalid retryDelay, revert to default (100)"); _retryDelay = 100; _preferences->putInt(preference_command_retry_delay, _retryDelay); } - if(_intervalLockstate == 0) { + Log->println("Invalid intervalLockstate, revert to default (1800)"); _intervalLockstate = 60 * 30; _preferences->putInt(preference_query_interval_lockstate, _intervalLockstate); } if(_intervalConfig == 0) { + Log->println("Invalid intervalConfig, revert to default (3600)"); _intervalConfig = 60 * 60; _preferences->putInt(preference_query_interval_configuration, _intervalConfig); } if(_intervalBattery == 0) { + Log->println("Invalid intervalBattery, revert to default (1800)"); _intervalBattery = 60 * 30; _preferences->putInt(preference_query_interval_battery, _intervalBattery); } if(_intervalKeypad == 0) { + Log->println("Invalid intervalKeypad, revert to default (1800)"); _intervalKeypad = 60 * 30; _preferences->putInt(preference_query_interval_keypad, _intervalKeypad); } - if(_restartBeaconTimeout < 10) + if(_restartBeaconTimeout != -1 && _restartBeaconTimeout < 10) { + Log->println("Invalid restartBeaconTimeout, revert to default (-1)"); _restartBeaconTimeout = -1; _preferences->putInt(preference_restart_ble_beacon_lost, _restartBeaconTimeout); } @@ -342,6 +347,11 @@ void NukiOpenerWrapper::setPin(const uint16_t pin) _nukiOpener.saveSecurityPincode(pin); } +uint16_t NukiOpenerWrapper::getPin() +{ + return _nukiOpener.getSecurityPincode(); +} + void NukiOpenerWrapper::unpair() { _nukiOpener.unPairNuki(); diff --git a/src/NukiOpenerWrapper.h b/src/NukiOpenerWrapper.h index de43423..085da94 100644 --- a/src/NukiOpenerWrapper.h +++ b/src/NukiOpenerWrapper.h @@ -27,6 +27,7 @@ public: bool isPinSet(); bool isPinValid(); void setPin(const uint16_t pin); + uint16_t getPin(); void unpair(); diff --git a/src/NukiWrapper.cpp b/src/NukiWrapper.cpp index 19706eb..4a1d036 100644 --- a/src/NukiWrapper.cpp +++ b/src/NukiWrapper.cpp @@ -94,7 +94,6 @@ void NukiWrapper::initialize(const bool& firstStart) _preferences->putBool(preference_find_best_rssi, false); _preferences->putBool(preference_check_updates, true); _preferences->putBool(preference_opener_continuous_mode, false); - _preferences->putBool(preference_network_wifi_fallback_disabled, false); _preferences->putBool(preference_official_hybrid, false); _preferences->putBool(preference_official_hybrid_actions, false); _preferences->putBool(preference_official_hybrid_retry, false); @@ -127,7 +126,7 @@ void NukiWrapper::initialize(const bool& firstStart) _preferences->putInt(preference_rssi_publish_interval, 60); _preferences->putInt(preference_network_timeout, 60); _preferences->putInt(preference_command_nr_of_retries, 3); - _preferences->putInt(preference_command_retry_delay, 1000); + _preferences->putInt(preference_command_retry_delay, 100); _preferences->putInt(preference_restart_ble_beacon_lost, 60); _preferences->putInt(preference_query_interval_lockstate, 1800); _preferences->putInt(preference_query_interval_configuration, 3600); @@ -144,14 +143,12 @@ void NukiWrapper::initialize(const bool& firstStart) _nrOfRetries = 3; _preferences->putInt(preference_command_nr_of_retries, _nrOfRetries); } - - if(_retryDelay <= 100) + if(_retryDelay < 100) { Log->println("Invalid retryDelay, revert to default (100)"); _retryDelay = 100; _preferences->putInt(preference_command_retry_delay, _retryDelay); } - if(_intervalLockstate == 0) { Log->println("Invalid intervalLockstate, revert to default (1800)"); @@ -182,7 +179,7 @@ void NukiWrapper::initialize(const bool& firstStart) _intervalKeypad = 60 * 30; _preferences->putInt(preference_query_interval_keypad, _intervalKeypad); } - if(_restartBeaconTimeout < 10) + if(_restartBeaconTimeout != -1 && _restartBeaconTimeout < 10) { Log->println("Invalid restartBeaconTimeout, revert to default (-1)"); _restartBeaconTimeout = -1; @@ -419,6 +416,11 @@ void NukiWrapper::setPin(const uint16_t pin) _nukiLock.saveSecurityPincode(pin); } +uint16_t NukiWrapper::getPin() +{ + return _nukiLock.getSecurityPincode(); +} + void NukiWrapper::unpair() { _nukiLock.unPairNuki(); diff --git a/src/NukiWrapper.h b/src/NukiWrapper.h index 93c8815..bc2b41f 100644 --- a/src/NukiWrapper.h +++ b/src/NukiWrapper.h @@ -27,6 +27,7 @@ public: bool isPinSet(); bool isPinValid(); void setPin(const uint16_t pin); + uint16_t getPin(); void unpair(); void disableHASS(); diff --git a/src/Ota.cpp b/src/Ota.cpp deleted file mode 100644 index 73d78db..0000000 --- a/src/Ota.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include "Ota.h" -#include "Logger.h" -#include "RestartReason.h" - -#define FULL_PACKET 1436 // HTTP_UPLOAD_BUFLEN in WebServer,h - -void Ota::updateFirmware(uint8_t* buf, size_t size) -{ - if(!_updateStarted && size == 0) - { - Log->println("OTA upload cancelled, size is 0."); - return; - } - - if (!_updateStarted) - { //If it's the first packet of OTA since bootup, begin OTA - Log->println("BeginOTA"); - esp_ota_begin(esp_ota_get_next_update_partition(NULL), OTA_SIZE_UNKNOWN, &otaHandler); - _updateStarted = true; - } - esp_ota_write(otaHandler, buf, size); - if (size != FULL_PACKET) - { - esp_ota_end(otaHandler); - Log->println("EndOTA"); - if (ESP_OK == esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL))) - { - _updateCompleted = true; - } - else - { - Log->println("Upload Error"); - } - } -} - -bool Ota::updateStarted() -{ - return _updateStarted; -} - -bool Ota::updateCompleted() -{ - return _updateCompleted; -} - -void Ota::restart() -{ - _updateCompleted = false; - _updateStarted = false; -} diff --git a/src/Ota.h b/src/Ota.h deleted file mode 100644 index 363be23..0000000 --- a/src/Ota.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include -#include "esp_ota_ops.h" - -class Ota -{ -public: - void updateFirmware(uint8_t* buf, size_t size); - - bool updateStarted(); - bool updateCompleted(); - void restart(); - -private: - bool _updateStarted = false; - bool _updateCompleted = false; - esp_ota_handle_t otaHandler = 0; -}; diff --git a/src/PreferencesKeys.h b/src/PreferencesKeys.h index b883b5a..f8f4452 100644 --- a/src/PreferencesKeys.h +++ b/src/PreferencesKeys.h @@ -76,10 +76,6 @@ #define preference_gpio_configuration (char*)"gpiocfg" #define preference_publish_debug_info (char*)"pubdbg" #define preference_presence_detection_timeout (char*)"prdtimeout" -#define preference_has_mac_saved (char*)"hasmac" -#define preference_has_mac_byte_0 (char*)"macb0" -#define preference_has_mac_byte_1 (char*)"macb1" -#define preference_has_mac_byte_2 (char*)"macb2" #define preference_latest_version (char*)"latest" #define preference_task_size_network (char*)"tsksznetw" #define preference_task_size_nuki (char*)"tsksznuki" @@ -100,6 +96,22 @@ #define preference_show_secrets (char*)"showSecr" #define preference_ble_tx_power (char*)"bleTxPwr" #define preference_recon_netw_on_mqtt_discon (char*)"recNtwMqttDis" +#define preference_network_custom_phy (char*)"ntwPHY" +#define preference_network_custom_addr (char*)"ntwADDR" +#define preference_network_custom_irq (char*)"ntwIRQ" +#define preference_network_custom_rst (char*)"ntwRST" +#define preference_network_custom_cs (char*)"ntwCS" +#define preference_network_custom_sck (char*)"ntwSCK" +#define preference_network_custom_miso (char*)"ntwMISO" +#define preference_network_custom_mosi (char*)"ntwMOSI" +#define preference_network_custom_pwr (char*)"ntwPWR" +#define preference_network_custom_mdio (char*)"ntwMDIO" +#define preference_network_custom_mdc (char*)"ntwMDC" +#define preference_network_custom_clk (char*)"ntwCLK" +#define preference_ntw_reconfigure (char*)"ntwRECONF" +#define preference_updater_version (char*)"updVer" +#define preference_updater_build (char*)"updBuild" +#define preference_updater_date (char*)"updDate" inline bool initPreferences(Preferences* preferences) { @@ -223,6 +235,13 @@ inline bool initPreferences(Preferences* preferences) } } } + if (configVer < 901) + { + #if defined(CONFIG_IDF_TARGET_ESP32S3) + if (preferences->getInt(preference_network_hardware) == 3) preferences->putInt(preference_network_hardware, 10); + #endif + if (preferences->getInt(preference_network_hardware) == 2) preferences->putInt(preference_network_hardware, 3); + } preferences->putInt(preference_config_version, atof(NUKI_HUB_VERSION) * 100); } @@ -249,10 +268,12 @@ private: preference_keypad_info_enabled, preference_keypad_publish_code, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_conf_info_enabled, preference_register_as_app, preference_register_opener_as_app, preference_command_nr_of_retries, preference_command_retry_delay, preference_cred_user, preference_cred_password, preference_disable_non_json, preference_publish_authdata, preference_publish_debug_info, preference_presence_detection_timeout, - preference_official_hybrid, preference_query_interval_hybrid_lockstate, preference_official_hybrid_actions, preference_official_hybrid_retry, preference_has_mac_saved, - preference_has_mac_byte_0, preference_has_mac_byte_1, preference_has_mac_byte_2, preference_latest_version, preference_task_size_network, preference_task_size_nuki, - preference_authlog_max_entries, preference_keypad_max_entries, preference_timecontrol_max_entries, preference_update_from_mqtt, preference_show_secrets, - preference_ble_tx_power, preference_recon_netw_on_mqtt_discon, preference_webserial_enabled + preference_official_hybrid, preference_query_interval_hybrid_lockstate, preference_official_hybrid_actions, preference_official_hybrid_retry, preference_latest_version, + preference_task_size_network, preference_task_size_nuki, preference_authlog_max_entries, preference_keypad_max_entries, preference_timecontrol_max_entries, + preference_update_from_mqtt, preference_show_secrets, preference_ble_tx_power, preference_recon_netw_on_mqtt_discon, preference_webserial_enabled, + preference_network_custom_mdc, preference_network_custom_clk, preference_network_custom_phy, preference_network_custom_addr, preference_network_custom_irq, + preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso, preference_network_custom_mosi, + preference_network_custom_pwr, preference_network_custom_mdio, preference_ntw_reconfigure }; std::vector _redact = { @@ -265,9 +286,9 @@ private: preference_timecontrol_topic_per_entry, preference_keypad_topic_per_entry, preference_enable_bootloop_reset, preference_webserver_enabled, preference_find_best_rssi, preference_restart_on_disconnect, preference_keypad_control_enabled, preference_keypad_info_enabled, preference_keypad_publish_code, preference_show_secrets, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_register_as_app, preference_register_opener_as_app, preference_ip_dhcp_enabled, - preference_publish_authdata, preference_has_mac_saved, preference_publish_debug_info, preference_network_wifi_fallback_disabled, preference_official_hybrid, + preference_publish_authdata, preference_publish_debug_info, preference_network_wifi_fallback_disabled, preference_official_hybrid, preference_official_hybrid_actions, preference_official_hybrid_retry, preference_conf_info_enabled, preference_disable_non_json, preference_update_from_mqtt, - preference_recon_netw_on_mqtt_discon, preference_webserial_enabled + preference_recon_netw_on_mqtt_discon, preference_webserial_enabled, preference_ntw_reconfigure }; std::vector _bytePrefs = { @@ -283,11 +304,9 @@ private: preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad, preference_command_nr_of_retries, preference_command_retry_delay, preference_presence_detection_timeout, preference_query_interval_hybrid_lockstate, preference_latest_version, preference_task_size_network, preference_task_size_nuki, preference_authlog_max_entries, preference_keypad_max_entries, preference_timecontrol_max_entries, - preference_ble_tx_power - }; - std::vector _charPrefs = - { - preference_has_mac_byte_0, preference_has_mac_byte_1, preference_has_mac_byte_2 + preference_ble_tx_power, preference_network_custom_mdc, preference_network_custom_clk, preference_network_custom_phy, preference_network_custom_addr, + preference_network_custom_irq, preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso, + preference_network_custom_mosi, preference_network_custom_pwr, preference_network_custom_mdio }; public: const std::vector getPreferencesKeys() @@ -310,8 +329,4 @@ public: { return _intPrefs; } - const std::vector getPreferencesCharKeys() - { - return _charPrefs; - } }; \ No newline at end of file diff --git a/src/RestartReason.h b/src/RestartReason.h index e7b403e..de62376 100644 --- a/src/RestartReason.h +++ b/src/RestartReason.h @@ -10,7 +10,7 @@ enum class RestartReason NetworkTimeoutWatchdog, WifiInitFailed, ReconfigureWifi, - ReconfigureLAN8720, + ReconfigureETH, NetworkDeviceCriticalFailure, NetworkDeviceCriticalFailureNoWifiFallback, ConfigurationUpdated, @@ -24,6 +24,7 @@ enum class RestartReason ImportCompleted, DeviceUnpaired, NukiHubReset, + ReconfigureWebServer, NotApplicable }; @@ -72,6 +73,8 @@ inline static String getRestartReason() return "RequestedViaMqtt"; case RestartReason::RequestedViaWebServer: return "RequestedViaWebServer"; + case RestartReason::ReconfigureWebServer: + return "ReconfigureWebServer"; case RestartReason::BLEBeaconWatchdog: return "BLEBeaconWatchdog"; case RestartReason::RestartOnDisconnectWatchdog: @@ -84,8 +87,8 @@ inline static String getRestartReason() return "WifiInitFailed"; case RestartReason::ReconfigureWifi: return "ReconfigureWifi"; - case RestartReason::ReconfigureLAN8720: - return "ReconfigureLAN8720"; + case RestartReason::ReconfigureETH: + return "ReconfigureETH"; case RestartReason::NetworkDeviceCriticalFailure: return "NetworkDeviceCriticalFailure"; case RestartReason::NetworkDeviceCriticalFailureNoWifiFallback: diff --git a/src/WebCfgServer.cpp b/src/WebCfgServer.cpp index de89d13..dad7f36 100644 --- a/src/WebCfgServer.cpp +++ b/src/WebCfgServer.cpp @@ -1,34 +1,38 @@ #include "WebCfgServer.h" #include "WebCfgServerConstants.h" #include "PreferencesKeys.h" -#include "hardware/WifiEthServer.h" #include "Logger.h" -#include "Config.h" #include "RestartReason.h" #include +#ifndef CONFIG_IDF_TARGET_ESP32H2 #include +#endif +#include + +extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start"); +extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end"); #ifndef NUKI_HUB_UPDATER #include #include #include "ArduinoJson.h" -WebCfgServer::WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, NukiNetwork* network, Gpio* gpio, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal, uint8_t partitionType) -: _server(ethServer), - _nuki(nuki), +WebCfgServer::WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, NukiNetwork* network, Gpio* gpio, Preferences* preferences, bool allowRestartToPortal, uint8_t partitionType, AsyncWebServer* asyncServer) +: _nuki(nuki), _nukiOpener(nukiOpener), _network(network), _gpio(gpio), _preferences(preferences), _allowRestartToPortal(allowRestartToPortal), - _partitionType(partitionType) + _partitionType(partitionType), + _asyncServer(asyncServer) #else -WebCfgServer::WebCfgServer(NukiNetwork* network, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal, uint8_t partitionType) -: _server(ethServer), - _network(network), +WebCfgServer::WebCfgServer(NukiNetwork* network, Preferences* preferences, bool allowRestartToPortal, uint8_t partitionType, AsyncWebServer* asyncServer) +: _network(network), _preferences(preferences), _allowRestartToPortal(allowRestartToPortal), - _partitionType(partitionType) + _partitionType(partitionType), + _asyncServer(asyncServer) #endif { _hostname = _preferences->getString(preference_hostname, ""); @@ -68,392 +72,238 @@ WebCfgServer::WebCfgServer(NukiNetwork* network, EthServer* ethServer, Preferenc void WebCfgServer::initialize() { - _response.reserve(8192); - _server.on("/", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; + _asyncServer->on("/", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); #ifndef NUKI_HUB_UPDATER - buildHtml(); + buildHtml(request); #else - buildOtaHtml(_server.arg("errored") != ""); + buildOtaHtml(request); #endif - _server.send(200, "text/html", _response); }); - _server.on("/style.css", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - sendCss(); + _asyncServer->on("/style.css", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + sendCss(request); }); - _server.on("/favicon.ico", HTTP_GET, [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - sendFavicon(); + _asyncServer->on("/favicon.ico", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + sendFavicon(request); }); #ifndef NUKI_HUB_UPDATER - _server.on("/import", [&]() - { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } + _asyncServer->on("/import", HTTP_POST, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); String message = ""; - bool restart = processImport(message); - if(restart) - { - _response = ""; - buildConfirmHtml(message); - _server.send(200, "text/html", _response); - Log->println(F("Restarting")); - - waitAndProcess(true, 1000); - restartEsp(RestartReason::ImportCompleted); - } - else - { - _response = ""; - buildConfirmHtml(message, 3); - _server.send(200, "text/html", _response); - waitAndProcess(false, 1000); - } + bool restart = processImport(request, message); + buildConfirmHtml(request, message, 3, true); }); - _server.on("/export", HTTP_GET, [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - sendSettings(); + _asyncServer->on("/export", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + sendSettings(request); }); - _server.on("/impexpcfg", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildImportExportHtml(); - _server.send(200, "text/html", _response); + _asyncServer->on("/impexpcfg", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildImportExportHtml(request); }); - _server.on("/status", HTTP_GET, [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildStatusHtml(); - _server.send(200, "application/json", _response); + _asyncServer->on("/status", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildStatusHtml(request); }); - _server.on("/acclvl", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildAccLvlHtml(); - _server.send(200, "text/html", _response); + _asyncServer->on("/acclvl", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildAccLvlHtml(request, 0); }); - _server.on("/advanced", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildAdvancedConfigHtml(); - _server.send(200, "text/html", _response); + _asyncServer->on("/acllock", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildAccLvlHtml(request, 1); }); - _server.on("/cred", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildCredHtml(); - _server.send(200, "text/html", _response); + _asyncServer->on("/aclopener", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildAccLvlHtml(request, 2); }); - _server.on("/mqttconfig", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildMqttConfigHtml(); - _server.send(200, "text/html", _response); + _asyncServer->on("/custntw", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildCustomNetworkConfigHtml(request); }); - _server.on("/nukicfg", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildNukiConfigHtml(); - _server.send(200, "text/html", _response); + _asyncServer->on("/advanced", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildAdvancedConfigHtml(request); }); - _server.on("/gpiocfg", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildGpioConfigHtml(); - _server.send(200, "text/html", _response); + _asyncServer->on("/cred", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildCredHtml(request); }); - _server.on("/wifi", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildConfigureWifiHtml(); - _server.send(200, "text/html", _response); + _asyncServer->on("/mqttconfig", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildMqttConfigHtml(request); }); - _server.on("/unpairlock", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - - processUnpair(false); + _asyncServer->on("/nukicfg", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildNukiConfigHtml(request); }); - _server.on("/unpairopener", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - - processUnpair(true); + _asyncServer->on("/gpiocfg", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildGpioConfigHtml(request); }); - _server.on("/factoryreset", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - - processFactoryReset(); + #ifndef CONFIG_IDF_TARGET_ESP32H2 + _asyncServer->on("/wifi", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildConfigureWifiHtml(request); }); - _server.on("/wifimanager", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } + _asyncServer->on("/wifimanager", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); if(_allowRestartToPortal) { - _response = ""; - buildConfirmHtml("Restarting. Connect to ESP access point to reconfigure Wi-Fi.", 0); - _server.send(200, "text/html", _response); - waitAndProcess(true, 2000); + buildConfirmHtml(request, "Restarting. Connect to ESP access point to reconfigure Wi-Fi.", 0); + waitAndProcess(false, 1000); _network->reconfigureDevice(); } }); - _server.on("/savecfg", [&]() - { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - String message = ""; - bool restart = processArgs(message); - if(restart) - { - _response = ""; - buildConfirmHtml(message); - _server.send(200, "text/html", _response); - Log->println(F("Restarting")); - - waitAndProcess(true, 1000); - restartEsp(RestartReason::ConfigurationUpdated); - } - else - { - _response = ""; - buildConfirmHtml(message, 3); - _server.send(200, "text/html", _response); - waitAndProcess(false, 1000); - } + #endif + _asyncServer->on("/unpairlock", HTTP_POST, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + processUnpair(request, false); }); - _server.on("/savegpiocfg", [&]() - { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - processGpioArgs(); - - _response = ""; - buildConfirmHtml(""); - _server.send(200, "text/html", _response); + _asyncServer->on("/unpairopener", HTTP_POST, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + processUnpair(request, true); + }); + _asyncServer->on("/factoryreset", HTTP_POST, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + processFactoryReset(request); + }); + _asyncServer->on("/info", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildInfoHtml(request); + }); + _asyncServer->on("/debugon", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + _preferences->putBool(preference_publish_debug_info, true); + buildConfirmHtml(request, "Debug On", 3, true); + Log->println(F("Restarting")); + waitAndProcess(true, 1000); + restartEsp(RestartReason::ConfigurationUpdated); + }); + _asyncServer->on("/debugoff", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + _preferences->putBool(preference_publish_debug_info, false); + buildConfirmHtml(request, "Debug Off", 3, true); + Log->println(F("Restarting")); + waitAndProcess(true, 1000); + restartEsp(RestartReason::ConfigurationUpdated); + }); + _asyncServer->on("/savecfg", HTTP_POST, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + String message = ""; + bool restart = processArgs(request, message); + buildConfirmHtml(request, message, 3, true); + }); + _asyncServer->on("/savegpiocfg", HTTP_POST, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + processGpioArgs(request); + buildConfirmHtml(request, "Saving GPIO configuration. Restarting.", 3, true); Log->println(F("Restarting")); - waitAndProcess(true, 1000); restartEsp(RestartReason::GpioConfigurationUpdated); }); - _server.on("/info", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildInfoHtml(); - _server.send(200, "text/html", _response); - }); - _server.on("/debugon", [&]() { - _preferences->putBool(preference_publish_debug_info, true); - - _response = ""; - buildConfirmHtml("OK"); - _server.send(200, "text/html", _response); - Log->println(F("Restarting")); - - waitAndProcess(true, 1000); - restartEsp(RestartReason::ConfigurationUpdated); - }); - _server.on("/debugoff", [&]() { - _preferences->putBool(preference_publish_debug_info, false); - - _response = ""; - buildConfirmHtml("OK"); - _server.send(200, "text/html", _response); - Log->println(F("Restarting")); - - waitAndProcess(true, 1000); - restartEsp(RestartReason::ConfigurationUpdated); - }); - _server.on("/webserial", [&]() { - _server.sendHeader("Location", (String)"http://" + _network->localIP() + ":81/webserial"); - _server.send(302, "text/plain", ""); - }); #endif - _server.on("/ota", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildOtaHtml(_server.arg("errored") != ""); - _server.send(200, "text/html", _response); + _asyncServer->on("/ota", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildOtaHtml(request); }); - _server.on("/otadebug", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildOtaHtml(_server.arg("errored") != "", true); - _server.send(200, "text/html", _response); + _asyncServer->on("/otadebug", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildOtaHtml(request, true); }); - _server.on("/reboottoota", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildConfirmHtml("Rebooting to other partition", 2); - _server.send(200, "text/html", _response); + _asyncServer->on("/reboottoota", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildConfirmHtml(request, "Rebooting to other partition", 2, true); waitAndProcess(true, 1000); esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL)); restartEsp(RestartReason::OTAReboot); }); - _server.on("/reboot", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - _response = ""; - buildConfirmHtml("Rebooting", 2); - _server.send(200, "text/html", _response); + _asyncServer->on("/reboot", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + buildConfirmHtml(request, "Rebooting", 2, true); waitAndProcess(true, 1000); restartEsp(RestartReason::RequestedViaWebServer); }); - _server.on("/autoupdate", [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } + _asyncServer->on("/autoupdate", HTTP_GET, [&](AsyncWebServerRequest *request){ + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); #ifndef NUKI_HUB_UPDATER - processUpdate(); + processUpdate(request); #else - _server.sendHeader("Location", "/"); - _server.send(302, "text/plain", ""); + request->redirect("/"); #endif }); - _server.on("/uploadota", HTTP_POST, [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - - if (_ota.updateStarted() && _ota.updateCompleted()) { - _response = ""; - buildOtaCompletedHtml(); - _server.send(200, "text/html", _response); - delay(2000); - restartEsp(RestartReason::OTACompleted); - } else { - _ota.restart(); - _server.sendHeader("Location", "/ota?errored=true"); - _server.send(302, "text/plain", ""); - } - }, [&]() { - if (_hasCredentials && !_server.authenticate(_credUser, _credPassword)) { - return _server.requestAuthentication(); - } - - handleOtaUpload(); - }); - - const char *headerkeys[] = {"Content-Length"}; - size_t headerkeyssize = sizeof(headerkeys) / sizeof(char *); - _server.collectHeaders(headerkeys, headerkeyssize); - - _server.begin(); - - _network->setKeepAliveCallback([&]() - { - update(); - }); + _asyncServer->on("/uploadota", HTTP_POST, + [&](AsyncWebServerRequest *request) {}, + [&](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) + { + if(strlen(_credUser) > 0 && strlen(_credPassword) > 0) if(!request->authenticate(_credUser, _credPassword)) return request->requestAuthentication(); + handleOtaUpload(request, filename, index, data, len, final); + } + ); + //Update.onProgress(printProgress); } -void WebCfgServer::update() +void WebCfgServer::buildOtaHtml(AsyncWebServerRequest *request, bool debug) { - if(_otaStartTs > 0 && ((esp_timer_get_time() / 1000) - _otaStartTs) > 120000) + AsyncResponseStream *response = request->beginResponseStream("text/html"); + buildHtmlHeader(response); + + bool errored = false; + if(request->hasParam("errored")) { - Log->println(F("OTA time out, restarting")); - delay(200); - restartEsp(RestartReason::OTATimeout); + const AsyncWebParameter* p = request->getParam("errored"); + if(p->value() != "") errored = true; } - if(!_enabled) return; - - _server.handleClient(); -} - -void WebCfgServer::buildOtaHtml(bool errored, bool debug) -{ - buildHtmlHeader(); - - if(errored) _response.concat("
Over-the-air update errored. Please check the logs for more info

"); + if(errored) response->print("
Over-the-air update errored. Please check the logs for more info

"); if(_partitionType == 0) { - _response.concat("

You are currently running Nuki Hub with an outdated partition scheme. Because of this you cannot use OTA to update to 9.00 or higher. Please check GitHub for instructions on how to update to 9.00 and the new partition scheme

"); - _response.concat(""); + response->print("

You are currently running Nuki Hub with an outdated partition scheme. Because of this you cannot use OTA to update to 9.00 or higher. Please check GitHub for instructions on how to update to 9.00 and the new partition scheme

"); + response->print(""); return; } - _response.concat("
Initiating Over-the-air update. This will take about two minutes, please be patient.
You will be forwarded automatically when the update is complete.
"); - _response.concat("

Update Nuki Hub

"); - _response.concat("Click on the button to reboot and automatically update Nuki Hub and the Nuki Hub updater to the latest versions from GitHub"); - _response.concat("
"); + response->print("
Initiating Over-the-air update. This will take about two minutes, please be patient.
You will be forwarded automatically when the update is complete.
"); + response->print("

Update Nuki Hub

"); + response->print("Click on the button to reboot and automatically update Nuki Hub and the Nuki Hub updater to the latest versions from GitHub"); + response->print("
"); String release_type; if(debug) release_type = "debug"; else release_type = "release"; - + #ifndef DEBUG_NUKIHUB String build_type = "release"; #else String build_type = "debug"; #endif - - _response.concat("

"); - _response.concat("

"); - _response.concat("

"); - _response.concat("

"); - _response.concat("Current version: "); - _response.concat(NUKI_HUB_VERSION); - _response.concat(" ("); - _response.concat(NUKI_HUB_BUILD); - _response.concat("), "); - _response.concat(NUKI_HUB_DATE); - _response.concat("
"); + response->print("

"); + response->print("

"); + response->print("

"); + response->print("

"); + + response->print("Current version: "); + response->print(NUKI_HUB_VERSION); + response->print(" ("); + response->print(NUKI_HUB_BUILD); + response->print("), "); + response->print(NUKI_HUB_DATE); + response->print("
"); #ifndef NUKI_HUB_UPDATER - bool manifestSuccess = false; + bool manifestSuccess = false; NetworkClientSecure *client = new NetworkClientSecure; if (client) { - client->setDefaultCACertBundle(); + //client->setDefaultCACertBundle(); + client->setCACertBundle(x509_crt_imported_bundle_bin_start, x509_crt_imported_bundle_bin_end - x509_crt_imported_bundle_bin_start); { HTTPClient https; https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); @@ -471,35 +321,35 @@ void WebCfgServer::buildOtaHtml(bool errored, bool debug) if (!jsonError) { manifestSuccess = true; - _response.concat("Latest release version: "); - _response.concat(doc["release"]["fullversion"].as()); - _response.concat(" ("); - _response.concat(doc["release"]["build"].as()); - _response.concat("), "); - _response.concat(doc["release"]["time"].as()); - _response.concat("
"); - _response.concat("Latest beta version: "); + response->print("Latest release version: "); + response->print(doc["release"]["fullversion"].as()); + response->print(" ("); + response->print(doc["release"]["build"].as()); + response->print("), "); + response->print(doc["release"]["time"].as()); + response->print("
"); + response->print("Latest beta version: "); if(doc["beta"]["fullversion"] != "No beta available") { - _response.concat(doc["beta"]["fullversion"].as()); - _response.concat(" ("); - _response.concat(doc["beta"]["build"].as()); - _response.concat("), "); - _response.concat(doc["beta"]["time"].as()); + response->print(doc["beta"]["fullversion"].as()); + response->print(" ("); + response->print(doc["beta"]["build"].as()); + response->print(")
, "); + response->print(doc["beta"]["time"].as()); } else { - _response.concat(doc["beta"]["fullversion"].as()); - _response.concat(""); + response->print(doc["beta"]["fullversion"].as()); + response->print(""); } - _response.concat("
"); - _response.concat("Latest development version: "); - _response.concat(doc["master"]["fullversion"].as()); - _response.concat(" ("); - _response.concat(doc["master"]["build"].as()); - _response.concat("), "); - _response.concat(doc["master"]["time"].as()); - _response.concat("
"); + response->print("
"); + response->print("Latest development version: "); + response->print(doc["master"]["fullversion"].as()); + response->print(" ("); + response->print(doc["master"]["build"].as()); + response->print("), "); + response->print(doc["master"]["time"].as()); + response->print("
"); } } https.end(); @@ -507,92 +357,93 @@ void WebCfgServer::buildOtaHtml(bool errored, bool debug) } delete client; } - + if(!manifestSuccess) { - _response.concat("currentverlatestverdevverbetaver"); + response->print("currentverlatestverdevverbetaver"); } #endif - _response.concat("
"); + response->print("
"); if(_partitionType == 1) { - _response.concat("

Manually update Nuki Hub

"); - _response.concat("

Reboot to Nuki Hub Updater

"); - _response.concat("Click on the button to reboot to the Nuki Hub updater, where you can select the latest Nuki Hub binary to update"); - _response.concat("



"); - _response.concat("

Update Nuki Hub Updater

"); - _response.concat("Select the latest Nuki Hub updater binary to update the Nuki Hub updater"); - _response.concat("
Choose the nuki_hub_updater.bin file to upload:
"); + response->print("

Manually update Nuki Hub

"); + response->print("

Reboot to Nuki Hub Updater

"); + response->print("Click on the button to reboot to the Nuki Hub updater, where you can select the latest Nuki Hub binary to update"); + response->print("


"); + response->print("

Update Nuki Hub Updater

"); + response->print("Select the latest Nuki Hub updater binary to update the Nuki Hub updater"); + response->print("
Choose the nuki_hub_updater.bin file to upload:
"); } else { - _response.concat("
"); - _response.concat("

Reboot to Nuki Hub

"); - _response.concat("Click on the button to reboot to Nuki Hub"); - _response.concat("


"); - _response.concat("

Update Nuki Hub

"); - _response.concat("Select the latest Nuki Hub binary to update Nuki Hub"); - _response.concat("
Choose the nuki_hub.bin file to upload:
"); + response->print("
"); + response->print("

Reboot to Nuki Hub

"); + response->print("Click on the button to reboot to Nuki Hub"); + response->print("


"); + response->print("

Update Nuki Hub

"); + response->print("Select the latest Nuki Hub binary to update Nuki Hub"); + response->print("
Choose the nuki_hub.bin file to upload:
"); } - _response.concat("


"); - _response.concat("
"); - _response.concat("

GitHub


"); - _response.concat(""); - _response.concat("

"); - _response.concat("

"); - _response.concat(""); - _response.concat(""); + response->print("


"); + response->print("
"); + response->print("

GitHub


"); + response->print(""); + response->print("

"); + response->print("

"); + response->print(""); + response->print(""); + request->send(response); } -void WebCfgServer::buildOtaCompletedHtml() +void WebCfgServer::buildOtaCompletedHtml(AsyncWebServerRequest *request) { - buildHtmlHeader(); + AsyncResponseStream *response = request->beginResponseStream("text/html"); + buildHtmlHeader(response); - _response.concat("
Over-the-air update completed.
You will be forwarded automatically.
"); - _response.concat(""); - _response.concat(""); + response->print("
Over-the-air update completed.
You will be forwarded automatically.
"); + response->print(""); + response->print(""); + request->send(response); } -void WebCfgServer::buildHtmlHeader(String additionalHeader) +void WebCfgServer::buildHtmlHeader(AsyncResponseStream *response, String additionalHeader) { - _response.concat(""); - _response.concat(""); - if(strcmp(additionalHeader.c_str(), "") != 0) _response.concat(additionalHeader); - _response.concat(""); - _response.concat("Nuki Hub"); - - srand(esp_timer_get_time() / 1000); + response->print(""); + response->print(""); + if(strcmp(additionalHeader.c_str(), "") != 0) response->print(additionalHeader); + response->print(""); + response->print("Nuki Hub"); } void WebCfgServer::waitAndProcess(const bool blocking, const uint32_t duration) @@ -600,7 +451,6 @@ void WebCfgServer::waitAndProcess(const bool blocking, const uint32_t duration) int64_t timeout = esp_timer_get_time() + (duration * 1000); while(esp_timer_get_time() < timeout) { - _server.handleClient(); if(blocking) { delay(10); @@ -612,39 +462,41 @@ void WebCfgServer::waitAndProcess(const bool blocking, const uint32_t duration) } } -void WebCfgServer::handleOtaUpload() +void WebCfgServer::printProgress(size_t prg, size_t sz) { + Log->printf("Progress: %d%%\n", (prg*100)/_otaContentLen); +} + +void WebCfgServer::handleOtaUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - if (_server.uri() != "/uploadota") - { - return; - } + if(!request->url().endsWith("/uploadota")) return; - HTTPUpload& upload = _server.upload(); - - if(upload.filename == "") + if(filename == "") { Log->println("Invalid file for OTA upload"); return; } - if(_partitionType == 1 && _server.header("Content-Length").toInt() > 1600000) + if (!index) { - if(upload.totalSize < 2000) Log->println("Uploaded OTA file too large, are you trying to upload a Nuki Hub binary instead of a Nuki Hub updater binary?"); - return; - } - else if(_partitionType == 2 && _server.header("Content-Length").toInt() < 1600000) - { - if(upload.totalSize < 2000) Log->println("Uploaded OTA file is too small, are you trying to upload a Nuki Hub updater binary instead of a Nuki Hub binary?"); - return; - } + Log->println("Starting manual OTA update"); + _otaContentLen = request->contentLength(); - if (upload.status == UPLOAD_FILE_START) - { - String filename = upload.filename; - if (!filename.startsWith("/")) + if(_partitionType == 1 && _otaContentLen > 1600000) { - filename = "/" + filename; + Log->println("Uploaded OTA file too large, are you trying to upload a Nuki Hub binary instead of a Nuki Hub updater binary?"); + return; } + else if(_partitionType == 2 && _otaContentLen < 1600000) + { + Log->println("Uploaded OTA file is too small, are you trying to upload a Nuki Hub updater binary instead of a Nuki Hub binary?"); + return; + } + + int cmd = U_FLASH; + if (!Update.begin(UPDATE_SIZE_UNKNOWN, cmd)) { + Update.printError(Serial); + } + _otaStartTs = esp_timer_get_time() / 1000; esp_task_wdt_config_t twdt_config = { .timeout_ms = 30000, @@ -665,35 +517,40 @@ void WebCfgServer::handleOtaUpload() _nukiOpener->disableWatchdog(); } #endif - Log->print("handleFileUpload Name: "); Log->println(filename); + Log->print("handleFileUpload Name: "); + Log->println(filename); } - else if (upload.status == UPLOAD_FILE_WRITE) - { - _transferredSize = _transferredSize + upload.currentSize; - Log->println(_transferredSize); - _ota.updateFirmware(upload.buf, upload.currentSize); - } else if (upload.status == UPLOAD_FILE_END) - { - Log->println(); - Log->print("handleFileUpload Size: "); Log->println(upload.totalSize); - } - else if(upload.status == UPLOAD_FILE_ABORTED) - { - Log->println(); - Log->println("OTA aborted, restarting ESP."); + + if (_otaContentLen == 0) return; + + if (Update.write(data, len) != len) { + Update.printError(Serial); restartEsp(RestartReason::OTAAborted); } - else - { - Log->println(); - Log->print("OTA unknown state: "); - Log->println((int)upload.status); - restartEsp(RestartReason::OTAUnknownState); + + if (final) { + AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots"); + response->addHeader("Refresh", "20"); + response->addHeader("Location", "/"); + request->send(response); + if (!Update.end(true)){ + Update.printError(Serial); + restartEsp(RestartReason::OTAAborted); + } else { + Log->print(F("Progress: 100%")); + Log->println(); + Log->print("handleFileUpload Total Size: "); + Log->println(index+len); + Log->println("Update complete"); + Log->flush(); + restartEsp(RestartReason::OTACompleted); + } } } -void WebCfgServer::buildConfirmHtml(const String &message, uint32_t redirectDelay, bool redirect) +void WebCfgServer::buildConfirmHtml(AsyncWebServerRequest *request, const String &message, uint32_t redirectDelay, bool redirect) { + AsyncResponseStream *response = request->beginResponseStream("text/html"); String header; if(!redirect) @@ -706,22 +563,25 @@ void WebCfgServer::buildConfirmHtml(const String &message, uint32_t redirectDela String delay(redirectDelay * 1000); header = ""; } - buildHtmlHeader(header); - _response.concat(message); - _response.concat(""); + buildHtmlHeader(response, header); + response->print(message); + response->print(""); + request->send(response); } -void WebCfgServer::sendCss() +void WebCfgServer::sendCss(AsyncWebServerRequest *request) { // escaped by https://www.cescaper.com/ - _server.sendHeader("Cache-Control", "public, max-age=3600"); - _server.send(200, "text/css", stylecss, sizeof(stylecss)); + AsyncWebServerResponse *asyncResponse = request->beginResponse(200, "text/css", (const uint8_t*)stylecss, sizeof(stylecss)); + asyncResponse ->addHeader("Cache-Control", "public, max-age=3600"); + request->send(asyncResponse ); } -void WebCfgServer::sendFavicon() +void WebCfgServer::sendFavicon(AsyncWebServerRequest *request) { - _server.sendHeader("Cache-Control", "public, max-age=604800"); - _server.send(200, "image/png", (const char*)favicon_32x32, sizeof(favicon_32x32)); + AsyncWebServerResponse *asyncResponse = request->beginResponse(200, "image/png", (const uint8_t*)favicon_32x32, sizeof(favicon_32x32)); + asyncResponse->addHeader("Cache-Control", "public, max-age=604800"); + request->send(asyncResponse); } String WebCfgServer::generateConfirmCode() @@ -731,24 +591,20 @@ String WebCfgServer::generateConfirmCode() } #ifndef NUKI_HUB_UPDATER -void WebCfgServer::sendSettings() +void WebCfgServer::sendSettings(AsyncWebServerRequest *request) { bool redacted = false; bool pairing = false; - String key = _server.argName(0); - String value = _server.arg(0); - if(key == "redacted" && value == "1") + if(request->hasParam("redacted")) { - redacted = true; + const AsyncWebParameter* p = request->getParam("redacted"); + if(p->value() == "1") redacted = true; } - - String key2 = _server.argName(1); - String value2 = _server.arg(1); - - if(key2 == "pairing" && value2 == "1") + if(request->hasParam("pairing")) { - pairing = true; + const AsyncWebParameter* p = request->getParam("pairing"); + if(p->value() == "1") pairing = true; } JsonDocument json; @@ -760,17 +616,14 @@ void WebCfgServer::sendSettings() const std::vector boolPrefs = debugPreferences.getPreferencesBoolKeys(); const std::vector redactedPrefs = debugPreferences.getPreferencesRedactedKeys(); const std::vector bytePrefs = debugPreferences.getPreferencesByteKeys(); - const std::vector charPrefs = debugPreferences.getPreferencesCharKeys(); for(const auto& key : keysPrefs) { if(strcmp(key, preference_show_secrets) == 0) continue; if(strcmp(key, preference_latest_version) == 0) continue; - if(strcmp(key, preference_has_mac_saved) == 0) continue; if(strcmp(key, preference_device_id_lock) == 0) continue; if(strcmp(key, preference_device_id_opener) == 0) continue; if(!redacted) if(std::find(redactedPrefs.begin(), redactedPrefs.end(), key) != redactedPrefs.end()) continue; - if(std::find(charPrefs.begin(), charPrefs.end(), key) != charPrefs.end()) continue; if(!_preferences->isKey(key)) json[key] = ""; else if(std::find(boolPrefs.begin(), boolPrefs.end(), key) != boolPrefs.end()) json[key] = _preferences->getBool(key) ? "1" : "0"; else @@ -909,11 +762,13 @@ void WebCfgServer::sendSettings() serializeJsonPretty(json, jsonPretty); - _server.sendHeader("Content-Disposition", "attachment; filename=nuki_hub.json"); - _server.send(200, "application/json", jsonPretty); + AsyncWebServerResponse *asyncResponse = request->beginResponse(200, "application/json", jsonPretty); + asyncResponse->addHeader("Content-Disposition", "attachment; filename=nuki_hub.json"); + request->send(asyncResponse); + } -bool WebCfgServer::processArgs(String& message) +bool WebCfgServer::processArgs(AsyncWebServerRequest *request, String& message) { bool configChanged = false; bool aclLvlChanged = false; @@ -935,25 +790,42 @@ bool WebCfgServer::processArgs(String& message) uint32_t advancedLockConfigAclPrefs[22] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; uint32_t advancedOpenerConfigAclPrefs[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - int count = _server.args(); + int params = request->params(); String pass1 = ""; String pass2 = ""; - for(int index = 0; index < count; index++) + for(int index = 0; index < params; index++) { - String key = _server.argName(index); - String value = _server.arg(index); + const AsyncWebParameter* p = request->getParam(index); + String key = p->name(); + String value = p->value(); + + if(index < params -1) + { + const AsyncWebParameter* next = request->getParam(index+1); + if(key == next->name()) continue; + } if(key == "MQTTSERVER") { - _preferences->putString(preference_mqtt_broker, value); - configChanged = true; + if(_preferences->getString(preference_mqtt_broker, "") != value) + { + _preferences->putString(preference_mqtt_broker, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "MQTTPORT") { - _preferences->putInt(preference_mqtt_broker_port, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_mqtt_broker_port, 0) != value.toInt()) + { + _preferences->putInt(preference_mqtt_broker_port, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "MQTTUSER") { @@ -963,299 +835,681 @@ bool WebCfgServer::processArgs(String& message) } else { - _preferences->putString(preference_mqtt_user, value); - configChanged = true; + if(_preferences->getString(preference_mqtt_user, "") != value) + { + _preferences->putString(preference_mqtt_user, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } } else if(key == "MQTTPASS") { if(value != "*") { - _preferences->putString(preference_mqtt_password, value); - configChanged = true; + if(_preferences->getString(preference_mqtt_password, "") != value) + { + _preferences->putString(preference_mqtt_password, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } } else if(key == "MQTTPATH") { - _preferences->putString(preference_mqtt_lock_path, value); - configChanged = true; + if(_preferences->getString(preference_mqtt_lock_path, "") != value) + { + _preferences->putString(preference_mqtt_lock_path, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "MQTTOPPATH") { - _preferences->putString(preference_mqtt_opener_path, value); - configChanged = true; + if(_preferences->getString(preference_mqtt_opener_path, "") != value) + { + _preferences->putString(preference_mqtt_opener_path, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "MQTTCA") { - _preferences->putString(preference_mqtt_ca, value); - configChanged = true; + if(_preferences->getString(preference_mqtt_ca, "") != value) + { + _preferences->putString(preference_mqtt_ca, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "MQTTCRT") { - _preferences->putString(preference_mqtt_crt, value); - configChanged = true; + if(_preferences->getString(preference_mqtt_crt, "") != value) + { + _preferences->putString(preference_mqtt_crt, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "MQTTKEY") { - _preferences->putString(preference_mqtt_key, value); - configChanged = true; + if(_preferences->getString(preference_mqtt_key, "") != value) + { + _preferences->putString(preference_mqtt_key, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "NWHW") { - _preferences->putInt(preference_network_hardware, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_network_hardware, 0) != value.toInt()) + { + if(value.toInt() > 1) + { + _preferences->putBool(preference_ntw_reconfigure, true); + if(value.toInt() != 11) _preferences->putInt(preference_network_custom_phy, 0); + } + _preferences->putInt(preference_network_hardware, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "NWCUSTPHY") + { + if(_preferences->getInt(preference_network_custom_phy, 0) != value.toInt()) + { + _preferences->putBool(preference_ntw_reconfigure, true); + _preferences->putInt(preference_network_custom_phy, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "NWCUSTADDR") + { + if(_preferences->getInt(preference_network_custom_addr, 0) != value.toInt()) + { + _preferences->putBool(preference_ntw_reconfigure, true); + _preferences->putInt(preference_network_custom_addr, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "NWCUSTIRQ") + { + if(_preferences->getInt(preference_network_custom_irq, 0) != value.toInt()) + { + _preferences->putBool(preference_ntw_reconfigure, true); + _preferences->putInt(preference_network_custom_irq, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "NWCUSTRST") + { + if(_preferences->getInt(preference_network_custom_rst, 0) != value.toInt()) + { + _preferences->putBool(preference_ntw_reconfigure, true); + _preferences->putInt(preference_network_custom_rst, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "NWCUSTCS") + { + if(_preferences->getInt(preference_network_custom_cs, 0) != value.toInt()) + { + _preferences->putBool(preference_ntw_reconfigure, true); + _preferences->putInt(preference_network_custom_cs, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "NWCUSTSCK") + { + if(_preferences->getInt(preference_network_custom_sck, 0) != value.toInt()) + { + _preferences->putBool(preference_ntw_reconfigure, true); + _preferences->putInt(preference_network_custom_sck, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "NWCUSTMISO") + { + if(_preferences->getInt(preference_network_custom_miso, 0) != value.toInt()) + { + _preferences->putBool(preference_ntw_reconfigure, true); + _preferences->putInt(preference_network_custom_miso, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "NWCUSTMOSI") + { + if(_preferences->getInt(preference_network_custom_mosi, 0) != value.toInt()) + { + _preferences->putBool(preference_ntw_reconfigure, true); + _preferences->putInt(preference_network_custom_mosi, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "NWCUSTPWR") + { + if(_preferences->getInt(preference_network_custom_pwr, 0) != value.toInt()) + { + _preferences->putBool(preference_ntw_reconfigure, true); + _preferences->putInt(preference_network_custom_pwr, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "NWCUSTMDIO") + { + if(_preferences->getInt(preference_network_custom_mdio, 0) != value.toInt()) + { + _preferences->putBool(preference_ntw_reconfigure, true); + _preferences->putInt(preference_network_custom_mdio, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "NWCUSTMDC") + { + if(_preferences->getInt(preference_network_custom_mdc, 0) != value.toInt()) + { + _preferences->putBool(preference_ntw_reconfigure, true); + _preferences->putInt(preference_network_custom_mdc, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } + } + else if(key == "NWCUSTCLK") + { + if(_preferences->getInt(preference_network_custom_clk, 0) != value.toInt()) + { + _preferences->putBool(preference_ntw_reconfigure, true); + _preferences->putInt(preference_network_custom_clk, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "NWHWWIFIFB") { - _preferences->putBool(preference_network_wifi_fallback_disabled, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_network_wifi_fallback_disabled, false) != (value == "1")) + { + _preferences->putBool(preference_network_wifi_fallback_disabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "RSSI") { - _preferences->putInt(preference_rssi_publish_interval, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_rssi_publish_interval, 60) != value.toInt()) + { + _preferences->putInt(preference_rssi_publish_interval, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "HASSDISCOVERY") { - if(_preferences->getString(preference_mqtt_hass_discovery) != value) + if(_preferences->getString(preference_mqtt_hass_discovery, "") != value) { - // Previous HASS config has to be disabled first (remove retained MQTT messages) - if (_nuki != nullptr) - { - _nuki->disableHASS(); - } - if (_nukiOpener != nullptr) - { - _nukiOpener->disableHASS(); - } + if (_nuki != nullptr) _nuki->disableHASS(); + if (_nukiOpener != nullptr) _nukiOpener->disableHASS(); _preferences->putString(preference_mqtt_hass_discovery, value); + Log->print(F("Setting changed: ")); + Log->println(key); configChanged = true; } } else if(key == "OPENERCONT") { - _preferences->putBool(preference_opener_continuous_mode, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_opener_continuous_mode, false) != (value == "1")) + { + _preferences->putBool(preference_opener_continuous_mode, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "HASSCUURL") { - _preferences->putString(preference_mqtt_hass_cu_url, value); - configChanged = true; + if(_preferences->getString(preference_mqtt_hass_cu_url, "") != value) + { + _preferences->putString(preference_mqtt_hass_cu_url, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "BESTRSSI") { - _preferences->putBool(preference_find_best_rssi, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_find_best_rssi, false) != (value == "1")) + { + _preferences->putBool(preference_find_best_rssi, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "HOSTNAME") { - _preferences->putString(preference_hostname, value); - configChanged = true; + if(_preferences->getString(preference_hostname, "") != value) + { + _preferences->putString(preference_hostname, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "NETTIMEOUT") { - _preferences->putInt(preference_network_timeout, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_network_timeout, 60) != value.toInt()) + { + _preferences->putInt(preference_network_timeout, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "RSTDISC") { - _preferences->putBool(preference_restart_on_disconnect, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_restart_on_disconnect, false) != (value == "1")) + { + _preferences->putBool(preference_restart_on_disconnect, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "RECNWTMQTTDIS") { - _preferences->putBool(preference_recon_netw_on_mqtt_discon, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_recon_netw_on_mqtt_discon, false) != (value == "1")) + { + _preferences->putBool(preference_recon_netw_on_mqtt_discon, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "MQTTLOG") { - _preferences->putBool(preference_mqtt_log_enabled, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_mqtt_log_enabled, false) != (value == "1")) + { + _preferences->putBool(preference_mqtt_log_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "WEBLOG") { - _preferences->putBool(preference_webserial_enabled, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_webserial_enabled, false) != (value == "1")) + { + _preferences->putBool(preference_webserial_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "CHECKUPDATE") { - _preferences->putBool(preference_check_updates, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_check_updates, false) != (value == "1")) + { + _preferences->putBool(preference_check_updates, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "UPDATEMQTT") { - _preferences->putBool(preference_update_from_mqtt, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_update_from_mqtt, false) != (value == "1")) + { + _preferences->putBool(preference_update_from_mqtt, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "OFFHYBRID") { - _preferences->putBool(preference_official_hybrid, (value == "1")); - if((value == "1")) _preferences->putBool(preference_register_as_app, true); - configChanged = true; + if(_preferences->getBool(preference_official_hybrid, false) != (value == "1")) + { + _preferences->putBool(preference_official_hybrid, (value == "1")); + if((value == "1")) _preferences->putBool(preference_register_as_app, true); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "HYBRIDACT") { - _preferences->putBool(preference_official_hybrid_actions, (value == "1")); - if(value == "1") _preferences->putBool(preference_register_as_app, true); - configChanged = true; + if(_preferences->getBool(preference_official_hybrid_actions, false) != (value == "1")) + { + _preferences->putBool(preference_official_hybrid_actions, (value == "1")); + if(value == "1") _preferences->putBool(preference_register_as_app, true); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "HYBRIDTIMER") { - _preferences->putInt(preference_query_interval_hybrid_lockstate, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_query_interval_hybrid_lockstate, 600) != value.toInt()) + { + _preferences->putInt(preference_query_interval_hybrid_lockstate, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "HYBRIDRETRY") { - _preferences->putBool(preference_official_hybrid_retry, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_official_hybrid_retry, false) != (value == "1")) + { + _preferences->putBool(preference_official_hybrid_retry, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "DISNONJSON") { - _preferences->putBool(preference_disable_non_json, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_disable_non_json, false) != (value == "1")) + { + _preferences->putBool(preference_disable_non_json, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "DHCPENA") { - _preferences->putBool(preference_ip_dhcp_enabled, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_ip_dhcp_enabled, true) != (value == "1")) + { + _preferences->putBool(preference_ip_dhcp_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "IPADDR") { - _preferences->putString(preference_ip_address, value); - configChanged = true; + if(_preferences->getString(preference_ip_address, "") != value) + { + _preferences->putString(preference_ip_address, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "IPSUB") { - _preferences->putString(preference_ip_subnet, value); - configChanged = true; + if(_preferences->getString(preference_ip_subnet, "") != value) + { + _preferences->putString(preference_ip_subnet, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "IPGTW") { - _preferences->putString(preference_ip_gateway, value); - configChanged = true; + if(_preferences->getString(preference_ip_gateway, "") != value) + { + _preferences->putString(preference_ip_gateway, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "DNSSRV") { - _preferences->putString(preference_ip_dns_server, value); - configChanged = true; + if(_preferences->getString(preference_ip_dns_server, "") != value) + { + _preferences->putString(preference_ip_dns_server, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "LSTINT") { - _preferences->putInt(preference_query_interval_lockstate, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_query_interval_lockstate, 1800) != value.toInt()) + { + _preferences->putInt(preference_query_interval_lockstate, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "CFGINT") { - _preferences->putInt(preference_query_interval_configuration, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_query_interval_configuration, 3600) != value.toInt()) + { + _preferences->putInt(preference_query_interval_configuration, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "BATINT") { - _preferences->putInt(preference_query_interval_battery, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_query_interval_battery, 1800) != value.toInt()) + { + _preferences->putInt(preference_query_interval_battery, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "KPINT") { - _preferences->putInt(preference_query_interval_keypad, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_query_interval_keypad, 1800) != value.toInt()) + { + _preferences->putInt(preference_query_interval_keypad, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "NRTRY") { - _preferences->putInt(preference_command_nr_of_retries, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_command_nr_of_retries, 3) != value.toInt()) + { + _preferences->putInt(preference_command_nr_of_retries, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "TRYDLY") { - _preferences->putInt(preference_command_retry_delay, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_command_retry_delay, 100) != value.toInt()) + { + _preferences->putInt(preference_command_retry_delay, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "TXPWR") { if(value.toInt() >= -12 && value.toInt() <= 9) { - _preferences->putInt(preference_ble_tx_power, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_ble_tx_power, 9) != value.toInt()) + { + _preferences->putInt(preference_ble_tx_power, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } } #if PRESENCE_DETECTION_ENABLED else if(key == "PRDTMO") { - _preferences->putInt(preference_presence_detection_timeout, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_presence_detection_timeout, 60) != value.toInt()) + { + _preferences->putInt(preference_presence_detection_timeout, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } #endif else if(key == "RSBC") { - _preferences->putInt(preference_restart_ble_beacon_lost, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_restart_ble_beacon_lost, 60) != value.toInt()) + { + _preferences->putInt(preference_restart_ble_beacon_lost, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "TSKNTWK") { if(value.toInt() > 12287 && value.toInt() < 32769) { - _preferences->putInt(preference_task_size_network, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE) != value.toInt()) + { + _preferences->putInt(preference_task_size_network, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } } else if(key == "TSKNUKI") { if(value.toInt() > 8191 && value.toInt() < 32769) { - _preferences->putInt(preference_task_size_nuki, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE) != value.toInt()) + { + _preferences->putInt(preference_task_size_nuki, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } } else if(key == "ALMAX") { if(value.toInt() > 0 && value.toInt() < 51) { - _preferences->putInt(preference_authlog_max_entries, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG) != value.toInt()) + { + _preferences->putInt(preference_authlog_max_entries, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } } else if(key == "KPMAX") { if(value.toInt() > 0 && value.toInt() < 101) { - _preferences->putInt(preference_keypad_max_entries, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD) != value.toInt()) + { + _preferences->putInt(preference_keypad_max_entries, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } } else if(key == "TCMAX") { if(value.toInt() > 0 && value.toInt() < 51) { - _preferences->putInt(preference_timecontrol_max_entries, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_timecontrol_max_entries, MAX_TIMECONTROL) != value.toInt()) + { + _preferences->putInt(preference_timecontrol_max_entries, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } } else if(key == "BUFFSIZE") { if(value.toInt() > 4095 && value.toInt() < 32769) { - _preferences->putInt(preference_buffer_size, value.toInt()); - configChanged = true; + if(_preferences->getInt(preference_buffer_size, CHAR_BUFFER_SIZE) != value.toInt()) + { + _preferences->putInt(preference_buffer_size, value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } } else if(key == "BTLPRST") { - _preferences->putBool(preference_enable_bootloop_reset, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_enable_bootloop_reset, false) != (value == "1")) + { + _preferences->putBool(preference_enable_bootloop_reset, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "OTAUPD") { - _preferences->putString(preference_ota_updater_url, value); - configChanged = true; + if(_preferences->getString(preference_ota_updater_url, "") != value) + { + _preferences->putString(preference_ota_updater_url, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "OTAMAIN") { - _preferences->putString(preference_ota_main_url, value); - configChanged = true; + if(_preferences->getString(preference_ota_main_url, "") != value) + { + _preferences->putString(preference_ota_main_url, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "SHOWSECRETS") { - _preferences->putBool(preference_show_secrets, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_show_secrets, false) != (value == "1")) + { + _preferences->putBool(preference_show_secrets, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "ACLLVLCHANGED") { @@ -1263,48 +1517,93 @@ bool WebCfgServer::processArgs(String& message) } else if(key == "CONFPUB") { - _preferences->putBool(preference_conf_info_enabled, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_conf_info_enabled, true) != (value == "1")) + { + _preferences->putBool(preference_conf_info_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "KPPUB") { - _preferences->putBool(preference_keypad_info_enabled, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_keypad_info_enabled, false) != (value == "1")) + { + _preferences->putBool(preference_keypad_info_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "KPCODE") { - _preferences->putBool(preference_keypad_publish_code, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_keypad_publish_code, false) != (value == "1")) + { + _preferences->putBool(preference_keypad_publish_code, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "KPENA") { - _preferences->putBool(preference_keypad_control_enabled, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_keypad_control_enabled, false) != (value == "1")) + { + _preferences->putBool(preference_keypad_control_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "TCPUB") { - _preferences->putBool(preference_timecontrol_info_enabled, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_timecontrol_info_enabled, false) != (value == "1")) + { + _preferences->putBool(preference_timecontrol_info_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "KPPER") { - _preferences->putBool(preference_keypad_topic_per_entry, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_keypad_topic_per_entry, false) != (value == "1")) + { + _preferences->putBool(preference_keypad_topic_per_entry, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "TCPER") { - _preferences->putBool(preference_timecontrol_topic_per_entry, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_timecontrol_topic_per_entry, false) != (value == "1")) + { + _preferences->putBool(preference_timecontrol_topic_per_entry, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "TCENA") { - _preferences->putBool(preference_timecontrol_control_enabled, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_timecontrol_control_enabled, false) != (value == "1")) + { + _preferences->putBool(preference_timecontrol_control_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "PUBAUTH") { - _preferences->putBool(preference_publish_authdata, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_publish_authdata, false) != (value == "1")) + { + _preferences->putBool(preference_publish_authdata, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "ACLLCKLCK") { @@ -1664,23 +1963,43 @@ bool WebCfgServer::processArgs(String& message) } else if(key == "REGAPP") { - _preferences->putBool(preference_register_as_app, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_register_as_app, false) != (value == "1")) + { + _preferences->putBool(preference_register_as_app, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "REGAPPOPN") { - _preferences->putBool(preference_register_opener_as_app, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_register_opener_as_app, false) != (value == "1")) + { + _preferences->putBool(preference_register_opener_as_app, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "LOCKENA") { - _preferences->putBool(preference_lock_enabled, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_lock_enabled, true) != (value == "1")) + { + _preferences->putBool(preference_lock_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "OPENA") { - _preferences->putBool(preference_opener_enabled, (value == "1")); - configChanged = true; + if(_preferences->getBool(preference_opener_enabled, false) != (value == "1")) + { + _preferences->putBool(preference_opener_enabled, (value == "1")); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } else if(key == "CREDUSER") { @@ -1690,8 +2009,13 @@ bool WebCfgServer::processArgs(String& message) } else { - _preferences->putString(preference_cred_user, value); - configChanged = true; + if(_preferences->getString(preference_cred_user, "") != value) + { + _preferences->putString(preference_cred_user, value); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } } else if(key == "CREDPASS") @@ -1708,13 +2032,21 @@ bool WebCfgServer::processArgs(String& message) { message = "Nuki Lock PIN cleared"; _nuki->setPin(0xffff); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; } else { - message = "Nuki Lock PIN saved"; - _nuki->setPin(value.toInt()); + if(_nuki->getPin() != value.toInt()) + { + message = "Nuki Lock PIN saved"; + _nuki->setPin(value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } - configChanged = true; } else if(key == "NUKIOPPIN" && _nukiOpener != nullptr) { @@ -1722,13 +2054,21 @@ bool WebCfgServer::processArgs(String& message) { message = "Nuki Opener PIN cleared"; _nukiOpener->setPin(0xffff); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; } else { - message = "Nuki Opener PIN saved"; - _nukiOpener->setPin(value.toInt()); + if(_nukiOpener->getPin() != value.toInt()) + { + message = "Nuki Opener PIN saved"; + _nukiOpener->setPin(value.toInt()); + Log->print(F("Setting changed: ")); + Log->println(key); + configChanged = true; + } } - configChanged = true; } else if(key == "LCKMANPAIR" && (value == "1")) { @@ -1774,6 +2114,8 @@ bool WebCfgServer::processArgs(String& message) nukiBlePref.putBytes("authorizationId", authorizationId, 4); nukiBlePref.putBytes("securityPinCode", pincode, 2); nukiBlePref.end(); + Log->print(F("Setting changed: ")); + Log->println("Lock pairing data"); configChanged = true; } @@ -1787,50 +2129,140 @@ bool WebCfgServer::processArgs(String& message) nukiBlePref.putBytes("authorizationId", authorizationIdOpn, 4); nukiBlePref.putBytes("securityPinCode", pincode, 2); nukiBlePref.end(); + Log->print(F("Setting changed: ")); + Log->println("Opener pairing data"); configChanged = true; } if(pass1 != "" && pass1 == pass2) { - _preferences->putString(preference_cred_password, pass1); - configChanged = true; + if(_preferences->getString(preference_cred_password, "") != pass1) + { + _preferences->putString(preference_cred_password, pass1); + Log->print(F("Setting changed: ")); + Log->println("CREDPASS"); + configChanged = true; + } } if(clearMqttCredentials) { - _preferences->putString(preference_mqtt_user, ""); - _preferences->putString(preference_mqtt_password, ""); - configChanged = true; + if(_preferences->getString(preference_mqtt_user, "") != "") + { + _preferences->putString(preference_mqtt_user, ""); + Log->print(F("Setting changed: ")); + Log->println("MQTTUSER"); + configChanged = true; + } + if(_preferences->getString(preference_mqtt_password, "") != "") + { + _preferences->putString(preference_mqtt_password, ""); + Log->print(F("Setting changed: ")); + Log->println("MQTTPASS"); + configChanged = true; + } } if(clearCredentials) { - _preferences->putString(preference_cred_user, ""); - _preferences->putString(preference_cred_password, ""); - configChanged = true; + if(_preferences->getString(preference_cred_user, "") != "") + { + _preferences->putString(preference_cred_user, ""); + Log->print(F("Setting changed: ")); + Log->println("CREDUSER"); + configChanged = true; + } + if(_preferences->getString(preference_cred_password, "") != "") + { + _preferences->putString(preference_cred_password, ""); + Log->print(F("Setting changed: ")); + Log->println("CREDPASS"); + configChanged = true; + } } if(aclLvlChanged) { - _preferences->putBytes(preference_acl, (byte*)(&aclPrefs), sizeof(aclPrefs)); - _preferences->putBytes(preference_conf_lock_basic_acl, (byte*)(&basicLockConfigAclPrefs), sizeof(basicLockConfigAclPrefs)); - _preferences->putBytes(preference_conf_opener_basic_acl, (byte*)(&basicOpenerConfigAclPrefs), sizeof(basicOpenerConfigAclPrefs)); - _preferences->putBytes(preference_conf_lock_advanced_acl, (byte*)(&advancedLockConfigAclPrefs), sizeof(advancedLockConfigAclPrefs)); - _preferences->putBytes(preference_conf_opener_advanced_acl, (byte*)(&advancedOpenerConfigAclPrefs), sizeof(advancedOpenerConfigAclPrefs)); - configChanged = true; + uint32_t curAclPrefs[17] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint32_t curBasicLockConfigAclPrefs[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint32_t curAdvancedLockConfigAclPrefs[22] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint32_t curBasicOpenerConfigAclPrefs[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint32_t curAdvancedOpenerConfigAclPrefs[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + _preferences->getBytes(preference_acl, &curAclPrefs, sizeof(curAclPrefs)); + _preferences->getBytes(preference_conf_lock_basic_acl, &curBasicLockConfigAclPrefs, sizeof(curBasicLockConfigAclPrefs)); + _preferences->getBytes(preference_conf_lock_advanced_acl, &curAdvancedLockConfigAclPrefs, sizeof(curAdvancedLockConfigAclPrefs)); + _preferences->getBytes(preference_conf_opener_basic_acl, &curBasicOpenerConfigAclPrefs, sizeof(curBasicOpenerConfigAclPrefs)); + _preferences->getBytes(preference_conf_opener_advanced_acl, &curAdvancedOpenerConfigAclPrefs, sizeof(curAdvancedOpenerConfigAclPrefs)); + + for(int i=0; i < 17; i++) + { + if(curAclPrefs[i] != aclPrefs[i]) + { + _preferences->putBytes(preference_acl, (byte*)(&aclPrefs), sizeof(aclPrefs)); + Log->print(F("Setting changed: ")); + Log->println("ACLPREFS"); + configChanged = true; + break; + } + } + for(int i=0; i < 16; i++) + { + if(curBasicLockConfigAclPrefs[i] != basicLockConfigAclPrefs[i]) + { + _preferences->putBytes(preference_conf_lock_basic_acl, (byte*)(&basicLockConfigAclPrefs), sizeof(basicLockConfigAclPrefs)); + Log->print(F("Setting changed: ")); + Log->println("ACLCONFBASICLOCK"); + configChanged = true; + break; + } + } + for(int i=0; i < 22; i++) + { + if(curAdvancedLockConfigAclPrefs[i] != advancedLockConfigAclPrefs[i]) + { + _preferences->putBytes(preference_conf_opener_basic_acl, (byte*)(&advancedLockConfigAclPrefs), sizeof(advancedLockConfigAclPrefs)); + Log->print(F("Setting changed: ")); + Log->println("ACLCONFADVANCEDLOCK"); + configChanged = true; + break; + + } + } + for(int i=0; i < 14; i++) + { + if(curBasicOpenerConfigAclPrefs[i] != basicOpenerConfigAclPrefs[i]) + { + _preferences->putBytes(preference_conf_lock_advanced_acl, (byte*)(&basicOpenerConfigAclPrefs), sizeof(basicOpenerConfigAclPrefs)); + Log->print(F("Setting changed: ")); + Log->println("ACLCONFBASICOPENER"); + configChanged = true; + break; + } + } + for(int i=0; i < 20; i++) + { + if(curAdvancedOpenerConfigAclPrefs[i] != advancedOpenerConfigAclPrefs[i]) + { + _preferences->putBytes(preference_conf_opener_advanced_acl, (byte*)(&advancedOpenerConfigAclPrefs), sizeof(advancedOpenerConfigAclPrefs)); + Log->print(F("Setting changed: ")); + Log->println("ACLCONFBADVANCEDOPENER"); + configChanged = true; + break; + } + } } if(configChanged) { - message = "Configuration saved ... restarting."; - _enabled = false; - _preferences->end(); + message = "Configuration saved."; + _rebootRequired = true; } + else message = "Nothing changed."; return configChanged; } -bool WebCfgServer::processImport(String& message) +bool WebCfgServer::processImport(AsyncWebServerRequest *request, String& message) { bool configChanged = false; unsigned char currentBleAddress[6]; @@ -1840,18 +2272,16 @@ bool WebCfgServer::processImport(String& message) unsigned char authorizationIdOpn[4] = {0x00}; unsigned char secretKeyKOpn[32] = {0x00}; - int count = _server.args(); + int params = request->params(); - for(int index = 0; index < count; index++) + for(int index = 0; index < params; index++) { - String postKey = _server.argName(index); - String postValue = _server.arg(index); - - if(postKey == "importjson") + const AsyncWebParameter* p = request->getParam(index); + if(p->name() == "importjson") { JsonDocument doc; - DeserializationError error = deserializeJson(doc, postValue); + DeserializationError error = deserializeJson(doc, p->value()); if (error) { Log->println("Invalid JSON for import"); @@ -1864,7 +2294,6 @@ bool WebCfgServer::processImport(String& message) const std::vector keysPrefs = debugPreferences.getPreferencesKeys(); const std::vector boolPrefs = debugPreferences.getPreferencesBoolKeys(); const std::vector bytePrefs = debugPreferences.getPreferencesByteKeys(); - const std::vector charPrefs = debugPreferences.getPreferencesCharKeys(); const std::vector intPrefs = debugPreferences.getPreferencesIntKeys(); for(const auto& key : keysPrefs) @@ -1872,10 +2301,8 @@ bool WebCfgServer::processImport(String& message) if(doc[key].isNull()) continue; if(strcmp(key, preference_show_secrets) == 0) continue; if(strcmp(key, preference_latest_version) == 0) continue; - if(strcmp(key, preference_has_mac_saved) == 0) continue; if(strcmp(key, preference_device_id_lock) == 0) continue; if(strcmp(key, preference_device_id_opener) == 0) continue; - if(std::find(charPrefs.begin(), charPrefs.end(), key) != charPrefs.end()) continue; if(std::find(boolPrefs.begin(), boolPrefs.end(), key) != boolPrefs.end()) { if (doc[key].as().length() > 0) _preferences->putBool(key, (doc[key].as() == "1" ? true : false)); @@ -1936,7 +2363,7 @@ bool WebCfgServer::processImport(String& message) } } nukiBlePref.end(); - if(!doc["securityPinCodeLock"].isNull()) + if(!doc["securityPinCodeLock"].isNull() && _nuki != nullptr) { if(doc["securityPinCodeLock"].as().length() > 0) _nuki->setPin(doc["securityPinCodeLock"].as()); else _nuki->setPin(0xffff); @@ -1970,7 +2397,7 @@ bool WebCfgServer::processImport(String& message) } } nukiBlePref.end(); - if(!doc["securityPinCodeOpener"].isNull()) + if(!doc["securityPinCodeOpener"].isNull() && _nukiOpener != nullptr) { if(doc["securityPinCodeOpener"].as().length() > 0) _nukiOpener->setPin(doc["securityPinCodeOpener"].as()); else _nukiOpener->setPin(0xffff); @@ -1982,30 +2409,26 @@ bool WebCfgServer::processImport(String& message) if(configChanged) { - message = "Configuration saved ... restarting."; - _enabled = false; - _preferences->end(); + message = "Configuration saved."; + _rebootRequired = true; } return configChanged; } -void WebCfgServer::processGpioArgs() +void WebCfgServer::processGpioArgs(AsyncWebServerRequest *request) { - int count = _server.args(); - + int params = request->params(); std::vector pinConfiguration; - for(int index = 0; index < count; index++) + for(int index = 0; index < params; index++) { - String key = _server.argName(index); - String value = _server.arg(index); - - PinRole role = (PinRole)value.toInt(); + const AsyncWebParameter* p = request->getParam(index); + PinRole role = (PinRole)p->value().toInt(); if(role != PinRole::Disabled) { PinEntry entry; - entry.pin = key.toInt(); + entry.pin = p->name().toInt(); entry.role = role; pinConfiguration.push_back(entry); } @@ -2014,59 +2437,95 @@ void WebCfgServer::processGpioArgs() _gpio->savePinConfiguration(pinConfiguration); } -void WebCfgServer::buildImportExportHtml() +void WebCfgServer::buildImportExportHtml(AsyncWebServerRequest *request) { - buildHtmlHeader(); + AsyncResponseStream *response = request->beginResponseStream("text/html"); + buildHtmlHeader(response); - _response.concat("

Import configuration

"); - _response.concat("

"); - _response.concat("


"); - _response.concat("
"); - _response.concat("

Export configuration


"); - _response.concat(""); - _response.concat("

"); - _response.concat("

"); - _response.concat("
Initiating config update. Please be patient.
You will be forwarded automatically when the import is complete.
"); - _response.concat(""); - _response.concat(""); + response->print("

Import configuration

"); + response->print("

"); + response->print("


"); + response->print("
"); + response->print("

Export configuration


"); + response->print(""); + response->print("

"); + response->print("

"); + response->print("
Initiating config update. Please be patient.
You will be forwarded automatically when the import is complete.
"); + response->print(""); + response->print(""); + request->send(response); } -void WebCfgServer::buildHtml() +void WebCfgServer::buildCustomNetworkConfigHtml(AsyncWebServerRequest *request) +{ + String header = ""; + AsyncResponseStream *response = request->beginResponseStream("text/html"); + buildHtmlHeader(response, header); + response->print("
"); + response->print("

Custom Ethernet Configuration

"); + response->print(""); + printDropDown(response, "NWCUSTPHY", "PHY", String(_preferences->getInt(preference_network_custom_phy)), getNetworkCustomPHYOptions(), ""); + printInputField(response, "NWCUSTADDR", "ADDR", _preferences->getInt(preference_network_custom_addr, 1), 6, ""); + #if defined(CONFIG_IDF_TARGET_ESP32) + printDropDown(response, "NWCUSTCLK", "CLK", String(_preferences->getInt(preference_network_custom_clk, 0)), getNetworkCustomCLKOptions(), "internalopt"); + printInputField(response, "NWCUSTPWR", "PWR", _preferences->getInt(preference_network_custom_pwr, 12), 6, "internalopt"); + printInputField(response, "NWCUSTMDIO", "MDIO", _preferences->getInt(preference_network_custom_mdio), 6, "internalopt"); + printInputField(response, "NWCUSTMDC", "MDC", _preferences->getInt(preference_network_custom_mdc), 6, "internalopt"); + #endif + printInputField(response, "NWCUSTIRQ", "IRQ", _preferences->getInt(preference_network_custom_irq, -1), 6, "externalopt"); + printInputField(response, "NWCUSTRST", "RST", _preferences->getInt(preference_network_custom_rst, -1), 6, "externalopt"); + printInputField(response, "NWCUSTCS", "CS", _preferences->getInt(preference_network_custom_cs, -1), 6, "externalopt"); + printInputField(response, "NWCUSTSCK", "SCK", _preferences->getInt(preference_network_custom_sck, -1), 6, "externalopt"); + printInputField(response, "NWCUSTMISO", "MISO", _preferences->getInt(preference_network_custom_miso, -1), 6, "externalopt"); + printInputField(response, "NWCUSTMOSI", "MOSI", _preferences->getInt(preference_network_custom_mosi, -1), 6, "externalopt"); + + response->print("
"); + + response->print("
"); + response->print("
"); + response->print(""); + request->send(response); +} + +void WebCfgServer::buildHtml(AsyncWebServerRequest *request) { String header = ""; - buildHtmlHeader(header); + AsyncResponseStream *response = request->beginResponseStream("text/html"); + buildHtmlHeader(response, header); - _response.concat("

Info

\n"); - _response.concat(""); + if(_rebootRequired) response->print("
REBOOT REQUIRED TO APPLY SETTINGS
"); - printParameter("Hostname", _hostname.c_str(), "", "hostname"); - printParameter("MQTT Connected", _network->mqttConnectionState() > 0 ? "Yes" : "No", "", "mqttState"); + response->print("

Info


"); + response->print(""); + + printParameter(response, "Hostname", _hostname.c_str(), "", "hostname"); + printParameter(response, "MQTT Connected", _network->mqttConnectionState() > 0 ? "Yes" : "No", "", "mqttState"); if(_nuki != nullptr) { char lockStateArr[20]; NukiLock::lockstateToString(_nuki->keyTurnerState().lockState, lockStateArr); - printParameter("Nuki Lock paired", _nuki->isPaired() ? ("Yes (BLE Address " + _nuki->getBleAddress().toString() + ")").c_str() : "No", "", "lockPaired"); - printParameter("Nuki Lock state", lockStateArr, "", "lockState"); + printParameter(response, "Nuki Lock paired", _nuki->isPaired() ? ("Yes (BLE Address " + _nuki->getBleAddress().toString() + ")").c_str() : "No", "", "lockPaired"); + printParameter(response, "Nuki Lock state", lockStateArr, "", "lockState"); if(_nuki->isPaired()) { String lockState = pinStateToString(_preferences->getInt(preference_lock_pin_status, 4)); - printParameter("Nuki Lock PIN status", lockState.c_str(), "", "lockPin"); + printParameter(response, "Nuki Lock PIN status", lockState.c_str(), "", "lockPin"); if(_preferences->getBool(preference_official_hybrid, false)) { String offConnected = _nuki->offConnected() ? "Yes": "No"; - printParameter("Nuki Lock hybrid mode connected", offConnected.c_str(), "", "lockHybrid"); + printParameter(response, "Nuki Lock hybrid mode connected", offConnected.c_str(), "", "lockHybrid"); } } } @@ -2074,222 +2533,223 @@ void WebCfgServer::buildHtml() { char openerStateArr[20]; NukiOpener::lockstateToString(_nukiOpener->keyTurnerState().lockState, openerStateArr); - printParameter("Nuki Opener paired", _nukiOpener->isPaired() ? ("Yes (BLE Address " + _nukiOpener->getBleAddress().toString() + ")").c_str() : "No", "", "openerPaired"); + printParameter(response, "Nuki Opener paired", _nukiOpener->isPaired() ? ("Yes (BLE Address " + _nukiOpener->getBleAddress().toString() + ")").c_str() : "No", "", "openerPaired"); - if(_nukiOpener->keyTurnerState().nukiState == NukiOpener::State::ContinuousMode) printParameter("Nuki Opener state", "Open (Continuous Mode)", "", "openerState"); - else printParameter("Nuki Opener state", openerStateArr, "", "openerState"); + if(_nukiOpener->keyTurnerState().nukiState == NukiOpener::State::ContinuousMode) printParameter(response, "Nuki Opener state", "Open (Continuous Mode)", "", "openerState"); + else printParameter(response, "Nuki Opener state", openerStateArr, "", "openerState"); if(_nukiOpener->isPaired()) { String openerState = pinStateToString(_preferences->getInt(preference_opener_pin_status, 4)); - printParameter("Nuki Opener PIN status", openerState.c_str(), "", "openerPin"); + printParameter(response, "Nuki Opener PIN status", openerState.c_str(), "", "openerPin"); } } - printParameter("Firmware", NUKI_HUB_VERSION, "/info", "firmware"); - if(_preferences->getBool(preference_check_updates)) printParameter("Latest Firmware", _preferences->getString(preference_latest_version).c_str(), "/ota", "ota"); - _response.concat("

"); - _response.concat("
    "); - buildNavigationMenuEntry("MQTT and Network Configuration", "/mqttconfig", _brokerConfigured ? "" : "Please configure MQTT broker"); - buildNavigationMenuEntry("Nuki Configuration", "/nukicfg"); - buildNavigationMenuEntry("Access Level Configuration", "/acclvl"); - buildNavigationMenuEntry("Credentials", "/cred", _pinsConfigured ? "" : "Please configure PIN"); - buildNavigationMenuEntry("GPIO Configuration", "/gpiocfg"); - buildNavigationMenuEntry("Firmware update", "/ota"); - buildNavigationMenuEntry("Import/Export Configuration", "/impexpcfg"); - if(_preferences->getBool(preference_publish_debug_info, false)) - { - buildNavigationMenuEntry("Advanced Configuration", "/advanced"); - } - if(_preferences->getBool(preference_webserial_enabled, false)) - { - buildNavigationMenuEntry("Open Webserial", "/webserial"); - } - if(_allowRestartToPortal) - { - buildNavigationMenuEntry("Configure Wi-Fi", "/wifi"); - } - buildNavigationMenuEntry("Reboot Nuki Hub", "/reboot"); - _response.concat("
"); + printParameter(response, "Firmware", NUKI_HUB_VERSION, "/info", "firmware"); + if(_preferences->getBool(preference_check_updates)) printParameter(response, "Latest Firmware", _preferences->getString(preference_latest_version).c_str(), "/ota", "ota"); + response->print("
"); + response->print("
    "); + buildNavigationMenuEntry(response, "MQTT and Network Configuration", "/mqttconfig", _brokerConfigured ? "" : "Please configure MQTT broker"); + buildNavigationMenuEntry(response, "Nuki Configuration", "/nukicfg"); + buildNavigationMenuEntry(response, "Access Level Configuration", "/acclvl"); + buildNavigationMenuEntry(response, "Credentials", "/cred", _pinsConfigured ? "" : "Please configure PIN"); + buildNavigationMenuEntry(response, "GPIO Configuration", "/gpiocfg"); + buildNavigationMenuEntry(response, "Firmware update", "/ota"); + buildNavigationMenuEntry(response, "Import/Export Configuration", "/impexpcfg"); + if(_preferences->getInt(preference_network_hardware, 0) == 11) buildNavigationMenuEntry(response, "Custom Ethernet Configuration", "/custntw"); + if(_preferences->getBool(preference_publish_debug_info, false)) buildNavigationMenuEntry(response, "Advanced Configuration", "/advanced"); + if(_preferences->getBool(preference_webserial_enabled, false)) buildNavigationMenuEntry(response, "Open Webserial", "/webserial"); + #ifndef CONFIG_IDF_TARGET_ESP32H2 + if(_allowRestartToPortal) buildNavigationMenuEntry(response, "Configure Wi-Fi", "/wifi"); + #endif + buildNavigationMenuEntry(response, "Reboot Nuki Hub", "/reboot"); + response->print("
"); + request->send(response); } - -void WebCfgServer::buildCredHtml() +void WebCfgServer::buildCredHtml(AsyncWebServerRequest *request) { - buildHtmlHeader(); - - _response.concat("
"); - _response.concat("

Credentials

"); - _response.concat(""); - printInputField("CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, "", false, true); - printInputField("CREDPASS", "Password", "*", 30, "", true, true); - printInputField("CREDPASSRE", "Retype password", "*", 30, "", true); - _response.concat("
"); - _response.concat("
"); - _response.concat("
"); - + AsyncResponseStream *response = request->beginResponseStream("text/html"); + buildHtmlHeader(response); + response->print("
"); + response->print("

Credentials

"); + response->print(""); + printInputField(response, "CREDUSER", "User (# to clear)", _preferences->getString(preference_cred_user).c_str(), 30, "", false, true); + printInputField(response, "CREDPASS", "Password", "*", 30, "", true, true); + printInputField(response, "CREDPASSRE", "Retype password", "*", 30, "", true); + response->print("
"); + response->print("
"); + response->print("
"); if(_nuki != nullptr) { - _response.concat("

"); - _response.concat("

Nuki Lock PIN

"); - _response.concat(""); - printInputField("NUKIPIN", "PIN Code (# to clear)", "*", 20, "", true); - _response.concat("
"); - _response.concat("
"); - _response.concat("
"); + response->print("

"); + response->print("

Nuki Lock PIN

"); + response->print(""); + printInputField(response, "NUKIPIN", "PIN Code (# to clear)", "*", 20, "", true); + response->print("
"); + response->print("
"); + response->print("
"); } - if(_nukiOpener != nullptr) { - _response.concat("

"); - _response.concat("

Nuki Opener PIN

"); - _response.concat(""); - printInputField("NUKIOPPIN", "PIN Code (# to clear)", "*", 20, "", true); - _response.concat("
"); - _response.concat("
"); - _response.concat("
"); + response->print("

"); + response->print("

Nuki Opener PIN

"); + response->print(""); + printInputField(response, "NUKIOPPIN", "PIN Code (# to clear)", "*", 20, "", true); + response->print("
"); + response->print("
"); + response->print("
"); } - - _confirmCode = generateConfirmCode(); if(_nuki != nullptr) { - _response.concat("

Unpair Nuki Lock

"); - _response.concat("
"); - _response.concat(""); + response->print("

Unpair Nuki Lock

"); + response->print(""); + response->print("
"); String message = "Type "; message.concat(_confirmCode); message.concat(" to confirm unpair"); - printInputField("CONFIRMTOKEN", message.c_str(), "", 10, ""); - _response.concat("
"); - _response.concat("
"); + printInputField(response, "CONFIRMTOKEN", message.c_str(), "", 10, ""); + response->print(""); + response->print("
"); } - if(_nukiOpener != nullptr) { - _response.concat("

Unpair Nuki Opener

"); - _response.concat("
"); - _response.concat(""); + response->print("

Unpair Nuki Opener

"); + response->print(""); + response->print("
"); String message = "Type "; message.concat(_confirmCode); message.concat(" to confirm unpair"); - printInputField("CONFIRMTOKEN", message.c_str(), "", 10, ""); - _response.concat("
"); - _response.concat("
"); + printInputField(response, "CONFIRMTOKEN", message.c_str(), "", 10, ""); + response->print(""); + response->print("
"); } - - _response.concat("

Factory reset Nuki Hub

"); - _response.concat("

This will reset all settings to default and unpair Nuki Lock and/or Opener. Optionally will also reset WiFi settings and reopen WiFi manager portal.

"); - _response.concat("
"); - _response.concat(""); + response->print("

Factory reset Nuki Hub

"); + response->print("

This will reset all settings to default and unpair Nuki Lock and/or Opener."); + #ifndef CONFIG_IDF_TARGET_ESP32H2 + response->print("Optionally will also reset WiFi settings and reopen WiFi manager portal."); + #endif + response->print("

"); + response->print(""); + response->print("
"); String message = "Type "; message.concat(_confirmCode); message.concat(" to confirm factory reset"); - printInputField("CONFIRMTOKEN", message.c_str(), "", 10, ""); - printCheckBox("WIFI", "Also reset WiFi settings", false, ""); - _response.concat("
"); - _response.concat("
"); - _response.concat(""); + printInputField(response, "CONFIRMTOKEN", message.c_str(), "", 10, ""); + #ifndef CONFIG_IDF_TARGET_ESP32H2 + printCheckBox(response, "WIFI", "Also reset WiFi settings", false, ""); + #endif + response->print(""); + response->print("
"); + response->print(""); + request->send(response); } -void WebCfgServer::buildMqttConfigHtml() +void WebCfgServer::buildMqttConfigHtml(AsyncWebServerRequest *request) { - buildHtmlHeader(); - _response.concat("
"); - _response.concat("

Basic MQTT and Network Configuration

"); - _response.concat(""); - printInputField("HOSTNAME", "Host name", _preferences->getString(preference_hostname).c_str(), 100, ""); - printInputField("MQTTSERVER", "MQTT Broker", _preferences->getString(preference_mqtt_broker).c_str(), 100, ""); - printInputField("MQTTPORT", "MQTT Broker port", _preferences->getInt(preference_mqtt_broker_port), 5, ""); - printInputField("MQTTUSER", "MQTT User (# to clear)", _preferences->getString(preference_mqtt_user).c_str(), 30, "", false, true); - printInputField("MQTTPASS", "MQTT Password", "*", 30, "", true, true); - _response.concat("

"); + AsyncResponseStream *response = request->beginResponseStream("text/html"); + buildHtmlHeader(response); + response->print(""); + response->print("

Basic MQTT and Network Configuration

"); + response->print(""); + printInputField(response, "HOSTNAME", "Host name", _preferences->getString(preference_hostname).c_str(), 100, ""); + printInputField(response, "MQTTSERVER", "MQTT Broker", _preferences->getString(preference_mqtt_broker).c_str(), 100, ""); + printInputField(response, "MQTTPORT", "MQTT Broker port", _preferences->getInt(preference_mqtt_broker_port), 5, ""); + printInputField(response, "MQTTUSER", "MQTT User (# to clear)", _preferences->getString(preference_mqtt_user).c_str(), 30, "", false, true); + printInputField(response, "MQTTPASS", "MQTT Password", "*", 30, "", true, true); + response->print("

"); - _response.concat("

Advanced MQTT and Network Configuration

"); - _response.concat(""); - printInputField("HASSDISCOVERY", "Home Assistant discovery topic (empty to disable; usually homeassistant)", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30, ""); - printInputField("HASSCUURL", "Home Assistant device configuration URL (empty to use http://LOCALIP; fill when using a reverse proxy for example)", _preferences->getString(preference_mqtt_hass_cu_url).c_str(), 261, ""); - if(_nukiOpener != nullptr) printCheckBox("OPENERCONT", "Set Nuki Opener Lock/Unlock action in Home Assistant to Continuous mode", _preferences->getBool(preference_opener_continuous_mode), ""); - printTextarea("MQTTCA", "MQTT SSL CA Certificate (*, optional)", _preferences->getString(preference_mqtt_ca).c_str(), TLS_CA_MAX_SIZE, _network->encryptionSupported(), true); - printTextarea("MQTTCRT", "MQTT SSL Client Certificate (*, optional)", _preferences->getString(preference_mqtt_crt).c_str(), TLS_CERT_MAX_SIZE, _network->encryptionSupported(), true); - printTextarea("MQTTKEY", "MQTT SSL Client Key (*, optional)", _preferences->getString(preference_mqtt_key).c_str(), TLS_KEY_MAX_SIZE, _network->encryptionSupported(), true); - printDropDown("NWHW", "Network hardware", String(_preferences->getInt(preference_network_hardware)), getNetworkDetectionOptions()); - printCheckBox("NWHWWIFIFB", "Disable fallback to Wi-Fi / Wi-Fi config portal", _preferences->getBool(preference_network_wifi_fallback_disabled), ""); - printCheckBox("BESTRSSI", "Connect to AP with the best signal in an environment with multiple APs with the same SSID", _preferences->getBool(preference_find_best_rssi), ""); - printInputField("RSSI", "RSSI Publish interval (seconds; -1 to disable)", _preferences->getInt(preference_rssi_publish_interval), 6, ""); - printInputField("NETTIMEOUT", "MQTT Timeout until restart (seconds; -1 to disable)", _preferences->getInt(preference_network_timeout), 5, ""); - printCheckBox("RSTDISC", "Restart on disconnect", _preferences->getBool(preference_restart_on_disconnect), ""); - printCheckBox("RECNWTMQTTDIS", "Reconnect network on MQTT connection failure", _preferences->getBool(preference_recon_netw_on_mqtt_discon), ""); - printCheckBox("MQTTLOG", "Enable MQTT logging", _preferences->getBool(preference_mqtt_log_enabled), ""); - printCheckBox("WEBLOG", "Enable WebSerial logging", _preferences->getBool(preference_webserial_enabled), ""); - printCheckBox("CHECKUPDATE", "Check for Firmware Updates every 24h", _preferences->getBool(preference_check_updates), ""); - printCheckBox("UPDATEMQTT", "Allow updating using MQTT", _preferences->getBool(preference_update_from_mqtt), ""); - printCheckBox("DISNONJSON", "Disable some extraneous non-JSON topics", _preferences->getBool(preference_disable_non_json), ""); - printCheckBox("OFFHYBRID", "Enable hybrid official MQTT and Nuki Hub setup", _preferences->getBool(preference_official_hybrid), ""); - printCheckBox("HYBRIDACT", "Enable sending actions through official MQTT", _preferences->getBool(preference_official_hybrid_actions), ""); - printInputField("HYBRIDTIMER", "Time between status updates when official MQTT is offline (seconds)", _preferences->getInt(preference_query_interval_hybrid_lockstate), 5, ""); - printCheckBox("HYBRIDRETRY", "Retry command sent using official MQTT over BLE if failed", _preferences->getBool(preference_official_hybrid_retry), ""); - _response.concat("
"); - _response.concat("* If no encryption is configured for the MQTT broker, leave empty. Only supported for Wi-Fi connections.

"); + response->print("

Advanced MQTT and Network Configuration

"); + response->print(""); + printInputField(response, "HASSDISCOVERY", "Home Assistant discovery topic (empty to disable; usually homeassistant)", _preferences->getString(preference_mqtt_hass_discovery).c_str(), 30, ""); + printInputField(response, "HASSCUURL", "Home Assistant device configuration URL (empty to use http://LOCALIP; fill when using a reverse proxy for example)", _preferences->getString(preference_mqtt_hass_cu_url).c_str(), 261, ""); + if(_preferences->getBool(preference_opener_enabled, false)) printCheckBox(response, "OPENERCONT", "Set Nuki Opener Lock/Unlock action in Home Assistant to Continuous mode", _preferences->getBool(preference_opener_continuous_mode), ""); + printTextarea(response, "MQTTCA", "MQTT SSL CA Certificate (*, optional)", _preferences->getString(preference_mqtt_ca).c_str(), TLS_CA_MAX_SIZE, _network->encryptionSupported(), true); + printTextarea(response, "MQTTCRT", "MQTT SSL Client Certificate (*, optional)", _preferences->getString(preference_mqtt_crt).c_str(), TLS_CERT_MAX_SIZE, _network->encryptionSupported(), true); + printTextarea(response, "MQTTKEY", "MQTT SSL Client Key (*, optional)", _preferences->getString(preference_mqtt_key).c_str(), TLS_KEY_MAX_SIZE, _network->encryptionSupported(), true); + printDropDown(response, "NWHW", "Network hardware", String(_preferences->getInt(preference_network_hardware)), getNetworkDetectionOptions(), ""); + #ifndef CONFIG_IDF_TARGET_ESP32H2 + printCheckBox(response, "NWHWWIFIFB", "Disable fallback to Wi-Fi / Wi-Fi config portal", _preferences->getBool(preference_network_wifi_fallback_disabled), ""); + printCheckBox(response, "BESTRSSI", "Connect to AP with the best signal in an environment with multiple APs with the same SSID", _preferences->getBool(preference_find_best_rssi), ""); + printInputField(response, "RSSI", "RSSI Publish interval (seconds; -1 to disable)", _preferences->getInt(preference_rssi_publish_interval), 6, ""); + #endif + printInputField(response, "NETTIMEOUT", "MQTT Timeout until restart (seconds; -1 to disable)", _preferences->getInt(preference_network_timeout), 5, ""); + printCheckBox(response, "RSTDISC", "Restart on disconnect", _preferences->getBool(preference_restart_on_disconnect), ""); + printCheckBox(response, "RECNWTMQTTDIS", "Reconnect network on MQTT connection failure", _preferences->getBool(preference_recon_netw_on_mqtt_discon), ""); + printCheckBox(response, "MQTTLOG", "Enable MQTT logging", _preferences->getBool(preference_mqtt_log_enabled), ""); + printCheckBox(response, "WEBLOG", "Enable WebSerial logging", _preferences->getBool(preference_webserial_enabled), ""); + printCheckBox(response, "CHECKUPDATE", "Check for Firmware Updates every 24h", _preferences->getBool(preference_check_updates), ""); + printCheckBox(response, "UPDATEMQTT", "Allow updating using MQTT", _preferences->getBool(preference_update_from_mqtt), ""); + printCheckBox(response, "DISNONJSON", "Disable some extraneous non-JSON topics", _preferences->getBool(preference_disable_non_json), ""); + printCheckBox(response, "OFFHYBRID", "Enable hybrid official MQTT and Nuki Hub setup", _preferences->getBool(preference_official_hybrid), ""); + printCheckBox(response, "HYBRIDACT", "Enable sending actions through official MQTT", _preferences->getBool(preference_official_hybrid_actions), ""); + printInputField(response, "HYBRIDTIMER", "Time between status updates when official MQTT is offline (seconds)", _preferences->getInt(preference_query_interval_hybrid_lockstate), 5, ""); + printCheckBox(response, "HYBRIDRETRY", "Retry command sent using official MQTT over BLE if failed", _preferences->getBool(preference_official_hybrid_retry), ""); + response->print("
"); + response->print("* If no encryption is configured for the MQTT broker, leave empty.

"); - _response.concat("

IP Address assignment

"); - _response.concat(""); - printCheckBox("DHCPENA", "Enable DHCP", _preferences->getBool(preference_ip_dhcp_enabled), ""); - printInputField("IPADDR", "Static IP address", _preferences->getString(preference_ip_address).c_str(), 15, ""); - printInputField("IPSUB", "Subnet", _preferences->getString(preference_ip_subnet).c_str(), 15, ""); - printInputField("IPGTW", "Default gateway", _preferences->getString(preference_ip_gateway).c_str(), 15, ""); - printInputField("DNSSRV", "DNS Server", _preferences->getString(preference_ip_dns_server).c_str(), 15, ""); - _response.concat("
"); + response->print("

IP Address assignment

"); + response->print(""); + printCheckBox(response, "DHCPENA", "Enable DHCP", _preferences->getBool(preference_ip_dhcp_enabled), ""); + printInputField(response, "IPADDR", "Static IP address", _preferences->getString(preference_ip_address).c_str(), 15, ""); + printInputField(response, "IPSUB", "Subnet", _preferences->getString(preference_ip_subnet).c_str(), 15, ""); + printInputField(response, "IPGTW", "Default gateway", _preferences->getString(preference_ip_gateway).c_str(), 15, ""); + printInputField(response, "DNSSRV", "DNS Server", _preferences->getString(preference_ip_dns_server).c_str(), 15, ""); + response->print("
"); - _response.concat("
"); - _response.concat("
"); - _response.concat(""); + response->print("
"); + response->print(""); + response->print(""); + request->send(response); } -void WebCfgServer::buildAdvancedConfigHtml() +void WebCfgServer::buildAdvancedConfigHtml(AsyncWebServerRequest *request) { - buildHtmlHeader(); - _response.concat("
"); - _response.concat("

Advanced Configuration

"); - _response.concat("

Warning: Changing these settings can lead to bootloops that might require you to erase the ESP32 and reflash nukihub using USB/serial

"); - _response.concat(""); - _response.concat(""); - printCheckBox("BTLPRST", "Enable Bootloop prevention (Try to reset these settings to default on bootloop)", true, ""); - printInputField("BUFFSIZE", "Char buffer size (min 4096, max 32768)", _preferences->getInt(preference_buffer_size, CHAR_BUFFER_SIZE), 6, ""); - _response.concat(""); - printInputField("TSKNTWK", "Task size Network (min 12288, max 32768)", _preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE), 6, ""); - _response.concat(""); - printInputField("TSKNUKI", "Task size Nuki (min 8192, max 32768)", _preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), 6, ""); - printInputField("ALMAX", "Max auth log entries (min 1, max 50)", _preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG), 3, "inputmaxauthlog"); - printInputField("KPMAX", "Max keypad entries (min 1, max 100)", _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD), 3, "inputmaxkeypad"); - printInputField("TCMAX", "Max timecontrol entries (min 1, max 50)", _preferences->getInt(preference_timecontrol_max_entries, MAX_TIMECONTROL), 3, "inputmaxtimecontrol"); - printCheckBox("SHOWSECRETS", "Show Pairing secrets on Info page (for 120s after next boot)", _preferences->getBool(preference_show_secrets), ""); - - if(_nuki != nullptr) + AsyncResponseStream *response = request->beginResponseStream("text/html"); + buildHtmlHeader(response); + response->print(""); + response->print("

Advanced Configuration

"); + response->print("

Warning: Changing these settings can lead to bootloops that might require you to erase the ESP32 and reflash nukihub using USB/serial

"); + response->print("
Current bootloop prevention state"); - _response.concat(_preferences->getBool(preference_enable_bootloop_reset, false) ? "Enabled" : "Disabled"); - _response.concat("
Advised minimum char buffer size based on current settings
Advised minimum network task size based on current settings
"); + response->print(""); + printCheckBox(response, "BTLPRST", "Enable Bootloop prevention (Try to reset these settings to default on bootloop)", true, ""); + printInputField(response, "BUFFSIZE", "Char buffer size (min 4096, max 32768)", _preferences->getInt(preference_buffer_size, CHAR_BUFFER_SIZE), 6, ""); + response->print(""); + printInputField(response, "TSKNTWK", "Task size Network (min 12288, max 32768)", _preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE), 6, ""); + response->print(""); + printInputField(response, "TSKNUKI", "Task size Nuki (min 8192, max 32768)", _preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), 6, ""); + printInputField(response, "ALMAX", "Max auth log entries (min 1, max 50)", _preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG), 3, "inputmaxauthlog"); + printInputField(response, "KPMAX", "Max keypad entries (min 1, max 100)", _preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD), 3, "inputmaxkeypad"); + printInputField(response, "TCMAX", "Max timecontrol entries (min 1, max 50)", _preferences->getInt(preference_timecontrol_max_entries, MAX_TIMECONTROL), 3, "inputmaxtimecontrol"); + printCheckBox(response, "SHOWSECRETS", "Show Pairing secrets on Info page (for 120s after next boot)", _preferences->getBool(preference_show_secrets), ""); + if(_preferences->getBool(preference_lock_enabled, true)) { - printCheckBox("LCKMANPAIR", "Manually set lock pairing data (enable to save values below)", false, ""); - printInputField("LCKBLEADDR", "currentBleAddress", "", 12, ""); - printInputField("LCKSECRETK", "secretKeyK", "", 64, ""); - printInputField("LCKAUTHID", "authorizationId", "", 8, ""); + printCheckBox(response, "LCKMANPAIR", "Manually set lock pairing data (enable to save values below)", false, ""); + printInputField(response, "LCKBLEADDR", "currentBleAddress", "", 12, ""); + printInputField(response, "LCKSECRETK", "secretKeyK", "", 64, ""); + printInputField(response, "LCKAUTHID", "authorizationId", "", 8, ""); } - if(_nukiOpener != nullptr) + if(_preferences->getBool(preference_opener_enabled, false)) { - printCheckBox("OPNMANPAIR", "Manually set opener pairing data (enable to save values below)", false, ""); - printInputField("OPNBLEADDR", "currentBleAddress", "", 12, ""); - printInputField("OPNSECRETK", "secretKeyK", "", 64, ""); - printInputField("OPNAUTHID", "authorizationId", "", 8, ""); + printCheckBox(response, "OPNMANPAIR", "Manually set opener pairing data (enable to save values below)", false, ""); + printInputField(response, "OPNBLEADDR", "currentBleAddress", "", 12, ""); + printInputField(response, "OPNSECRETK", "secretKeyK", "", 64, ""); + printInputField(response, "OPNAUTHID", "authorizationId", "", 8, ""); } - printInputField("OTAUPD", "Custom URL to update Nuki Hub updater", "", 255, ""); - printInputField("OTAMAIN", "Custom URL to update Nuki Hub", "", 255, ""); - _response.concat("
Current bootloop prevention state"); + response->print(_preferences->getBool(preference_enable_bootloop_reset, false) ? "Enabled" : "Disabled"); + response->print("
Advised minimum char buffer size based on current settings
Advised minimum network task size based on current settings
"); + printInputField(response, "OTAUPD", "Custom URL to update Nuki Hub updater", "", 255, ""); + printInputField(response, "OTAMAIN", "Custom URL to update Nuki Hub", "", 255, ""); + response->print(""); - _response.concat("
"); - _response.concat("
"); - _response.concat(""); + response->print("
"); + response->print(""); + response->print(""); + request->send(response); } -void WebCfgServer::buildStatusHtml() +void WebCfgServer::buildStatusHtml(AsyncWebServerRequest *request) { + AsyncResponseStream *response = request->beginResponseStream("application/json"); JsonDocument json; char _resbuf[2048]; bool mqttDone = false; @@ -2353,7 +2813,8 @@ void WebCfgServer::buildStatusHtml() if(mqttDone && lockDone && openerDone && latestDone) json["stop"] = 1; serializeJson(json, _resbuf, sizeof(_resbuf)); - _response = _resbuf; + response->print(_resbuf); + request->send(response); } String WebCfgServer::pinStateToString(uint8_t value) { @@ -2370,609 +2831,657 @@ String WebCfgServer::pinStateToString(uint8_t value) { } } -void WebCfgServer::buildAccLvlHtml() +void WebCfgServer::buildAccLvlHtml(AsyncWebServerRequest *request, int aclPart) +{ + String partString = ""; + AsyncResponseStream *response; + + if(aclPart == 0) + { + response = request->beginResponseStream("text/html"); + buildHtmlHeader(response); + } + else response = request->beginResponseStream("text/plain"); + + partAccLvlHtml(partString, aclPart); + response->print(partString); + request->send(response); +} + +void WebCfgServer::partAccLvlHtml(String &partString, int aclPart) { - buildHtmlHeader(); uint32_t aclPrefs[17]; _preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs)); - _response.concat("
"); - _response.concat(""); - _response.concat("

Nuki General Access Control

"); - _response.concat(""); - printCheckBox("CONFPUB", "Publish Nuki configuration information", _preferences->getBool(preference_conf_info_enabled, true), ""); - - if((_nuki != nullptr && _nuki->hasKeypad()) || (_nukiOpener != nullptr && _nukiOpener->hasKeypad())) + switch(aclPart) { - printCheckBox("KPPUB", "Publish keypad entries information", _preferences->getBool(preference_keypad_info_enabled), ""); - printCheckBox("KPPER", "Publish a topic per keypad entry and create HA sensor", _preferences->getBool(preference_keypad_topic_per_entry), ""); - printCheckBox("KPCODE", "Also publish keypad codes (Disadvised for security reasons)", _preferences->getBool(preference_keypad_publish_code, false), ""); - printCheckBox("KPENA", "Add, modify and delete keypad codes", _preferences->getBool(preference_keypad_control_enabled), ""); + case 0: + partString.concat(""); + partString.concat(""); + partString.concat("

Nuki General Access Control

"); + partString.concat("
SettingEnabled
"); + printCheckBox(partString, "CONFPUB", "Publish Nuki configuration information", _preferences->getBool(preference_conf_info_enabled, true), ""); + + if((_nuki != nullptr && _nuki->hasKeypad()) || (_nukiOpener != nullptr && _nukiOpener->hasKeypad())) + { + printCheckBox(partString, "KPPUB", "Publish keypad entries information", _preferences->getBool(preference_keypad_info_enabled), ""); + printCheckBox(partString, "KPPER", "Publish a topic per keypad entry and create HA sensor", _preferences->getBool(preference_keypad_topic_per_entry), ""); + printCheckBox(partString, "KPCODE", "Also publish keypad codes (Disadvised for security reasons)", _preferences->getBool(preference_keypad_publish_code, false), ""); + printCheckBox(partString, "KPENA", "Add, modify and delete keypad codes", _preferences->getBool(preference_keypad_control_enabled), ""); + } + printCheckBox(partString, "TCPUB", "Publish time control entries information", _preferences->getBool(preference_timecontrol_info_enabled), ""); + printCheckBox(partString, "TCPER", "Publish a topic per time control entry and create HA sensor", _preferences->getBool(preference_timecontrol_topic_per_entry), ""); + printCheckBox(partString, "TCENA", "Add, modify and delete time control entries", _preferences->getBool(preference_timecontrol_control_enabled), ""); + printCheckBox(partString, "PUBAUTH", "Publish authorization log", _preferences->getBool(preference_publish_authdata), ""); + partString.concat("
SettingEnabled

"); + partString.concat("
"); + partString.concat("
"); + partString.concat("
"); + partString.concat("
"); + partString.concat(""); + partString.concat(""); + break; + case 1: + uint32_t basicLockConfigAclPrefs[16]; + _preferences->getBytes(preference_conf_lock_basic_acl, &basicLockConfigAclPrefs, sizeof(basicLockConfigAclPrefs)); + uint32_t advancedLockConfigAclPrefs[22]; + _preferences->getBytes(preference_conf_lock_advanced_acl, &advancedLockConfigAclPrefs, sizeof(advancedLockConfigAclPrefs)); + + partString.concat("

Nuki Lock Access Control

"); + partString.concat(""); + partString.concat(""); + partString.concat(""); + + printCheckBox(partString, "ACLLCKLCK", "Lock", ((int)aclPrefs[0] == 1), "chk_access_lock"); + printCheckBox(partString, "ACLLCKUNLCK", "Unlock", ((int)aclPrefs[1] == 1), "chk_access_lock"); + printCheckBox(partString, "ACLLCKUNLTCH", "Unlatch", ((int)aclPrefs[2] == 1), "chk_access_lock"); + printCheckBox(partString, "ACLLCKLNG", "Lock N Go", ((int)aclPrefs[3] == 1), "chk_access_lock"); + printCheckBox(partString, "ACLLCKLNGU", "Lock N Go Unlatch", ((int)aclPrefs[4] == 1), "chk_access_lock"); + printCheckBox(partString, "ACLLCKFLLCK", "Full Lock", ((int)aclPrefs[5] == 1), "chk_access_lock"); + printCheckBox(partString, "ACLLCKFOB1", "Fob Action 1", ((int)aclPrefs[6] == 1), "chk_access_lock"); + printCheckBox(partString, "ACLLCKFOB2", "Fob Action 2", ((int)aclPrefs[7] == 1), "chk_access_lock"); + printCheckBox(partString, "ACLLCKFOB3", "Fob Action 3", ((int)aclPrefs[8] == 1), "chk_access_lock"); + partString.concat("
ActionAllowed

"); + + partString.concat("

Nuki Lock Config Control (Requires PIN to be set)

"); + partString.concat(""); + partString.concat(""); + partString.concat(""); + + printCheckBox(partString, "CONFLCKNAME", "Name", ((int)basicLockConfigAclPrefs[0] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKLAT", "Latitude", ((int)basicLockConfigAclPrefs[1] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKLONG", "Longitude", ((int)basicLockConfigAclPrefs[2] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKAUNL", "Auto unlatch", ((int)basicLockConfigAclPrefs[3] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKPRENA", "Pairing enabled", ((int)basicLockConfigAclPrefs[4] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKBTENA", "Button enabled", ((int)basicLockConfigAclPrefs[5] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKLEDENA", "LED flash enabled", ((int)basicLockConfigAclPrefs[6] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKLEDBR", "LED brightness", ((int)basicLockConfigAclPrefs[7] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKTZOFF", "Timezone offset", ((int)basicLockConfigAclPrefs[8] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKDSTM", "DST mode", ((int)basicLockConfigAclPrefs[9] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKFOB1", "Fob Action 1", ((int)basicLockConfigAclPrefs[10] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKFOB2", "Fob Action 2", ((int)basicLockConfigAclPrefs[11] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKFOB3", "Fob Action 3", ((int)basicLockConfigAclPrefs[12] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKSGLLCK", "Single Lock", ((int)basicLockConfigAclPrefs[13] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKADVM", "Advertising Mode", ((int)basicLockConfigAclPrefs[14] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKTZID", "Timezone ID", ((int)basicLockConfigAclPrefs[15] == 1), "chk_config_lock"); + + printCheckBox(partString, "CONFLCKUPOD", "Unlocked Position Offset Degrees", ((int)advancedLockConfigAclPrefs[0] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKLPOD", "Locked Position Offset Degrees", ((int)advancedLockConfigAclPrefs[1] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKSLPOD", "Single Locked Position Offset Degrees", ((int)advancedLockConfigAclPrefs[2] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKUTLTOD", "Unlocked To Locked Transition Offset Degrees", ((int)advancedLockConfigAclPrefs[3] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKLNGT", "Lock n Go timeout", ((int)advancedLockConfigAclPrefs[4] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKSBPA", "Single button press action", ((int)advancedLockConfigAclPrefs[5] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKDBPA", "Double button press action", ((int)advancedLockConfigAclPrefs[6] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKDC", "Detached cylinder", ((int)advancedLockConfigAclPrefs[7] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKBATT", "Battery type", ((int)advancedLockConfigAclPrefs[8] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKABTD", "Automatic battery type detection", ((int)advancedLockConfigAclPrefs[9] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKUNLD", "Unlatch duration", ((int)advancedLockConfigAclPrefs[10] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKALT", "Auto lock timeout", ((int)advancedLockConfigAclPrefs[11] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKAUNLD", "Auto unlock disabled", ((int)advancedLockConfigAclPrefs[12] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKNMENA", "Nightmode enabled", ((int)advancedLockConfigAclPrefs[13] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKNMST", "Nightmode start time", ((int)advancedLockConfigAclPrefs[14] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKNMET", "Nightmode end time", ((int)advancedLockConfigAclPrefs[15] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKNMALENA", "Nightmode auto lock enabled", ((int)advancedLockConfigAclPrefs[16] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKNMAULD", "Nightmode auto unlock disabled", ((int)advancedLockConfigAclPrefs[17] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKNMLOS", "Nightmode immediate lock on start", ((int)advancedLockConfigAclPrefs[18] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKALENA", "Auto lock enabled", ((int)advancedLockConfigAclPrefs[19] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKIALENA", "Immediate auto lock enabled", ((int)advancedLockConfigAclPrefs[20] == 1), "chk_config_lock"); + printCheckBox(partString, "CONFLCKAUENA", "Auto update enabled", ((int)advancedLockConfigAclPrefs[21] == 1), "chk_config_lock"); + partString.concat("
ChangeAllowed

"); + break; + case 2: + uint32_t basicOpenerConfigAclPrefs[14]; + _preferences->getBytes(preference_conf_opener_basic_acl, &basicOpenerConfigAclPrefs, sizeof(basicOpenerConfigAclPrefs)); + uint32_t advancedOpenerConfigAclPrefs[20]; + _preferences->getBytes(preference_conf_opener_advanced_acl, &advancedOpenerConfigAclPrefs, sizeof(advancedOpenerConfigAclPrefs)); + + partString.concat("

Nuki Opener Access Control

"); + partString.concat(""); + partString.concat(""); + partString.concat(""); + + printCheckBox(partString, "ACLOPNUNLCK", "Activate Ring-to-Open", ((int)aclPrefs[9] == 1), "chk_access_opener"); + printCheckBox(partString, "ACLOPNLCK", "Deactivate Ring-to-Open", ((int)aclPrefs[10] == 1), "chk_access_opener"); + printCheckBox(partString, "ACLOPNUNLTCH", "Electric Strike Actuation", ((int)aclPrefs[11] == 1), "chk_access_opener"); + printCheckBox(partString, "ACLOPNUNLCKCM", "Activate Continuous Mode", ((int)aclPrefs[12] == 1), "chk_access_opener"); + printCheckBox(partString, "ACLOPNLCKCM", "Deactivate Continuous Mode", ((int)aclPrefs[13] == 1), "chk_access_opener"); + printCheckBox(partString, "ACLOPNFOB1", "Fob Action 1", ((int)aclPrefs[14] == 1), "chk_access_opener"); + printCheckBox(partString, "ACLOPNFOB2", "Fob Action 2", ((int)aclPrefs[15] == 1), "chk_access_opener"); + printCheckBox(partString, "ACLOPNFOB3", "Fob Action 3", ((int)aclPrefs[16] == 1), "chk_access_opener"); + partString.concat("
ActionAllowed

"); + + partString.concat("

Nuki Opener Config Control (Requires PIN to be set)

"); + partString.concat(""); + partString.concat(""); + partString.concat(""); + + printCheckBox(partString, "CONFOPNNAME", "Name", ((int)basicOpenerConfigAclPrefs[0] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNLAT", "Latitude", ((int)basicOpenerConfigAclPrefs[1] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNLONG", "Longitude", ((int)basicOpenerConfigAclPrefs[2] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNPRENA", "Pairing enabled", ((int)basicOpenerConfigAclPrefs[3] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNBTENA", "Button enabled", ((int)basicOpenerConfigAclPrefs[4] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNLEDENA", "LED flash enabled", ((int)basicOpenerConfigAclPrefs[5] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNTZOFF", "Timezone offset", ((int)basicOpenerConfigAclPrefs[6] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNDSTM", "DST mode", ((int)basicOpenerConfigAclPrefs[7] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNFOB1", "Fob Action 1", ((int)basicOpenerConfigAclPrefs[8] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNFOB2", "Fob Action 2", ((int)basicOpenerConfigAclPrefs[9] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNFOB3", "Fob Action 3", ((int)basicOpenerConfigAclPrefs[10] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNOPM", "Operating Mode", ((int)basicOpenerConfigAclPrefs[11] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNADVM", "Advertising Mode", ((int)basicOpenerConfigAclPrefs[12] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNTZID", "Timezone ID", ((int)basicOpenerConfigAclPrefs[13] == 1), "chk_config_opener"); + + printCheckBox(partString, "CONFOPNICID", "Intercom ID", ((int)advancedOpenerConfigAclPrefs[0] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNBUSMS", "BUS mode Switch", ((int)advancedOpenerConfigAclPrefs[1] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNSCDUR", "Short Circuit Duration", ((int)advancedOpenerConfigAclPrefs[2] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNESD", "Eletric Strike Delay", ((int)advancedOpenerConfigAclPrefs[3] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNRESD", "Random Electric Strike Delay", ((int)advancedOpenerConfigAclPrefs[4] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNESDUR", "Electric Strike Duration", ((int)advancedOpenerConfigAclPrefs[5] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNDRTOAR", "Disable RTO after ring", ((int)advancedOpenerConfigAclPrefs[6] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNRTOT", "RTO timeout", ((int)advancedOpenerConfigAclPrefs[7] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNDRBSUP", "Doorbell suppression", ((int)advancedOpenerConfigAclPrefs[8] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNDRBSUPDUR", "Doorbell suppression duration", ((int)advancedOpenerConfigAclPrefs[9] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNSRING", "Sound Ring", ((int)advancedOpenerConfigAclPrefs[10] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNSOPN", "Sound Open", ((int)advancedOpenerConfigAclPrefs[11] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNSRTO", "Sound RTO", ((int)advancedOpenerConfigAclPrefs[12] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNSCM", "Sound CM", ((int)advancedOpenerConfigAclPrefs[13] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNSCFRM", "Sound confirmation", ((int)advancedOpenerConfigAclPrefs[14] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNSLVL", "Sound level", ((int)advancedOpenerConfigAclPrefs[15] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNSBPA", "Single button press action", ((int)advancedOpenerConfigAclPrefs[16] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNDBPA", "Double button press action", ((int)advancedOpenerConfigAclPrefs[17] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNBATT", "Battery type", ((int)advancedOpenerConfigAclPrefs[18] == 1), "chk_config_opener"); + printCheckBox(partString, "CONFOPNABTD", "Automatic battery type detection", ((int)advancedOpenerConfigAclPrefs[19] == 1), "chk_config_opener"); + partString.concat("
ChangeAllowed

"); + break; + default: + break; } - printCheckBox("TCPUB", "Publish time control entries information", _preferences->getBool(preference_timecontrol_info_enabled), ""); - printCheckBox("TCPER", "Publish a topic per time control entry and create HA sensor", _preferences->getBool(preference_timecontrol_topic_per_entry), ""); - printCheckBox("TCENA", "Add, modify and delete time control entries", _preferences->getBool(preference_timecontrol_control_enabled), ""); - printCheckBox("PUBAUTH", "Publish authorization log (may reduce battery life)", _preferences->getBool(preference_publish_authdata), ""); - _response.concat("
"); - if(_nuki != nullptr) - { - uint32_t basicLockConfigAclPrefs[16]; - _preferences->getBytes(preference_conf_lock_basic_acl, &basicLockConfigAclPrefs, sizeof(basicLockConfigAclPrefs)); - uint32_t advancedLockConfigAclPrefs[22]; - _preferences->getBytes(preference_conf_lock_advanced_acl, &advancedLockConfigAclPrefs, sizeof(advancedLockConfigAclPrefs)); - - _response.concat("

Nuki Lock Access Control

"); - _response.concat(""); - _response.concat(""); - _response.concat(""); - - printCheckBox("ACLLCKLCK", "Lock", ((int)aclPrefs[0] == 1), "chk_access_lock"); - printCheckBox("ACLLCKUNLCK", "Unlock", ((int)aclPrefs[1] == 1), "chk_access_lock"); - printCheckBox("ACLLCKUNLTCH", "Unlatch", ((int)aclPrefs[2] == 1), "chk_access_lock"); - printCheckBox("ACLLCKLNG", "Lock N Go", ((int)aclPrefs[3] == 1), "chk_access_lock"); - printCheckBox("ACLLCKLNGU", "Lock N Go Unlatch", ((int)aclPrefs[4] == 1), "chk_access_lock"); - printCheckBox("ACLLCKFLLCK", "Full Lock", ((int)aclPrefs[5] == 1), "chk_access_lock"); - printCheckBox("ACLLCKFOB1", "Fob Action 1", ((int)aclPrefs[6] == 1), "chk_access_lock"); - printCheckBox("ACLLCKFOB2", "Fob Action 2", ((int)aclPrefs[7] == 1), "chk_access_lock"); - printCheckBox("ACLLCKFOB3", "Fob Action 3", ((int)aclPrefs[8] == 1), "chk_access_lock"); - _response.concat("
ActionAllowed

"); - - _response.concat("

Nuki Lock Config Control (Requires PIN to be set)

"); - _response.concat(""); - _response.concat(""); - _response.concat(""); - - printCheckBox("CONFLCKNAME", "Name", ((int)basicLockConfigAclPrefs[0] == 1), "chk_config_lock"); - printCheckBox("CONFLCKLAT", "Latitude", ((int)basicLockConfigAclPrefs[1] == 1), "chk_config_lock"); - printCheckBox("CONFLCKLONG", "Longitude", ((int)basicLockConfigAclPrefs[2] == 1), "chk_config_lock"); - printCheckBox("CONFLCKAUNL", "Auto unlatch", ((int)basicLockConfigAclPrefs[3] == 1), "chk_config_lock"); - printCheckBox("CONFLCKPRENA", "Pairing enabled", ((int)basicLockConfigAclPrefs[4] == 1), "chk_config_lock"); - printCheckBox("CONFLCKBTENA", "Button enabled", ((int)basicLockConfigAclPrefs[5] == 1), "chk_config_lock"); - printCheckBox("CONFLCKLEDENA", "LED flash enabled", ((int)basicLockConfigAclPrefs[6] == 1), "chk_config_lock"); - printCheckBox("CONFLCKLEDBR", "LED brightness", ((int)basicLockConfigAclPrefs[7] == 1), "chk_config_lock"); - printCheckBox("CONFLCKTZOFF", "Timezone offset", ((int)basicLockConfigAclPrefs[8] == 1), "chk_config_lock"); - printCheckBox("CONFLCKDSTM", "DST mode", ((int)basicLockConfigAclPrefs[9] == 1), "chk_config_lock"); - printCheckBox("CONFLCKFOB1", "Fob Action 1", ((int)basicLockConfigAclPrefs[10] == 1), "chk_config_lock"); - printCheckBox("CONFLCKFOB2", "Fob Action 2", ((int)basicLockConfigAclPrefs[11] == 1), "chk_config_lock"); - printCheckBox("CONFLCKFOB3", "Fob Action 3", ((int)basicLockConfigAclPrefs[12] == 1), "chk_config_lock"); - printCheckBox("CONFLCKSGLLCK", "Single Lock", ((int)basicLockConfigAclPrefs[13] == 1), "chk_config_lock"); - printCheckBox("CONFLCKADVM", "Advertising Mode", ((int)basicLockConfigAclPrefs[14] == 1), "chk_config_lock"); - printCheckBox("CONFLCKTZID", "Timezone ID", ((int)basicLockConfigAclPrefs[15] == 1), "chk_config_lock"); - - printCheckBox("CONFLCKUPOD", "Unlocked Position Offset Degrees", ((int)advancedLockConfigAclPrefs[0] == 1), "chk_config_lock"); - printCheckBox("CONFLCKLPOD", "Locked Position Offset Degrees", ((int)advancedLockConfigAclPrefs[1] == 1), "chk_config_lock"); - printCheckBox("CONFLCKSLPOD", "Single Locked Position Offset Degrees", ((int)advancedLockConfigAclPrefs[2] == 1), "chk_config_lock"); - printCheckBox("CONFLCKUTLTOD", "Unlocked To Locked Transition Offset Degrees", ((int)advancedLockConfigAclPrefs[3] == 1), "chk_config_lock"); - printCheckBox("CONFLCKLNGT", "Lock n Go timeout", ((int)advancedLockConfigAclPrefs[4] == 1), "chk_config_lock"); - printCheckBox("CONFLCKSBPA", "Single button press action", ((int)advancedLockConfigAclPrefs[5] == 1), "chk_config_lock"); - printCheckBox("CONFLCKDBPA", "Double button press action", ((int)advancedLockConfigAclPrefs[6] == 1), "chk_config_lock"); - printCheckBox("CONFLCKDC", "Detached cylinder", ((int)advancedLockConfigAclPrefs[7] == 1), "chk_config_lock"); - printCheckBox("CONFLCKBATT", "Battery type", ((int)advancedLockConfigAclPrefs[8] == 1), "chk_config_lock"); - printCheckBox("CONFLCKABTD", "Automatic battery type detection", ((int)advancedLockConfigAclPrefs[9] == 1), "chk_config_lock"); - printCheckBox("CONFLCKUNLD", "Unlatch duration", ((int)advancedLockConfigAclPrefs[10] == 1), "chk_config_lock"); - printCheckBox("CONFLCKALT", "Auto lock timeout", ((int)advancedLockConfigAclPrefs[11] == 1), "chk_config_lock"); - printCheckBox("CONFLCKAUNLD", "Auto unlock disabled", ((int)advancedLockConfigAclPrefs[12] == 1), "chk_config_lock"); - printCheckBox("CONFLCKNMENA", "Nightmode enabled", ((int)advancedLockConfigAclPrefs[13] == 1), "chk_config_lock"); - printCheckBox("CONFLCKNMST", "Nightmode start time", ((int)advancedLockConfigAclPrefs[14] == 1), "chk_config_lock"); - printCheckBox("CONFLCKNMET", "Nightmode end time", ((int)advancedLockConfigAclPrefs[15] == 1), "chk_config_lock"); - printCheckBox("CONFLCKNMALENA", "Nightmode auto lock enabled", ((int)advancedLockConfigAclPrefs[16] == 1), "chk_config_lock"); - printCheckBox("CONFLCKNMAULD", "Nightmode auto unlock disabled", ((int)advancedLockConfigAclPrefs[17] == 1), "chk_config_lock"); - printCheckBox("CONFLCKNMLOS", "Nightmode immediate lock on start", ((int)advancedLockConfigAclPrefs[18] == 1), "chk_config_lock"); - printCheckBox("CONFLCKALENA", "Auto lock enabled", ((int)advancedLockConfigAclPrefs[19] == 1), "chk_config_lock"); - printCheckBox("CONFLCKIALENA", "Immediate auto lock enabled", ((int)advancedLockConfigAclPrefs[20] == 1), "chk_config_lock"); - printCheckBox("CONFLCKAUENA", "Auto update enabled", ((int)advancedLockConfigAclPrefs[21] == 1), "chk_config_lock"); - _response.concat("
ChangeAllowed

"); - } - if(_nukiOpener != nullptr) - { - uint32_t basicOpenerConfigAclPrefs[14]; - _preferences->getBytes(preference_conf_opener_basic_acl, &basicOpenerConfigAclPrefs, sizeof(basicOpenerConfigAclPrefs)); - uint32_t advancedOpenerConfigAclPrefs[20]; - _preferences->getBytes(preference_conf_opener_advanced_acl, &advancedOpenerConfigAclPrefs, sizeof(advancedOpenerConfigAclPrefs)); - - _response.concat("

Nuki Opener Access Control

"); - _response.concat(""); - _response.concat(""); - _response.concat(""); - - printCheckBox("ACLOPNUNLCK", "Activate Ring-to-Open", ((int)aclPrefs[9] == 1), "chk_access_opener"); - printCheckBox("ACLOPNLCK", "Deactivate Ring-to-Open", ((int)aclPrefs[10] == 1), "chk_access_opener"); - printCheckBox("ACLOPNUNLTCH", "Electric Strike Actuation", ((int)aclPrefs[11] == 1), "chk_access_opener"); - printCheckBox("ACLOPNUNLCKCM", "Activate Continuous Mode", ((int)aclPrefs[12] == 1), "chk_access_opener"); - printCheckBox("ACLOPNLCKCM", "Deactivate Continuous Mode", ((int)aclPrefs[13] == 1), "chk_access_opener"); - printCheckBox("ACLOPNFOB1", "Fob Action 1", ((int)aclPrefs[14] == 1), "chk_access_opener"); - printCheckBox("ACLOPNFOB2", "Fob Action 2", ((int)aclPrefs[15] == 1), "chk_access_opener"); - printCheckBox("ACLOPNFOB3", "Fob Action 3", ((int)aclPrefs[16] == 1), "chk_access_opener"); - _response.concat("
ActionAllowed

"); - - _response.concat("

Nuki Opener Config Control (Requires PIN to be set)

"); - _response.concat(""); - _response.concat(""); - _response.concat(""); - - printCheckBox("CONFOPNNAME", "Name", ((int)basicOpenerConfigAclPrefs[0] == 1), "chk_config_opener"); - printCheckBox("CONFOPNLAT", "Latitude", ((int)basicOpenerConfigAclPrefs[1] == 1), "chk_config_opener"); - printCheckBox("CONFOPNLONG", "Longitude", ((int)basicOpenerConfigAclPrefs[2] == 1), "chk_config_opener"); - printCheckBox("CONFOPNPRENA", "Pairing enabled", ((int)basicOpenerConfigAclPrefs[3] == 1), "chk_config_opener"); - printCheckBox("CONFOPNBTENA", "Button enabled", ((int)basicOpenerConfigAclPrefs[4] == 1), "chk_config_opener"); - printCheckBox("CONFOPNLEDENA", "LED flash enabled", ((int)basicOpenerConfigAclPrefs[5] == 1), "chk_config_opener"); - printCheckBox("CONFOPNTZOFF", "Timezone offset", ((int)basicOpenerConfigAclPrefs[6] == 1), "chk_config_opener"); - printCheckBox("CONFOPNDSTM", "DST mode", ((int)basicOpenerConfigAclPrefs[7] == 1), "chk_config_opener"); - printCheckBox("CONFOPNFOB1", "Fob Action 1", ((int)basicOpenerConfigAclPrefs[8] == 1), "chk_config_opener"); - printCheckBox("CONFOPNFOB2", "Fob Action 2", ((int)basicOpenerConfigAclPrefs[9] == 1), "chk_config_opener"); - printCheckBox("CONFOPNFOB3", "Fob Action 3", ((int)basicOpenerConfigAclPrefs[10] == 1), "chk_config_opener"); - printCheckBox("CONFOPNOPM", "Operating Mode", ((int)basicOpenerConfigAclPrefs[11] == 1), "chk_config_opener"); - printCheckBox("CONFOPNADVM", "Advertising Mode", ((int)basicOpenerConfigAclPrefs[12] == 1), "chk_config_opener"); - printCheckBox("CONFOPNTZID", "Timezone ID", ((int)basicOpenerConfigAclPrefs[13] == 1), "chk_config_opener"); - - printCheckBox("CONFOPNICID", "Intercom ID", ((int)advancedOpenerConfigAclPrefs[0] == 1), "chk_config_opener"); - printCheckBox("CONFOPNBUSMS", "BUS mode Switch", ((int)advancedOpenerConfigAclPrefs[1] == 1), "chk_config_opener"); - printCheckBox("CONFOPNSCDUR", "Short Circuit Duration", ((int)advancedOpenerConfigAclPrefs[2] == 1), "chk_config_opener"); - printCheckBox("CONFOPNESD", "Eletric Strike Delay", ((int)advancedOpenerConfigAclPrefs[3] == 1), "chk_config_opener"); - printCheckBox("CONFOPNRESD", "Random Electric Strike Delay", ((int)advancedOpenerConfigAclPrefs[4] == 1), "chk_config_opener"); - printCheckBox("CONFOPNESDUR", "Electric Strike Duration", ((int)advancedOpenerConfigAclPrefs[5] == 1), "chk_config_opener"); - printCheckBox("CONFOPNDRTOAR", "Disable RTO after ring", ((int)advancedOpenerConfigAclPrefs[6] == 1), "chk_config_opener"); - printCheckBox("CONFOPNRTOT", "RTO timeout", ((int)advancedOpenerConfigAclPrefs[7] == 1), "chk_config_opener"); - printCheckBox("CONFOPNDRBSUP", "Doorbell suppression", ((int)advancedOpenerConfigAclPrefs[8] == 1), "chk_config_opener"); - printCheckBox("CONFOPNDRBSUPDUR", "Doorbell suppression duration", ((int)advancedOpenerConfigAclPrefs[9] == 1), "chk_config_opener"); - printCheckBox("CONFOPNSRING", "Sound Ring", ((int)advancedOpenerConfigAclPrefs[10] == 1), "chk_config_opener"); - printCheckBox("CONFOPNSOPN", "Sound Open", ((int)advancedOpenerConfigAclPrefs[11] == 1), "chk_config_opener"); - printCheckBox("CONFOPNSRTO", "Sound RTO", ((int)advancedOpenerConfigAclPrefs[12] == 1), "chk_config_opener"); - printCheckBox("CONFOPNSCM", "Sound CM", ((int)advancedOpenerConfigAclPrefs[13] == 1), "chk_config_opener"); - printCheckBox("CONFOPNSCFRM", "Sound confirmation", ((int)advancedOpenerConfigAclPrefs[14] == 1), "chk_config_opener"); - printCheckBox("CONFOPNSLVL", "Sound level", ((int)advancedOpenerConfigAclPrefs[15] == 1), "chk_config_opener"); - printCheckBox("CONFOPNSBPA", "Single button press action", ((int)advancedOpenerConfigAclPrefs[16] == 1), "chk_config_opener"); - printCheckBox("CONFOPNDBPA", "Double button press action", ((int)advancedOpenerConfigAclPrefs[17] == 1), "chk_config_opener"); - printCheckBox("CONFOPNBATT", "Battery type", ((int)advancedOpenerConfigAclPrefs[18] == 1), "chk_config_opener"); - printCheckBox("CONFOPNABTD", "Automatic battery type detection", ((int)advancedOpenerConfigAclPrefs[19] == 1), "chk_config_opener"); - _response.concat("
ChangeAllowed

"); - } - _response.concat("
"); - _response.concat(""); - _response.concat(""); } -void WebCfgServer::buildNukiConfigHtml() +void WebCfgServer::buildNukiConfigHtml(AsyncWebServerRequest *request) { - buildHtmlHeader(); + AsyncResponseStream *response = request->beginResponseStream("text/html"); + buildHtmlHeader(response); + response->print("
"); + response->print("

Basic Nuki Configuration

"); + response->print(""); + printCheckBox(response, "LOCKENA", "Nuki Lock enabled", _preferences->getBool(preference_lock_enabled), ""); + if(_preferences->getBool(preference_lock_enabled)) printInputField(response, "MQTTPATH", "MQTT Nuki Lock Path", _preferences->getString(preference_mqtt_lock_path).c_str(), 180, ""); + printCheckBox(response, "OPENA", "Nuki Opener enabled", _preferences->getBool(preference_opener_enabled), ""); + if(_preferences->getBool(preference_opener_enabled)) printInputField(response, "MQTTOPPATH", "MQTT Nuki Opener Path", _preferences->getString(preference_mqtt_opener_path).c_str(), 180, ""); + response->print("

"); + response->print("

Advanced Nuki Configuration

"); + response->print(""); - _response.concat(""); - _response.concat("

Basic Nuki Configuration

"); - _response.concat("
"); - printCheckBox("LOCKENA", "Nuki Smartlock enabled", _preferences->getBool(preference_lock_enabled), ""); - - if(_preferences->getBool(preference_lock_enabled)) - { - printInputField("MQTTPATH", "MQTT Nuki Smartlock Path", _preferences->getString(preference_mqtt_lock_path).c_str(), 180, ""); - } - - printCheckBox("OPENA", "Nuki Opener enabled", _preferences->getBool(preference_opener_enabled), ""); - - if(_preferences->getBool(preference_opener_enabled)) - { - printInputField("MQTTOPPATH", "MQTT Nuki Opener Path", _preferences->getString(preference_mqtt_opener_path).c_str(), 180, ""); - } - _response.concat("

"); - - _response.concat("

Advanced Nuki Configuration

"); - _response.concat(""); - - printInputField("LSTINT", "Query interval lock state (seconds)", _preferences->getInt(preference_query_interval_lockstate), 10, ""); - printInputField("CFGINT", "Query interval configuration (seconds)", _preferences->getInt(preference_query_interval_configuration), 10, ""); - printInputField("BATINT", "Query interval battery (seconds)", _preferences->getInt(preference_query_interval_battery), 10, ""); + printInputField(response, "LSTINT", "Query interval lock state (seconds)", _preferences->getInt(preference_query_interval_lockstate), 10, ""); + printInputField(response, "CFGINT", "Query interval configuration (seconds)", _preferences->getInt(preference_query_interval_configuration), 10, ""); + printInputField(response, "BATINT", "Query interval battery (seconds)", _preferences->getInt(preference_query_interval_battery), 10, ""); if((_nuki != nullptr && _nuki->hasKeypad()) || (_nukiOpener != nullptr && _nukiOpener->hasKeypad())) { - printInputField("KPINT", "Query interval keypad (seconds)", _preferences->getInt(preference_query_interval_keypad), 10, ""); + printInputField(response, "KPINT", "Query interval keypad (seconds)", _preferences->getInt(preference_query_interval_keypad), 10, ""); } - printInputField("NRTRY", "Number of retries if command failed", _preferences->getInt(preference_command_nr_of_retries), 10, ""); - printInputField("TRYDLY", "Delay between retries (milliseconds)", _preferences->getInt(preference_command_retry_delay), 10, ""); - if(_nuki != nullptr) printCheckBox("REGAPP", "Lock: Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)", _preferences->getBool(preference_register_as_app), ""); - if(_nukiOpener != nullptr) printCheckBox("REGAPPOPN", "Opener: Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)", _preferences->getBool(preference_register_opener_as_app), ""); + printInputField(response, "NRTRY", "Number of retries if command failed", _preferences->getInt(preference_command_nr_of_retries), 10, ""); + printInputField(response, "TRYDLY", "Delay between retries (milliseconds)", _preferences->getInt(preference_command_retry_delay), 10, ""); + if(_preferences->getBool(preference_lock_enabled, true)) printCheckBox(response, "REGAPP", "Lock: Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)", _preferences->getBool(preference_register_as_app), ""); + if(_preferences->getBool(preference_opener_enabled, false)) printCheckBox(response, "REGAPPOPN", "Opener: Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)", _preferences->getBool(preference_register_opener_as_app), ""); #if PRESENCE_DETECTION_ENABLED - printInputField("PRDTMO", "Presence detection timeout (seconds; -1 to disable)", _preferences->getInt(preference_presence_detection_timeout), 10, ""); + printInputField(response, "PRDTMO", "Presence detection timeout (seconds; -1 to disable)", _preferences->getInt(preference_presence_detection_timeout), 10, ""); #endif - printInputField("RSBC", "Restart if bluetooth beacons not received (seconds; -1 to disable)", _preferences->getInt(preference_restart_ble_beacon_lost), 10, ""); - printInputField("TXPWR", "BLE transmit power in dB (minimum -12, maximum 9)", _preferences->getInt(preference_ble_tx_power, 9), 10, ""); + printInputField(response, "RSBC", "Restart if bluetooth beacons not received (seconds; -1 to disable)", _preferences->getInt(preference_restart_ble_beacon_lost), 10, ""); + printInputField(response, "TXPWR", "BLE transmit power in dB (minimum -12, maximum 9)", _preferences->getInt(preference_ble_tx_power, 9), 10, ""); - _response.concat("
"); - _response.concat("
"); - _response.concat("
"); - _response.concat(""); + response->print(""); + response->print("
"); + response->print(""); + response->print(""); + request->send(response); } -void WebCfgServer::buildGpioConfigHtml() +void WebCfgServer::buildGpioConfigHtml(AsyncWebServerRequest *request) { - buildHtmlHeader(); - - _response.concat("
"); - _response.concat("

GPIO Configuration

"); - _response.concat(""); + AsyncResponseStream *response = request->beginResponseStream("text/html"); + buildHtmlHeader(response); + response->print(""); + response->print("

GPIO Configuration

"); + response->print("
"); + std::vector> options; + String gpiopreselects = "var gpio = []; "; const auto& availablePins = _gpio->availablePins(); + const auto& disabledPins = _gpio->getDisabledPins(); + for(const auto& pin : availablePins) { String pinStr = String(pin); String pinDesc = "Gpio " + pinStr; - - printDropDown(pinStr.c_str(), pinDesc.c_str(), getPreselectionForGpio(pin), getGpioOptions()); + printDropDown(response, pinStr.c_str(), pinDesc.c_str(), "", options, "gpioselect"); + if(std::find(disabledPins.begin(), disabledPins.end(), pin) != disabledPins.end()) gpiopreselects.concat("gpio[" + pinStr + "] = '21';"); + else gpiopreselects.concat("gpio[" + pinStr + "] = '" + getPreselectionForGpio(pin) + "';"); } - _response.concat("
"); - _response.concat("
"); - _response.concat("
"); - _response.concat(""); + response->print(""); + response->print("
"); + response->print(""); + + options = getGpioOptions(); + + response->print(""); + response->print(""); + request->send(response); } -void WebCfgServer::buildConfigureWifiHtml() +#ifndef CONFIG_IDF_TARGET_ESP32H2 +void WebCfgServer::buildConfigureWifiHtml(AsyncWebServerRequest *request) { - buildHtmlHeader(); - - _response.concat("

Wi-Fi

"); - _response.concat("Click confirm to restart ESP into Wi-Fi configuration mode. After restart, connect to ESP access point to reconfigure Wi-Fi.

"); - buildNavigationButton("Confirm", "/wifimanager"); - - _response.concat(""); + AsyncResponseStream *response = request->beginResponseStream("text/html"); + buildHtmlHeader(response); + response->print("

Wi-Fi

"); + response->print("Click confirm to restart ESP into Wi-Fi configuration mode. After restart, connect to ESP access point to reconfigure Wi-Fi.

"); + buildNavigationButton(response, "Confirm", "/wifimanager"); + response->print(""); + request->send(response); } +#endif -void WebCfgServer::buildInfoHtml() +void WebCfgServer::buildInfoHtml(AsyncWebServerRequest *request) { uint32_t aclPrefs[17]; _preferences->getBytes(preference_acl, &aclPrefs, sizeof(aclPrefs)); - buildHtmlHeader(); - _response.concat("

System Information

");
-    _response.concat("------------ NUKI HUB ------------");
-    _response.concat("\nVersion: ");
-    _response.concat(NUKI_HUB_VERSION);
-    _response.concat("\nBuild: ");
-    _response.concat(NUKI_HUB_BUILD);
+    AsyncResponseStream *response = request->beginResponseStream("text/html");
+    buildHtmlHeader(response);
+    response->print("

System Information

");
+    response->print("------------ NUKI HUB ------------");
+    response->print("\nVersion: ");
+    response->print(NUKI_HUB_VERSION);
+    response->print("\nBuild: ");
+    response->print(NUKI_HUB_BUILD);
     #ifndef DEBUG_NUKIHUB
-    _response.concat("\nBuild type: Release");
+    response->print("\nBuild type: Release");
     #else
-    _response.concat("\nBuild type: Debug");
+    response->print("\nBuild type: Debug");
     #endif
-    _response.concat("\nBuild date: ");
-    _response.concat(NUKI_HUB_DATE);
-    _response.concat("\nUptime (min): ");
-    _response.concat(esp_timer_get_time() / 1000 / 1000 / 60);
-    _response.concat("\nConfig version: ");
-    _response.concat(_preferences->getInt(preference_config_version));
-    _response.concat("\nLast restart reason FW: ");
-    _response.concat(getRestartReason());
-    _response.concat("\nLast restart reason ESP: ");
-    _response.concat(getEspRestartReason());
-    _response.concat("\nFree heap: ");
-    _response.concat(esp_get_free_heap_size());
-    _response.concat("\nNetwork task stack high watermark: ");
-    _response.concat(uxTaskGetStackHighWaterMark(networkTaskHandle));
-    _response.concat("\nNuki task stack high watermark: ");
-    _response.concat(uxTaskGetStackHighWaterMark(nukiTaskHandle));
-    _response.concat("\n\n------------ GENERAL SETTINGS ------------");
-    _response.concat("\nNetwork task stack size: ");
-    _response.concat(_preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE));
-    _response.concat("\nNuki task stack size: ");
-    _response.concat(_preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE));
-    _response.concat("\nCheck for updates: ");
-    _response.concat(_preferences->getBool(preference_check_updates, false) ? "Yes" : "No");
-    _response.concat("\nLatest version: ");
-    _response.concat(_preferences->getString(preference_latest_version, ""));
-    _response.concat("\nAllow update from MQTT: ");
-    _response.concat(_preferences->getBool(preference_update_from_mqtt, false) ? "Yes" : "No");
-    _response.concat("\nWeb configurator username: ");
-    _response.concat(_preferences->getString(preference_cred_user, "").length() > 0 ? "***" : "Not set");
-    _response.concat("\nWeb configurator password: ");
-    _response.concat(_preferences->getString(preference_cred_password, "").length() > 0 ? "***" : "Not set");
-    _response.concat("\nWeb configurator enabled: ");
-    _response.concat(_preferences->getBool(preference_webserver_enabled, true) ? "Yes" : "No");
-    _response.concat("\nPublish debug information enabled: ");
-    _response.concat(_preferences->getBool(preference_publish_debug_info, false) ? "Yes" : "No");
-    _response.concat("\nMQTT log enabled: ");
-    _response.concat(_preferences->getBool(preference_mqtt_log_enabled, false) ? "Yes" : "No");
-    _response.concat("\nWebserial enabled: ");
-    _response.concat(_preferences->getBool(preference_webserial_enabled, false) ? "Yes" : "No");
-    _response.concat("\nBootloop protection enabled: ");
-    _response.concat(_preferences->getBool(preference_enable_bootloop_reset, false) ? "Yes" : "No");
-    _response.concat("\n\n------------ NETWORK ------------");
-    _response.concat("\nNetwork device: ");
-    _response.concat(_network->networkDeviceName());
-    _response.concat("\nNetwork connected: ");
-    _response.concat(_network->isConnected() ? "Yes" : "No");
+    response->print("\nBuild date: ");
+    response->print(NUKI_HUB_DATE);
+    response->print("\nUpdater version: ");
+    response->print(_preferences->getString(preference_updater_version, ""));
+    response->print("\nUpdater build: ");
+    response->print(_preferences->getString(preference_updater_build, ""));
+    response->print("\nUpdater build date: ");
+    response->print(_preferences->getString(preference_updater_date, ""));
+    response->print("\nUptime (min): ");
+    response->print(esp_timer_get_time() / 1000 / 1000 / 60);
+    response->print("\nConfig version: ");
+    response->print(_preferences->getInt(preference_config_version));
+    response->print("\nLast restart reason FW: ");
+    response->print(getRestartReason());
+    response->print("\nLast restart reason ESP: ");
+    response->print(getEspRestartReason());
+    response->print("\nFree heap: ");
+    response->print(esp_get_free_heap_size());
+    response->print("\nNetwork task stack high watermark: ");
+    response->print(uxTaskGetStackHighWaterMark(networkTaskHandle));
+    response->print("\nNuki task stack high watermark: ");
+    response->print(uxTaskGetStackHighWaterMark(nukiTaskHandle));
+    response->print("\n\n------------ GENERAL SETTINGS ------------");
+    response->print("\nNetwork task stack size: ");
+    response->print(_preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE));
+    response->print("\nNuki task stack size: ");
+    response->print(_preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE));
+    response->print("\nCheck for updates: ");
+    response->print(_preferences->getBool(preference_check_updates, false) ? "Yes" : "No");
+    response->print("\nLatest version: ");
+    response->print(_preferences->getString(preference_latest_version, ""));
+    response->print("\nAllow update from MQTT: ");
+    response->print(_preferences->getBool(preference_update_from_mqtt, false) ? "Yes" : "No");
+    response->print("\nWeb configurator username: ");
+    response->print(_preferences->getString(preference_cred_user, "").length() > 0 ? "***" : "Not set");
+    response->print("\nWeb configurator password: ");
+    response->print(_preferences->getString(preference_cred_password, "").length() > 0 ? "***" : "Not set");
+    response->print("\nWeb configurator enabled: ");
+    response->print(_preferences->getBool(preference_webserver_enabled, true) ? "Yes" : "No");
+    response->print("\nPublish debug information enabled: ");
+    response->print(_preferences->getBool(preference_publish_debug_info, false) ? "Yes" : "No");
+    response->print("\nMQTT log enabled: ");
+    response->print(_preferences->getBool(preference_mqtt_log_enabled, false) ? "Yes" : "No");
+    response->print("\nWebserial enabled: ");
+    response->print(_preferences->getBool(preference_webserial_enabled, false) ? "Yes" : "No");
+    response->print("\nBootloop protection enabled: ");
+    response->print(_preferences->getBool(preference_enable_bootloop_reset, false) ? "Yes" : "No");
+    response->print("\n\n------------ NETWORK ------------");
+    response->print("\nNetwork device: ");
+    response->print(_network->networkDeviceName());
+    response->print("\nNetwork connected: ");
+    response->print(_network->isConnected() ? "Yes" : "No");
     if(_network->isConnected())
     {
-        _response.concat("\nIP Address: ");
-        _response.concat(_network->localIP());
+        response->print("\nIP Address: ");
+        response->print(_network->localIP());
+
         if(_network->networkDeviceName() == "Built-in Wi-Fi")
         {
-            _response.concat("\nSSID: ");
-            _response.concat(WiFi.SSID());
-            _response.concat("\nBSSID of AP: ");
-            _response.concat(_network->networkBSSID());
-            _response.concat("\nESP32 MAC address: ");
-            _response.concat(WiFi.macAddress());
+            #ifndef CONFIG_IDF_TARGET_ESP32H2
+            response->print("\nSSID: ");
+            response->print(WiFi.SSID());
+            response->print("\nBSSID of AP: ");
+            response->print(_network->networkBSSID());
+            response->print("\nESP32 MAC address: ");
+            response->print(WiFi.macAddress());
+            #endif
         }
         else
         {
-            /*
-            preference_has_mac_saved
-            preference_has_mac_byte_0
-            preference_has_mac_byte_1
-            preference_has_mac_byte_2
-            */
+            //Ethernet info
         }
     }
-    _response.concat("\n\n------------ NETWORK SETTINGS ------------");
-    _response.concat("\nNuki Hub hostname: ");
-    _response.concat(_preferences->getString(preference_hostname, ""));
-    if(_preferences->getBool(preference_ip_dhcp_enabled, true)) _response.concat("\nDHCP enabled: Yes");
+    response->print("\n\n------------ NETWORK SETTINGS ------------");
+    response->print("\nNuki Hub hostname: ");
+    response->print(_preferences->getString(preference_hostname, ""));
+    if(_preferences->getBool(preference_ip_dhcp_enabled, true)) response->print("\nDHCP enabled: Yes");
     else
     {
-        _response.concat("\nDHCP enabled: No");
-        _response.concat("\nStatic IP address: ");
-        _response.concat(_preferences->getString(preference_ip_address, ""));
-        _response.concat("\nStatic IP subnet: ");
-        _response.concat(_preferences->getString(preference_ip_subnet, ""));
-        _response.concat("\nStatic IP gateway: ");
-        _response.concat(_preferences->getString(preference_ip_gateway, ""));
-        _response.concat("\nStatic IP DNS server: ");
-        _response.concat(_preferences->getString(preference_ip_dns_server, ""));
+        response->print("\nDHCP enabled: No");
+        response->print("\nStatic IP address: ");
+        response->print(_preferences->getString(preference_ip_address, ""));
+        response->print("\nStatic IP subnet: ");
+        response->print(_preferences->getString(preference_ip_subnet, ""));
+        response->print("\nStatic IP gateway: ");
+        response->print(_preferences->getString(preference_ip_gateway, ""));
+        response->print("\nStatic IP DNS server: ");
+        response->print(_preferences->getString(preference_ip_dns_server, ""));
     }
 
-    _response.concat("\nFallback to Wi-Fi / Wi-Fi config portal disabled: ");
-    _response.concat(_preferences->getBool(preference_network_wifi_fallback_disabled, false) ? "Yes" : "No");
+    #ifndef CONFIG_IDF_TARGET_ESP32H2
+    response->print("\nFallback to Wi-Fi / Wi-Fi config portal disabled: ");
+    response->print(_preferences->getBool(preference_network_wifi_fallback_disabled, false) ? "Yes" : "No");
     if(_network->networkDeviceName() == "Built-in Wi-Fi")
     {
-        _response.concat("\nConnect to AP with the best signal enabled: ");
-        _response.concat(_preferences->getBool(preference_find_best_rssi, false) ? "Yes" : "No");
-        _response.concat("\nRSSI Publish interval (s): ");
-        
-        if(_preferences->getInt(preference_rssi_publish_interval, 60) < 0) _response.concat("Disabled");
-        else _response.concat(_preferences->getInt(preference_rssi_publish_interval, 60));
+        response->print("\nConnect to AP with the best signal enabled: ");
+        response->print(_preferences->getBool(preference_find_best_rssi, false) ? "Yes" : "No");
+        response->print("\nRSSI Publish interval (s): ");
+
+        if(_preferences->getInt(preference_rssi_publish_interval, 60) < 0) response->print("Disabled");
+        else response->print(_preferences->getInt(preference_rssi_publish_interval, 60));
     }
-    _response.concat("\nRestart ESP32 on network disconnect enabled: ");
-    _response.concat(_preferences->getBool(preference_restart_on_disconnect, false) ? "Yes" : "No");
-    _response.concat("\nReconnect network on MQTT connection failure enabled: ");
-    _response.concat(_preferences->getBool(preference_recon_netw_on_mqtt_discon, false) ? "Yes" : "No");
-    _response.concat("\nMQTT Timeout until restart (s): ");
-    if(_preferences->getInt(preference_network_timeout, 60) < 0) _response.concat("Disabled");
-    else _response.concat(_preferences->getInt(preference_network_timeout, 60));
-    _response.concat("\n\n------------ MQTT ------------");
-    _response.concat("\nMQTT connected: ");
-    _response.concat(_network->mqttConnectionState() > 0 ? "Yes" : "No");
-    _response.concat("\nMQTT broker address: ");
-    _response.concat(_preferences->getString(preference_mqtt_broker, ""));
-    _response.concat("\nMQTT broker port: ");
-    _response.concat(_preferences->getInt(preference_mqtt_broker_port, 1883));
-    _response.concat("\nMQTT username: ");
-    _response.concat(_preferences->getString(preference_mqtt_user, "").length() > 0 ? "***" : "Not set");
-    _response.concat("\nMQTT password: ");
-    _response.concat(_preferences->getString(preference_mqtt_password, "").length() > 0 ? "***" : "Not set");
-    if(_nuki != nullptr)
+    #endif
+    response->print("\nRestart ESP32 on network disconnect enabled: ");
+    response->print(_preferences->getBool(preference_restart_on_disconnect, false) ? "Yes" : "No");
+    response->print("\nReconnect network on MQTT connection failure enabled: ");
+    response->print(_preferences->getBool(preference_recon_netw_on_mqtt_discon, false) ? "Yes" : "No");
+    response->print("\nMQTT Timeout until restart (s): ");
+    if(_preferences->getInt(preference_network_timeout, 60) < 0) response->print("Disabled");
+    else response->print(_preferences->getInt(preference_network_timeout, 60));
+    response->print("\n\n------------ MQTT ------------");
+    response->print("\nMQTT connected: ");
+    response->print(_network->mqttConnectionState() > 0 ? "Yes" : "No");
+    response->print("\nMQTT broker address: ");
+    response->print(_preferences->getString(preference_mqtt_broker, ""));
+    response->print("\nMQTT broker port: ");
+    response->print(_preferences->getInt(preference_mqtt_broker_port, 1883));
+    response->print("\nMQTT username: ");
+    response->print(_preferences->getString(preference_mqtt_user, "").length() > 0 ? "***" : "Not set");
+    response->print("\nMQTT password: ");
+    response->print(_preferences->getString(preference_mqtt_password, "").length() > 0 ? "***" : "Not set");
+    if(_preferences->getBool(preference_lock_enabled, true))
     {
-        _response.concat("\nMQTT lock base topic: ");
-        _response.concat(_preferences->getString(preference_mqtt_lock_path, ""));
+        response->print("\nMQTT lock base topic: ");
+        response->print(_preferences->getString(preference_mqtt_lock_path, ""));
     }
-    if(_nukiOpener != nullptr)
+    if(_preferences->getBool(preference_opener_enabled, false))
     {
-        _response.concat("\nMQTT opener base topic: ");
-        _response.concat(_preferences->getString(preference_mqtt_lock_path, ""));
+        response->print("\nMQTT opener base topic: ");
+        response->print(_preferences->getString(preference_mqtt_lock_path, ""));
     }
-    _response.concat("\nMQTT SSL CA: ");
-    _response.concat(_preferences->getString(preference_mqtt_ca, "").length() > 0 ? "***" : "Not set");
-    _response.concat("\nMQTT SSL CRT: ");
-    _response.concat(_preferences->getString(preference_mqtt_crt, "").length() > 0 ? "***" : "Not set");
-    _response.concat("\nMQTT SSL Key: ");
-    _response.concat(_preferences->getString(preference_mqtt_key, "").length() > 0 ? "***" : "Not set");
-    _response.concat("\n\n------------ BLUETOOTH ------------");
-    _response.concat("\nBluetooth TX power (dB): ");
-    _response.concat(_preferences->getInt(preference_ble_tx_power, 9));
-    _response.concat("\nBluetooth command nr of retries: ");
-    _response.concat(_preferences->getInt(preference_command_nr_of_retries, 3));
-    _response.concat("\nBluetooth command retry delay (ms): ");
-    _response.concat(_preferences->getInt(preference_command_retry_delay, 100));
-    _response.concat("\nSeconds until reboot when no BLE beacons recieved: ");
-    _response.concat(_preferences->getInt(preference_restart_ble_beacon_lost, 60));
-    _response.concat("\n\n------------ QUERY / PUBLISH SETTINGS ------------");
-    _response.concat("\nLock/Opener state query interval (s): ");
-    _response.concat(_preferences->getInt(preference_query_interval_lockstate, 1800));
-    _response.concat("\nPublish Nuki device authorization log: ");
-    _response.concat(_preferences->getBool(preference_publish_authdata, false) ? "Yes" : "No");
-    _response.concat("\nMax authorization log entries to retrieve: ");
-    _response.concat(_preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG));
-    _response.concat("\nBattery state query interval (s): ");
-    _response.concat(_preferences->getInt(preference_query_interval_battery, 1800));
-    _response.concat("\nMost non-JSON MQTT topics disabled: ");
-    _response.concat(_preferences->getBool(preference_disable_non_json, false) ? "Yes" : "No");
-    _response.concat("\nPublish Nuki device config: ");
-    _response.concat(_preferences->getBool(preference_conf_info_enabled, false) ? "Yes" : "No");
-    _response.concat("\nConfig query interval (s): ");
-    _response.concat(_preferences->getInt(preference_query_interval_configuration, 3600));
-    _response.concat("\nPublish Keypad info: ");
-    _response.concat(_preferences->getBool(preference_keypad_info_enabled, false) ? "Yes" : "No");
-    _response.concat("\nKeypad query interval (s): ");
-    _response.concat(_preferences->getInt(preference_query_interval_keypad, 1800));
-    _response.concat("\nEnable Keypad control: ");
-    _response.concat(_preferences->getBool(preference_keypad_control_enabled, false) ? "Yes" : "No");
-    _response.concat("\nPublish Keypad topic per entry: ");
-    _response.concat(_preferences->getBool(preference_keypad_topic_per_entry, false) ? "Yes" : "No");
-    _response.concat("\nPublish Keypad codes: ");
-    _response.concat(_preferences->getBool(preference_keypad_publish_code, false) ? "Yes" : "No");
-    _response.concat("\nMax keypad entries to retrieve: ");
-    _response.concat(_preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD));
-    _response.concat("\nPublish timecontrol info: ");
-    _response.concat(_preferences->getBool(preference_timecontrol_info_enabled, false) ? "Yes" : "No");
-    _response.concat("\nKeypad query interval (s): ");
-    _response.concat(_preferences->getInt(preference_query_interval_keypad, 1800));
-    _response.concat("\nEnable timecontrol control: ");
-    _response.concat(_preferences->getBool(preference_timecontrol_control_enabled, false) ? "Yes" : "No");
-    _response.concat("\nPublish timecontrol topic per entry: ");
-    _response.concat(_preferences->getBool(preference_timecontrol_topic_per_entry, false) ? "Yes" : "No");
-    _response.concat("\nMax timecontrol entries to retrieve: ");
-    _response.concat(_preferences->getInt(preference_timecontrol_max_entries, MAX_TIMECONTROL));
-    _response.concat("\n\n------------ HOME ASSISTANT ------------");
-    _response.concat("\nHome Assistant auto discovery enabled: ");
+    response->print("\nMQTT SSL CA: ");
+    response->print(_preferences->getString(preference_mqtt_ca, "").length() > 0 ? "***" : "Not set");
+    response->print("\nMQTT SSL CRT: ");
+    response->print(_preferences->getString(preference_mqtt_crt, "").length() > 0 ? "***" : "Not set");
+    response->print("\nMQTT SSL Key: ");
+    response->print(_preferences->getString(preference_mqtt_key, "").length() > 0 ? "***" : "Not set");
+    response->print("\n\n------------ BLUETOOTH ------------");
+    response->print("\nBluetooth TX power (dB): ");
+    response->print(_preferences->getInt(preference_ble_tx_power, 9));
+    response->print("\nBluetooth command nr of retries: ");
+    response->print(_preferences->getInt(preference_command_nr_of_retries, 3));
+    response->print("\nBluetooth command retry delay (ms): ");
+    response->print(_preferences->getInt(preference_command_retry_delay, 100));
+    response->print("\nSeconds until reboot when no BLE beacons recieved: ");
+    response->print(_preferences->getInt(preference_restart_ble_beacon_lost, 60));
+    response->print("\n\n------------ QUERY / PUBLISH SETTINGS ------------");
+    response->print("\nLock/Opener state query interval (s): ");
+    response->print(_preferences->getInt(preference_query_interval_lockstate, 1800));
+    response->print("\nPublish Nuki device authorization log: ");
+    response->print(_preferences->getBool(preference_publish_authdata, false) ? "Yes" : "No");
+    response->print("\nMax authorization log entries to retrieve: ");
+    response->print(_preferences->getInt(preference_authlog_max_entries, MAX_AUTHLOG));
+    response->print("\nBattery state query interval (s): ");
+    response->print(_preferences->getInt(preference_query_interval_battery, 1800));
+    response->print("\nMost non-JSON MQTT topics disabled: ");
+    response->print(_preferences->getBool(preference_disable_non_json, false) ? "Yes" : "No");
+    response->print("\nPublish Nuki device config: ");
+    response->print(_preferences->getBool(preference_conf_info_enabled, false) ? "Yes" : "No");
+    response->print("\nConfig query interval (s): ");
+    response->print(_preferences->getInt(preference_query_interval_configuration, 3600));
+    response->print("\nPublish Keypad info: ");
+    response->print(_preferences->getBool(preference_keypad_info_enabled, false) ? "Yes" : "No");
+    response->print("\nKeypad query interval (s): ");
+    response->print(_preferences->getInt(preference_query_interval_keypad, 1800));
+    response->print("\nEnable Keypad control: ");
+    response->print(_preferences->getBool(preference_keypad_control_enabled, false) ? "Yes" : "No");
+    response->print("\nPublish Keypad topic per entry: ");
+    response->print(_preferences->getBool(preference_keypad_topic_per_entry, false) ? "Yes" : "No");
+    response->print("\nPublish Keypad codes: ");
+    response->print(_preferences->getBool(preference_keypad_publish_code, false) ? "Yes" : "No");
+    response->print("\nMax keypad entries to retrieve: ");
+    response->print(_preferences->getInt(preference_keypad_max_entries, MAX_KEYPAD));
+    response->print("\nPublish timecontrol info: ");
+    response->print(_preferences->getBool(preference_timecontrol_info_enabled, false) ? "Yes" : "No");
+    response->print("\nKeypad query interval (s): ");
+    response->print(_preferences->getInt(preference_query_interval_keypad, 1800));
+    response->print("\nEnable timecontrol control: ");
+    response->print(_preferences->getBool(preference_timecontrol_control_enabled, false) ? "Yes" : "No");
+    response->print("\nPublish timecontrol topic per entry: ");
+    response->print(_preferences->getBool(preference_timecontrol_topic_per_entry, false) ? "Yes" : "No");
+    response->print("\nMax timecontrol entries to retrieve: ");
+    response->print(_preferences->getInt(preference_timecontrol_max_entries, MAX_TIMECONTROL));
+    response->print("\n\n------------ HOME ASSISTANT ------------");
+    response->print("\nHome Assistant auto discovery enabled: ");
     if(_preferences->getString(preference_mqtt_hass_discovery, "").length() > 0)
     {
-        _response.concat("Yes");
-        _response.concat("\nHome Assistant auto discovery topic: ");
-        _response.concat(_preferences->getString(preference_mqtt_hass_discovery, "") + "/");
-        _response.concat("\nNuki Hub configuration URL for HA: ");
-        _response.concat(_preferences->getString(preference_mqtt_hass_cu_url, "").length() > 0 ? _preferences->getString(preference_mqtt_hass_cu_url, "") : "http://" + _network->localIP());
+        response->print("Yes");
+        response->print("\nHome Assistant auto discovery topic: ");
+        response->print(_preferences->getString(preference_mqtt_hass_discovery, "") + "/");
+        response->print("\nNuki Hub configuration URL for HA: ");
+        response->print(_preferences->getString(preference_mqtt_hass_cu_url, "").length() > 0 ? _preferences->getString(preference_mqtt_hass_cu_url, "") : "http://" + _network->localIP());
     }
-    else _response.concat("No");
-    _response.concat("\n\n------------ NUKI LOCK ------------");
-    if(_nuki == nullptr || !_preferences->getBool(preference_lock_enabled, true)) _response.concat("\nLock enabled: No");
+    else response->print("No");
+    response->print("\n\n------------ NUKI LOCK ------------");
+    if(_nuki == nullptr || !_preferences->getBool(preference_lock_enabled, true)) response->print("\nLock enabled: No");
     else
     {
-        _response.concat("\nLock enabled: Yes");
-        _response.concat("\nPaired: ");
-        _response.concat(_nuki->isPaired() ? "Yes" : "No");
-        _response.concat("\nNuki Hub device ID: ");
-        _response.concat(_preferences->getUInt(preference_device_id_lock, 0));
-        _response.concat("\nNuki device ID: ");
-        _response.concat(_preferences->getUInt(preference_nuki_id_lock, 0) > 0 ? "***" : "Not set");
-        _response.concat("\nFirmware version: ");
-        _response.concat(_nuki->firmwareVersion().c_str());
-        _response.concat("\nHardware version: ");
-        _response.concat(_nuki->hardwareVersion().c_str());
-        _response.concat("\nValid PIN set: ");
-        _response.concat(_nuki->isPaired() ? _nuki->isPinValid() ? "Yes" : "No" : "-");
-        _response.concat("\nHas door sensor: ");
-        _response.concat(_nuki->hasDoorSensor() ? "Yes" : "No");
-        _response.concat("\nHas keypad: ");
-        _response.concat(_nuki->hasKeypad() ? "Yes" : "No");
+        response->print("\nLock enabled: Yes");
+        response->print("\nPaired: ");
+        response->print(_nuki->isPaired() ? "Yes" : "No");
+        response->print("\nNuki Hub device ID: ");
+        response->print(_preferences->getUInt(preference_device_id_lock, 0));
+        response->print("\nNuki device ID: ");
+        response->print(_preferences->getUInt(preference_nuki_id_lock, 0) > 0 ? "***" : "Not set");
+        response->print("\nFirmware version: ");
+        response->print(_nuki->firmwareVersion().c_str());
+        response->print("\nHardware version: ");
+        response->print(_nuki->hardwareVersion().c_str());
+        response->print("\nValid PIN set: ");
+        response->print(_nuki->isPaired() ? _nuki->isPinValid() ? "Yes" : "No" : "-");
+        response->print("\nHas door sensor: ");
+        response->print(_nuki->hasDoorSensor() ? "Yes" : "No");
+        response->print("\nHas keypad: ");
+        response->print(_nuki->hasKeypad() ? "Yes" : "No");
         if(_nuki->hasKeypad())
         {
-            _response.concat("\nKeypad highest entries count: ");
-            _response.concat(_preferences->getInt(preference_lock_max_keypad_code_count, 0));
+            response->print("\nKeypad highest entries count: ");
+            response->print(_preferences->getInt(preference_lock_max_keypad_code_count, 0));
         }
-        _response.concat("\nTimecontrol highest entries count: ");
-        _response.concat(_preferences->getInt(preference_lock_max_timecontrol_entry_count, 0));
-        _response.concat("\nRegister as: ");
-        _response.concat(_preferences->getBool(preference_register_as_app, false) ? "App" : "Bridge");
-        _response.concat("\n\n------------ HYBRID MODE ------------");
-        if(!_preferences->getBool(preference_official_hybrid, false)) _response.concat("\nHybrid mode enabled: No");
+        response->print("\nTimecontrol highest entries count: ");
+        response->print(_preferences->getInt(preference_lock_max_timecontrol_entry_count, 0));
+        response->print("\nRegister as: ");
+        response->print(_preferences->getBool(preference_register_as_app, false) ? "App" : "Bridge");
+        response->print("\n\n------------ HYBRID MODE ------------");
+        if(!_preferences->getBool(preference_official_hybrid, false)) response->print("\nHybrid mode enabled: No");
         else
         {
-            _response.concat("\nHybrid mode enabled: Yes");
-            _response.concat("\nHybrid mode connected: ");
-            _response.concat(_nuki->offConnected() ? "Yes": "No");
-            _response.concat("\nSending actions through official MQTT enabled: ");
-            _response.concat(_preferences->getBool(preference_official_hybrid_actions, false) ? "Yes" : "No");
+            response->print("\nHybrid mode enabled: Yes");
+            response->print("\nHybrid mode connected: ");
+            response->print(_nuki->offConnected() ? "Yes": "No");
+            response->print("\nSending actions through official MQTT enabled: ");
+            response->print(_preferences->getBool(preference_official_hybrid_actions, false) ? "Yes" : "No");
             if(_preferences->getBool(preference_official_hybrid_actions, false))
             {
-                _response.concat("\nRetry actions through BLE enabled: ");
-                _response.concat(_preferences->getBool(preference_official_hybrid_retry, false) ? "Yes" : "No");
+                response->print("\nRetry actions through BLE enabled: ");
+                response->print(_preferences->getBool(preference_official_hybrid_retry, false) ? "Yes" : "No");
             }
-            _response.concat("\nTime between status updates when official MQTT is offline (s): ");
-            _response.concat(_preferences->getInt(preference_query_interval_hybrid_lockstate, 600));
+            response->print("\nTime between status updates when official MQTT is offline (s): ");
+            response->print(_preferences->getInt(preference_query_interval_hybrid_lockstate, 600));
         }
         uint32_t basicLockConfigAclPrefs[16];
         _preferences->getBytes(preference_conf_lock_basic_acl, &basicLockConfigAclPrefs, sizeof(basicLockConfigAclPrefs));
         uint32_t advancedLockConfigAclPrefs[22];
         _preferences->getBytes(preference_conf_lock_advanced_acl, &advancedLockConfigAclPrefs, sizeof(advancedLockConfigAclPrefs));
-        _response.concat("\n\n------------ NUKI LOCK ACL ------------");
-        _response.concat("\nLock: ");
-        _response.concat((int)aclPrefs[0] ? "Allowed" : "Disallowed");
-        _response.concat("\nUnlock: ");
-        _response.concat((int)aclPrefs[1] ? "Allowed" : "Disallowed");
-        _response.concat("\nUnlatch: ");
-        _response.concat((int)aclPrefs[2] ? "Allowed" : "Disallowed");
-        _response.concat("\nLock N Go: ");
-        _response.concat((int)aclPrefs[3] ? "Allowed" : "Disallowed");
-        _response.concat("\nLock N Go Unlatch: ");
-        _response.concat((int)aclPrefs[4] ? "Allowed" : "Disallowed");
-        _response.concat("\nFull Lock: ");
-        _response.concat((int)aclPrefs[5] ? "Allowed" : "Disallowed");
-        _response.concat("\nFob Action 1: ");
-        _response.concat((int)aclPrefs[6] ? "Allowed" : "Disallowed");
-        _response.concat("\nFob Action 2: ");
-        _response.concat((int)aclPrefs[7] ? "Allowed" : "Disallowed");
-        _response.concat("\nFob Action 3: ");
-        _response.concat((int)aclPrefs[8] ? "Allowed" : "Disallowed");
-        _response.concat("\n\n------------ NUKI LOCK CONFIG ACL ------------");
-        _response.concat("\nName: ");
-        _response.concat((int)basicLockConfigAclPrefs[0] ? "Allowed" : "Disallowed");
-        _response.concat("\nLatitude: ");
-        _response.concat((int)basicLockConfigAclPrefs[1] ? "Allowed" : "Disallowed");
-        _response.concat("\nLongitude: ");
-        _response.concat((int)basicLockConfigAclPrefs[2] ? "Allowed" : "Disallowed");
-        _response.concat("\nAuto Unlatch: ");
-        _response.concat((int)basicLockConfigAclPrefs[3] ? "Allowed" : "Disallowed");
-        _response.concat("\nPairing enabled: ");
-        _response.concat((int)basicLockConfigAclPrefs[4] ? "Allowed" : "Disallowed");
-        _response.concat("\nButton enabled: ");
-        _response.concat((int)basicLockConfigAclPrefs[5] ? "Allowed" : "Disallowed");
-        _response.concat("\nLED flash enabled: ");
-        _response.concat((int)basicLockConfigAclPrefs[6] ? "Allowed" : "Disallowed");
-        _response.concat("\nLED brightness: ");
-        _response.concat((int)basicLockConfigAclPrefs[7] ? "Allowed" : "Disallowed");
-        _response.concat("\nTimezone offset: ");
-        _response.concat((int)basicLockConfigAclPrefs[8] ? "Allowed" : "Disallowed");
-        _response.concat("\nDST mode: ");
-        _response.concat((int)basicLockConfigAclPrefs[9] ? "Allowed" : "Disallowed");
-        _response.concat("\nFob Action 1: ");
-        _response.concat((int)basicLockConfigAclPrefs[10] ? "Allowed" : "Disallowed");
-        _response.concat("\nFob Action 2: ");
-        _response.concat((int)basicLockConfigAclPrefs[11] ? "Allowed" : "Disallowed");
-        _response.concat("\nFob Action 3: ");
-        _response.concat((int)basicLockConfigAclPrefs[12] ? "Allowed" : "Disallowed");
-        _response.concat("\nSingle Lock: ");
-        _response.concat((int)basicLockConfigAclPrefs[13] ? "Allowed" : "Disallowed");
-        _response.concat("\nAdvertising Mode: ");
-        _response.concat((int)basicLockConfigAclPrefs[14] ? "Allowed" : "Disallowed");
-        _response.concat("\nTimezone ID: ");
-        _response.concat((int)basicLockConfigAclPrefs[15] ? "Allowed" : "Disallowed");
-        _response.concat("\nUnlocked Position Offset Degrees: ");
-        _response.concat((int)advancedLockConfigAclPrefs[0] ? "Allowed" : "Disallowed");
-        _response.concat("\nLocked Position Offset Degrees: ");
-        _response.concat((int)advancedLockConfigAclPrefs[1] ? "Allowed" : "Disallowed");
-        _response.concat("\nSingle Locked Position Offset Degrees: ");
-        _response.concat((int)advancedLockConfigAclPrefs[2] ? "Allowed" : "Disallowed");
-        _response.concat("\nUnlocked To Locked Transition Offset Degrees: ");
-        _response.concat((int)advancedLockConfigAclPrefs[3] ? "Allowed" : "Disallowed");
-        _response.concat("\nLock n Go timeout: ");
-        _response.concat((int)advancedLockConfigAclPrefs[4] ? "Allowed" : "Disallowed");
-        _response.concat("\nSingle button press action: ");
-        _response.concat((int)advancedLockConfigAclPrefs[5] ? "Allowed" : "Disallowed");
-        _response.concat("\nDouble button press action: ");
-        _response.concat((int)advancedLockConfigAclPrefs[6] ? "Allowed" : "Disallowed");
-        _response.concat("\nDetached cylinder: ");
-        _response.concat((int)advancedLockConfigAclPrefs[7] ? "Allowed" : "Disallowed");
-        _response.concat("\nBattery type: ");
-        _response.concat((int)advancedLockConfigAclPrefs[8] ? "Allowed" : "Disallowed");
-        _response.concat("\nAutomatic battery type detection: ");
-        _response.concat((int)advancedLockConfigAclPrefs[9] ? "Allowed" : "Disallowed");
-        _response.concat("\nUnlatch duration: ");
-        _response.concat((int)advancedLockConfigAclPrefs[10] ? "Allowed" : "Disallowed");
-        _response.concat("\nAuto lock timeout: ");
-        _response.concat((int)advancedLockConfigAclPrefs[11] ? "Allowed" : "Disallowed");
-        _response.concat("\nAuto unlock disabled: ");
-        _response.concat((int)advancedLockConfigAclPrefs[12] ? "Allowed" : "Disallowed");
-        _response.concat("\nNightmode enabled: ");
-        _response.concat((int)advancedLockConfigAclPrefs[13] ? "Allowed" : "Disallowed");
-        _response.concat("\nNightmode start time: ");
-        _response.concat((int)advancedLockConfigAclPrefs[14] ? "Allowed" : "Disallowed");
-        _response.concat("\nNightmode end time: ");
-        _response.concat((int)advancedLockConfigAclPrefs[15] ? "Allowed" : "Disallowed");
-        _response.concat("\nNightmode auto lock enabled: ");
-        _response.concat((int)advancedLockConfigAclPrefs[16] ? "Allowed" : "Disallowed");
-        _response.concat("\nNightmode auto unlock disabled: ");
-        _response.concat((int)advancedLockConfigAclPrefs[17] ? "Allowed" : "Disallowed");
-        _response.concat("\nNightmode immediate lock on start: ");
-        _response.concat((int)advancedLockConfigAclPrefs[18] ? "Allowed" : "Disallowed");
-        _response.concat("\nAuto lock enabled: ");
-        _response.concat((int)advancedLockConfigAclPrefs[19] ? "Allowed" : "Disallowed");
-        _response.concat("\nImmediate auto lock enabled: ");
-        _response.concat((int)advancedLockConfigAclPrefs[20] ? "Allowed" : "Disallowed");
-        _response.concat("\nAuto update enabled: ");
-        _response.concat((int)advancedLockConfigAclPrefs[21] ? "Allowed" : "Disallowed");
+        response->print("\n\n------------ NUKI LOCK ACL ------------");
+        response->print("\nLock: ");
+        response->print((int)aclPrefs[0] ? "Allowed" : "Disallowed");
+        response->print("\nUnlock: ");
+        response->print((int)aclPrefs[1] ? "Allowed" : "Disallowed");
+        response->print("\nUnlatch: ");
+        response->print((int)aclPrefs[2] ? "Allowed" : "Disallowed");
+        response->print("\nLock N Go: ");
+        response->print((int)aclPrefs[3] ? "Allowed" : "Disallowed");
+        response->print("\nLock N Go Unlatch: ");
+        response->print((int)aclPrefs[4] ? "Allowed" : "Disallowed");
+        response->print("\nFull Lock: ");
+        response->print((int)aclPrefs[5] ? "Allowed" : "Disallowed");
+        response->print("\nFob Action 1: ");
+        response->print((int)aclPrefs[6] ? "Allowed" : "Disallowed");
+        response->print("\nFob Action 2: ");
+        response->print((int)aclPrefs[7] ? "Allowed" : "Disallowed");
+        response->print("\nFob Action 3: ");
+        response->print((int)aclPrefs[8] ? "Allowed" : "Disallowed");
+        response->print("\n\n------------ NUKI LOCK CONFIG ACL ------------");
+        response->print("\nName: ");
+        response->print((int)basicLockConfigAclPrefs[0] ? "Allowed" : "Disallowed");
+        response->print("\nLatitude: ");
+        response->print((int)basicLockConfigAclPrefs[1] ? "Allowed" : "Disallowed");
+        response->print("\nLongitude: ");
+        response->print((int)basicLockConfigAclPrefs[2] ? "Allowed" : "Disallowed");
+        response->print("\nAuto Unlatch: ");
+        response->print((int)basicLockConfigAclPrefs[3] ? "Allowed" : "Disallowed");
+        response->print("\nPairing enabled: ");
+        response->print((int)basicLockConfigAclPrefs[4] ? "Allowed" : "Disallowed");
+        response->print("\nButton enabled: ");
+        response->print((int)basicLockConfigAclPrefs[5] ? "Allowed" : "Disallowed");
+        response->print("\nLED flash enabled: ");
+        response->print((int)basicLockConfigAclPrefs[6] ? "Allowed" : "Disallowed");
+        response->print("\nLED brightness: ");
+        response->print((int)basicLockConfigAclPrefs[7] ? "Allowed" : "Disallowed");
+        response->print("\nTimezone offset: ");
+        response->print((int)basicLockConfigAclPrefs[8] ? "Allowed" : "Disallowed");
+        response->print("\nDST mode: ");
+        response->print((int)basicLockConfigAclPrefs[9] ? "Allowed" : "Disallowed");
+        response->print("\nFob Action 1: ");
+        response->print((int)basicLockConfigAclPrefs[10] ? "Allowed" : "Disallowed");
+        response->print("\nFob Action 2: ");
+        response->print((int)basicLockConfigAclPrefs[11] ? "Allowed" : "Disallowed");
+        response->print("\nFob Action 3: ");
+        response->print((int)basicLockConfigAclPrefs[12] ? "Allowed" : "Disallowed");
+        response->print("\nSingle Lock: ");
+        response->print((int)basicLockConfigAclPrefs[13] ? "Allowed" : "Disallowed");
+        response->print("\nAdvertising Mode: ");
+        response->print((int)basicLockConfigAclPrefs[14] ? "Allowed" : "Disallowed");
+        response->print("\nTimezone ID: ");
+        response->print((int)basicLockConfigAclPrefs[15] ? "Allowed" : "Disallowed");
+        response->print("\nUnlocked Position Offset Degrees: ");
+        response->print((int)advancedLockConfigAclPrefs[0] ? "Allowed" : "Disallowed");
+        response->print("\nLocked Position Offset Degrees: ");
+        response->print((int)advancedLockConfigAclPrefs[1] ? "Allowed" : "Disallowed");
+        response->print("\nSingle Locked Position Offset Degrees: ");
+        response->print((int)advancedLockConfigAclPrefs[2] ? "Allowed" : "Disallowed");
+        response->print("\nUnlocked To Locked Transition Offset Degrees: ");
+        response->print((int)advancedLockConfigAclPrefs[3] ? "Allowed" : "Disallowed");
+        response->print("\nLock n Go timeout: ");
+        response->print((int)advancedLockConfigAclPrefs[4] ? "Allowed" : "Disallowed");
+        response->print("\nSingle button press action: ");
+        response->print((int)advancedLockConfigAclPrefs[5] ? "Allowed" : "Disallowed");
+        response->print("\nDouble button press action: ");
+        response->print((int)advancedLockConfigAclPrefs[6] ? "Allowed" : "Disallowed");
+        response->print("\nDetached cylinder: ");
+        response->print((int)advancedLockConfigAclPrefs[7] ? "Allowed" : "Disallowed");
+        response->print("\nBattery type: ");
+        response->print((int)advancedLockConfigAclPrefs[8] ? "Allowed" : "Disallowed");
+        response->print("\nAutomatic battery type detection: ");
+        response->print((int)advancedLockConfigAclPrefs[9] ? "Allowed" : "Disallowed");
+        response->print("\nUnlatch duration: ");
+        response->print((int)advancedLockConfigAclPrefs[10] ? "Allowed" : "Disallowed");
+        response->print("\nAuto lock timeout: ");
+        response->print((int)advancedLockConfigAclPrefs[11] ? "Allowed" : "Disallowed");
+        response->print("\nAuto unlock disabled: ");
+        response->print((int)advancedLockConfigAclPrefs[12] ? "Allowed" : "Disallowed");
+        response->print("\nNightmode enabled: ");
+        response->print((int)advancedLockConfigAclPrefs[13] ? "Allowed" : "Disallowed");
+        response->print("\nNightmode start time: ");
+        response->print((int)advancedLockConfigAclPrefs[14] ? "Allowed" : "Disallowed");
+        response->print("\nNightmode end time: ");
+        response->print((int)advancedLockConfigAclPrefs[15] ? "Allowed" : "Disallowed");
+        response->print("\nNightmode auto lock enabled: ");
+        response->print((int)advancedLockConfigAclPrefs[16] ? "Allowed" : "Disallowed");
+        response->print("\nNightmode auto unlock disabled: ");
+        response->print((int)advancedLockConfigAclPrefs[17] ? "Allowed" : "Disallowed");
+        response->print("\nNightmode immediate lock on start: ");
+        response->print((int)advancedLockConfigAclPrefs[18] ? "Allowed" : "Disallowed");
+        response->print("\nAuto lock enabled: ");
+        response->print((int)advancedLockConfigAclPrefs[19] ? "Allowed" : "Disallowed");
+        response->print("\nImmediate auto lock enabled: ");
+        response->print((int)advancedLockConfigAclPrefs[20] ? "Allowed" : "Disallowed");
+        response->print("\nAuto update enabled: ");
+        response->print((int)advancedLockConfigAclPrefs[21] ? "Allowed" : "Disallowed");
 
         if(_preferences->getBool(preference_show_secrets))
         {
@@ -2986,148 +3495,148 @@ void WebCfgServer::buildInfoHtml()
             nukiBlePref.getBytes("secretKeyK", secretKeyK, 32);
             nukiBlePref.getBytes("authorizationId", authorizationId, 4);
             nukiBlePref.end();
-            _response.concat("\n\n------------ NUKI LOCK PAIRING ------------");
-            _response.concat("\nBLE Address: ");
+            response->print("\n\n------------ NUKI LOCK PAIRING ------------");
+            response->print("\nBLE Address: ");
             for (int i = 0; i < 6; i++)
             {
                 sprintf(tmp, "%02x", currentBleAddress[i]);
-                _response.concat(tmp);
+                response->print(tmp);
             }
-            _response.concat("\nSecretKeyK: ");
+            response->print("\nSecretKeyK: ");
             for (int i = 0; i < 32; i++)
             {
                 sprintf(tmp, "%02x", secretKeyK[i]);
-                _response.concat(tmp);
+                response->print(tmp);
             }
-            _response.concat("\nAuthorizationId: ");
+            response->print("\nAuthorizationId: ");
             for (int i = 0; i < 4; i++)
             {
                 sprintf(tmp, "%02x", authorizationId[i]);
-                _response.concat(tmp);
+                response->print(tmp);
             }
         }
     }
 
-    _response.concat("\n\n------------ NUKI OPENER ------------");
-    if(_nukiOpener == nullptr || !_preferences->getBool(preference_opener_enabled, true)) _response.concat("\nOpener enabled: No");
+    response->print("\n\n------------ NUKI OPENER ------------");
+    if(_nukiOpener == nullptr || !_preferences->getBool(preference_opener_enabled, false)) response->print("\nOpener enabled: No");
     else
     {
-        _response.concat("\nOpener enabled: Yes");
-        _response.concat("\nPaired: ");
-        _response.concat(_nukiOpener->isPaired() ? "Yes" : "No");
-        _response.concat("\nNuki Hub device ID: ");
-        _response.concat(_preferences->getUInt(preference_device_id_opener, 0));
-        _response.concat("\nNuki device ID: ");
-        _response.concat(_preferences->getUInt(preference_nuki_id_opener, 0) > 0 ? "***" : "Not set");
-        _response.concat("\nFirmware version: ");
-        _response.concat(_nukiOpener->firmwareVersion().c_str());
-        _response.concat("\nHardware version: ");
-        _response.concat(_nukiOpener->hardwareVersion().c_str());
-        _response.concat("\nOpener valid PIN set: ");
-        _response.concat(_nukiOpener->isPaired() ? _nukiOpener->isPinValid() ? "Yes" : "No" : "-");
-        _response.concat("\nOpener has keypad: ");
-        _response.concat(_nukiOpener->hasKeypad() ? "Yes" : "No");
+        response->print("\nOpener enabled: Yes");
+        response->print("\nPaired: ");
+        response->print(_nukiOpener->isPaired() ? "Yes" : "No");
+        response->print("\nNuki Hub device ID: ");
+        response->print(_preferences->getUInt(preference_device_id_opener, 0));
+        response->print("\nNuki device ID: ");
+        response->print(_preferences->getUInt(preference_nuki_id_opener, 0) > 0 ? "***" : "Not set");
+        response->print("\nFirmware version: ");
+        response->print(_nukiOpener->firmwareVersion().c_str());
+        response->print("\nHardware version: ");
+        response->print(_nukiOpener->hardwareVersion().c_str());
+        response->print("\nOpener valid PIN set: ");
+        response->print(_nukiOpener->isPaired() ? _nukiOpener->isPinValid() ? "Yes" : "No" : "-");
+        response->print("\nOpener has keypad: ");
+        response->print(_nukiOpener->hasKeypad() ? "Yes" : "No");
         if(_nuki->hasKeypad())
         {
-            _response.concat("\nKeypad highest entries count: ");
-            _response.concat(_preferences->getInt(preference_opener_max_keypad_code_count, 0));
+            response->print("\nKeypad highest entries count: ");
+            response->print(_preferences->getInt(preference_opener_max_keypad_code_count, 0));
         }
-        _response.concat("\nTimecontrol highest entries count: ");
-        _response.concat(_preferences->getInt(preference_opener_max_timecontrol_entry_count, 0));
-        _response.concat("\nRegister as: ");
-        _response.concat(_preferences->getBool(preference_register_opener_as_app, false) ? "App" : "Bridge");
-        _response.concat("\nNuki Opener Lock/Unlock action set to Continuous mode in Home Assistant: ");
-        _response.concat(_preferences->getBool(preference_opener_continuous_mode, false) ? "Yes" : "No");
+        response->print("\nTimecontrol highest entries count: ");
+        response->print(_preferences->getInt(preference_opener_max_timecontrol_entry_count, 0));
+        response->print("\nRegister as: ");
+        response->print(_preferences->getBool(preference_register_opener_as_app, false) ? "App" : "Bridge");
+        response->print("\nNuki Opener Lock/Unlock action set to Continuous mode in Home Assistant: ");
+        response->print(_preferences->getBool(preference_opener_continuous_mode, false) ? "Yes" : "No");
         uint32_t basicOpenerConfigAclPrefs[14];
         _preferences->getBytes(preference_conf_opener_basic_acl, &basicOpenerConfigAclPrefs, sizeof(basicOpenerConfigAclPrefs));
         uint32_t advancedOpenerConfigAclPrefs[20];
         _preferences->getBytes(preference_conf_opener_advanced_acl, &advancedOpenerConfigAclPrefs, sizeof(advancedOpenerConfigAclPrefs));
-        _response.concat("\n\n------------ NUKI OPENER ACL ------------");
-        _response.concat("\nActivate Ring-to-Open: ");
-        _response.concat((int)aclPrefs[9] ? "Allowed" : "Disallowed");
-        _response.concat("\nDeactivate Ring-to-Open: ");
-        _response.concat((int)aclPrefs[10] ? "Allowed" : "Disallowed");
-        _response.concat("\nElectric Strike Actuation: ");
-        _response.concat((int)aclPrefs[11] ? "Allowed" : "Disallowed");
-        _response.concat("\nActivate Continuous Mode: ");
-        _response.concat((int)aclPrefs[12] ? "Allowed" : "Disallowed");
-        _response.concat("\nDeactivate Continuous Mode: ");
-        _response.concat((int)aclPrefs[13] ? "Allowed" : "Disallowed");
-        _response.concat("\nFob Action 1: ");
-        _response.concat((int)aclPrefs[14] ? "Allowed" : "Disallowed");
-        _response.concat("\nFob Action 2: ");
-        _response.concat((int)aclPrefs[15] ? "Allowed" : "Disallowed");
-        _response.concat("\nFob Action 3: ");
-        _response.concat((int)aclPrefs[16] ? "Allowed" : "Disallowed");
-        _response.concat("\n\n------------ NUKI OPENER CONFIG ACL ------------");
-        _response.concat("\nName: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[0] ? "Allowed" : "Disallowed");
-        _response.concat("\nLatitude: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[1] ? "Allowed" : "Disallowed");
-        _response.concat("\nLongitude: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[2] ? "Allowed" : "Disallowed");
-        _response.concat("\nPairing enabled: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[3] ? "Allowed" : "Disallowed");
-        _response.concat("\nButton enabled: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[4] ? "Allowed" : "Disallowed");
-        _response.concat("\nLED flash enabled: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[5] ? "Allowed" : "Disallowed");
-        _response.concat("\nTimezone offset: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[6] ? "Allowed" : "Disallowed");
-        _response.concat("\nDST mode: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[7] ? "Allowed" : "Disallowed");
-        _response.concat("\nFob Action 1: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[8] ? "Allowed" : "Disallowed");
-        _response.concat("\nFob Action 2: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[9] ? "Allowed" : "Disallowed");
-        _response.concat("\nFob Action 3: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[10] ? "Allowed" : "Disallowed");
-        _response.concat("\nOperating Mode: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[11] ? "Allowed" : "Disallowed");
-        _response.concat("\nAdvertising Mode: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[12] ? "Allowed" : "Disallowed");
-        _response.concat("\nTimezone ID: ");
-        _response.concat((int)basicOpenerConfigAclPrefs[13] ? "Allowed" : "Disallowed");
-        _response.concat("\nIntercom ID: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[0] ? "Allowed" : "Disallowed");
-        _response.concat("\nBUS mode Switch: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[1] ? "Allowed" : "Disallowed");
-        _response.concat("\nShort Circuit Duration: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[2] ? "Allowed" : "Disallowed");
-        _response.concat("\nEletric Strike Delay: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[3] ? "Allowed" : "Disallowed");
-        _response.concat("\nRandom Electric Strike Delay: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[4] ? "Allowed" : "Disallowed");
-        _response.concat("\nElectric Strike Duration: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[5] ? "Allowed" : "Disallowed");
-        _response.concat("\nDisable RTO after ring: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[6] ? "Allowed" : "Disallowed");
-        _response.concat("\nRTO timeout: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[7] ? "Allowed" : "Disallowed");
-        _response.concat("\nDoorbell suppression: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[8] ? "Allowed" : "Disallowed");
-        _response.concat("\nDoorbell suppression duration: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[9] ? "Allowed" : "Disallowed");
-        _response.concat("\nSound Ring: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[10] ? "Allowed" : "Disallowed");
-        _response.concat("\nSound Open: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[11] ? "Allowed" : "Disallowed");
-        _response.concat("\nSound RTO: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[12] ? "Allowed" : "Disallowed");
-        _response.concat("\nSound CM: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[13] ? "Allowed" : "Disallowed");
-        _response.concat("\nSound confirmation: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[14] ? "Allowed" : "Disallowed");
-        _response.concat("\nSound level: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[15] ? "Allowed" : "Disallowed");
-        _response.concat("\nSingle button press action: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[16] ? "Allowed" : "Disallowed");
-        _response.concat("\nDouble button press action: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[17] ? "Allowed" : "Disallowed");
-        _response.concat("\nBattery type: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[18] ? "Allowed" : "Disallowed");
-        _response.concat("\nAutomatic battery type detection: ");
-        _response.concat((int)advancedOpenerConfigAclPrefs[19] ? "Allowed" : "Disallowed");
+        response->print("\n\n------------ NUKI OPENER ACL ------------");
+        response->print("\nActivate Ring-to-Open: ");
+        response->print((int)aclPrefs[9] ? "Allowed" : "Disallowed");
+        response->print("\nDeactivate Ring-to-Open: ");
+        response->print((int)aclPrefs[10] ? "Allowed" : "Disallowed");
+        response->print("\nElectric Strike Actuation: ");
+        response->print((int)aclPrefs[11] ? "Allowed" : "Disallowed");
+        response->print("\nActivate Continuous Mode: ");
+        response->print((int)aclPrefs[12] ? "Allowed" : "Disallowed");
+        response->print("\nDeactivate Continuous Mode: ");
+        response->print((int)aclPrefs[13] ? "Allowed" : "Disallowed");
+        response->print("\nFob Action 1: ");
+        response->print((int)aclPrefs[14] ? "Allowed" : "Disallowed");
+        response->print("\nFob Action 2: ");
+        response->print((int)aclPrefs[15] ? "Allowed" : "Disallowed");
+        response->print("\nFob Action 3: ");
+        response->print((int)aclPrefs[16] ? "Allowed" : "Disallowed");
+        response->print("\n\n------------ NUKI OPENER CONFIG ACL ------------");
+        response->print("\nName: ");
+        response->print((int)basicOpenerConfigAclPrefs[0] ? "Allowed" : "Disallowed");
+        response->print("\nLatitude: ");
+        response->print((int)basicOpenerConfigAclPrefs[1] ? "Allowed" : "Disallowed");
+        response->print("\nLongitude: ");
+        response->print((int)basicOpenerConfigAclPrefs[2] ? "Allowed" : "Disallowed");
+        response->print("\nPairing enabled: ");
+        response->print((int)basicOpenerConfigAclPrefs[3] ? "Allowed" : "Disallowed");
+        response->print("\nButton enabled: ");
+        response->print((int)basicOpenerConfigAclPrefs[4] ? "Allowed" : "Disallowed");
+        response->print("\nLED flash enabled: ");
+        response->print((int)basicOpenerConfigAclPrefs[5] ? "Allowed" : "Disallowed");
+        response->print("\nTimezone offset: ");
+        response->print((int)basicOpenerConfigAclPrefs[6] ? "Allowed" : "Disallowed");
+        response->print("\nDST mode: ");
+        response->print((int)basicOpenerConfigAclPrefs[7] ? "Allowed" : "Disallowed");
+        response->print("\nFob Action 1: ");
+        response->print((int)basicOpenerConfigAclPrefs[8] ? "Allowed" : "Disallowed");
+        response->print("\nFob Action 2: ");
+        response->print((int)basicOpenerConfigAclPrefs[9] ? "Allowed" : "Disallowed");
+        response->print("\nFob Action 3: ");
+        response->print((int)basicOpenerConfigAclPrefs[10] ? "Allowed" : "Disallowed");
+        response->print("\nOperating Mode: ");
+        response->print((int)basicOpenerConfigAclPrefs[11] ? "Allowed" : "Disallowed");
+        response->print("\nAdvertising Mode: ");
+        response->print((int)basicOpenerConfigAclPrefs[12] ? "Allowed" : "Disallowed");
+        response->print("\nTimezone ID: ");
+        response->print((int)basicOpenerConfigAclPrefs[13] ? "Allowed" : "Disallowed");
+        response->print("\nIntercom ID: ");
+        response->print((int)advancedOpenerConfigAclPrefs[0] ? "Allowed" : "Disallowed");
+        response->print("\nBUS mode Switch: ");
+        response->print((int)advancedOpenerConfigAclPrefs[1] ? "Allowed" : "Disallowed");
+        response->print("\nShort Circuit Duration: ");
+        response->print((int)advancedOpenerConfigAclPrefs[2] ? "Allowed" : "Disallowed");
+        response->print("\nEletric Strike Delay: ");
+        response->print((int)advancedOpenerConfigAclPrefs[3] ? "Allowed" : "Disallowed");
+        response->print("\nRandom Electric Strike Delay: ");
+        response->print((int)advancedOpenerConfigAclPrefs[4] ? "Allowed" : "Disallowed");
+        response->print("\nElectric Strike Duration: ");
+        response->print((int)advancedOpenerConfigAclPrefs[5] ? "Allowed" : "Disallowed");
+        response->print("\nDisable RTO after ring: ");
+        response->print((int)advancedOpenerConfigAclPrefs[6] ? "Allowed" : "Disallowed");
+        response->print("\nRTO timeout: ");
+        response->print((int)advancedOpenerConfigAclPrefs[7] ? "Allowed" : "Disallowed");
+        response->print("\nDoorbell suppression: ");
+        response->print((int)advancedOpenerConfigAclPrefs[8] ? "Allowed" : "Disallowed");
+        response->print("\nDoorbell suppression duration: ");
+        response->print((int)advancedOpenerConfigAclPrefs[9] ? "Allowed" : "Disallowed");
+        response->print("\nSound Ring: ");
+        response->print((int)advancedOpenerConfigAclPrefs[10] ? "Allowed" : "Disallowed");
+        response->print("\nSound Open: ");
+        response->print((int)advancedOpenerConfigAclPrefs[11] ? "Allowed" : "Disallowed");
+        response->print("\nSound RTO: ");
+        response->print((int)advancedOpenerConfigAclPrefs[12] ? "Allowed" : "Disallowed");
+        response->print("\nSound CM: ");
+        response->print((int)advancedOpenerConfigAclPrefs[13] ? "Allowed" : "Disallowed");
+        response->print("\nSound confirmation: ");
+        response->print((int)advancedOpenerConfigAclPrefs[14] ? "Allowed" : "Disallowed");
+        response->print("\nSound level: ");
+        response->print((int)advancedOpenerConfigAclPrefs[15] ? "Allowed" : "Disallowed");
+        response->print("\nSingle button press action: ");
+        response->print((int)advancedOpenerConfigAclPrefs[16] ? "Allowed" : "Disallowed");
+        response->print("\nDouble button press action: ");
+        response->print((int)advancedOpenerConfigAclPrefs[17] ? "Allowed" : "Disallowed");
+        response->print("\nBattery type: ");
+        response->print((int)advancedOpenerConfigAclPrefs[18] ? "Allowed" : "Disallowed");
+        response->print("\nAutomatic battery type detection: ");
+        response->print((int)advancedOpenerConfigAclPrefs[19] ? "Allowed" : "Disallowed");
         if(_preferences->getBool(preference_show_secrets))
         {
             char tmp[16];
@@ -3140,58 +3649,53 @@ void WebCfgServer::buildInfoHtml()
             nukiBlePref.getBytes("secretKeyK", secretKeyKOpn, 32);
             nukiBlePref.getBytes("authorizationId", authorizationIdOpn, 4);
             nukiBlePref.end();
-            _response.concat("\n\n------------ NUKI OPENER PAIRING ------------");
-            _response.concat("\nBLE Address: ");
+            response->print("\n\n------------ NUKI OPENER PAIRING ------------");
+            response->print("\nBLE Address: ");
             for (int i = 0; i < 6; i++)
             {
                 sprintf(tmp, "%02x", currentBleAddressOpn[i]);
-                _response.concat(tmp);
+                response->print(tmp);
             }
-            _response.concat("\nSecretKeyK: ");
+            response->print("\nSecretKeyK: ");
             for (int i = 0; i < 32; i++)
             {
                 sprintf(tmp, "%02x", secretKeyKOpn[i]);
-                _response.concat(tmp);
+                response->print(tmp);
             }
-            _response.concat("\nAuthorizationId: ");
+            response->print("\nAuthorizationId: ");
             for (int i = 0; i < 4; i++)
             {
                 sprintf(tmp, "%02x", authorizationIdOpn[i]);
-                _response.concat(tmp);
+                response->print(tmp);
             }
         }
     }
 
-    _response.concat("\n\n------------ GPIO ------------");
-    _gpio->getConfigurationText(_response, _gpio->pinConfiguration());
+    response->print("\n\n------------ GPIO ------------\n");
+    String gpioStr = "";
+    _gpio->getConfigurationText(gpioStr, _gpio->pinConfiguration());
+    response->print(gpioStr);
 
-    _response.concat("
"); + response->print("
"); + request->send(response); } -void WebCfgServer::processUnpair(bool opener) +void WebCfgServer::processUnpair(AsyncWebServerRequest *request, bool opener) { - _response = ""; - if(_server.args() == 0) + String value = ""; + if(request->hasParam("CONFIRMTOKEN", true)) { - buildConfirmHtml("Confirm code is invalid.", 3); - _server.send(200, "text/html", _response); + const AsyncWebParameter* p = request->getParam("CONFIRMTOKEN", true); + if(p->value() != "") value = p->value(); + } + + if(value != _confirmCode) + { + buildConfirmHtml(request, "Confirm code is invalid.", 3, true); return; } - else - { - String key = _server.argName(0); - String value = _server.arg(0); - if(key != "CONFIRMTOKEN" || value != _confirmCode) - { - buildConfirmHtml("Confirm code is invalid.", 3); - _server.send(200, "text/html", _response); - return; - } - } - - buildConfirmHtml(opener ? "Unpairing Nuki Opener and restarting." : "Unpairing Nuki Lock and restarting.", 3); - _server.send(200, "text/html", _response); + buildConfirmHtml(request, opener ? "Unpairing Nuki Opener and restarting." : "Unpairing Nuki Lock and restarting.", 3, true); if(!opener && _nuki != nullptr) { _nuki->disableHASS(); @@ -3206,105 +3710,102 @@ void WebCfgServer::processUnpair(bool opener) restartEsp(RestartReason::DeviceUnpaired); } -void WebCfgServer::processUpdate() +void WebCfgServer::processUpdate(AsyncWebServerRequest *request) { - String key = _server.argName(0); - String key2 = _server.argName(1); - String key3 = _server.argName(2); - String value3 = _server.arg(2); - String key4 = _server.argName(3); - - if(key3 != "token" || value3 != _confirmCode) + String value = ""; + if(request->hasParam("token")) { - buildConfirmHtml("Confirm code is invalid.", 3, true); - _server.send(200, "text/html", _response); + const AsyncWebParameter* p = request->getParam("token"); + if(p->value() != "") value = p->value(); + } + + if(value != _confirmCode) + { + buildConfirmHtml(request, "Confirm code is invalid.", 3, true); return; } - if(key == "beta") + if(request->hasParam("beta")) { - if(key2 == "debug") + if(request->hasParam("debug")) { - buildConfirmHtml("Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEBUG BETA version", 2, true); + buildConfirmHtml(request, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEBUG BETA version", 2, true); _preferences->putString(preference_ota_updater_url, GITHUB_BETA_UPDATER_BINARY_URL_DBG); _preferences->putString(preference_ota_main_url, GITHUB_BETA_RELEASE_BINARY_URL_DBG); } else { - buildConfirmHtml("Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest BETA version", 2, true); + buildConfirmHtml(request, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest BETA version", 2, true); _preferences->putString(preference_ota_updater_url, GITHUB_BETA_UPDATER_BINARY_URL); _preferences->putString(preference_ota_main_url, GITHUB_BETA_RELEASE_BINARY_URL); } } - else if(key == "master") + else if(request->hasParam("master")) { - if(key2 == "debug") + if(request->hasParam("debug")) { - buildConfirmHtml("Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEBUG DEVELOPMENT version", 2, true); + buildConfirmHtml(request, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEBUG DEVELOPMENT version", 2, true); _preferences->putString(preference_ota_updater_url, GITHUB_MASTER_UPDATER_BINARY_URL_DBG); _preferences->putString(preference_ota_main_url, GITHUB_MASTER_RELEASE_BINARY_URL_DBG); } else { - buildConfirmHtml("Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEVELOPMENT version", 2, true); + buildConfirmHtml(request, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEVELOPMENT version", 2, true); _preferences->putString(preference_ota_updater_url, GITHUB_MASTER_UPDATER_BINARY_URL); _preferences->putString(preference_ota_main_url, GITHUB_MASTER_RELEASE_BINARY_URL); } } else { - if(key2 == "debug") + if(request->hasParam("debug")) { - buildConfirmHtml("Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEBUG RELEASE version", 2, true); + buildConfirmHtml(request, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest DEBUG RELEASE version", 2, true); _preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL_DBG); _preferences->putString(preference_ota_main_url, GITHUB_LATEST_UPDATER_BINARY_URL_DBG); } else { - buildConfirmHtml("Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest RELEASE version", 2, true); + buildConfirmHtml(request, "Rebooting to update Nuki Hub and Nuki Hub updater
Updating to latest RELEASE version", 2, true); _preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL); _preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL); } } - _server.send(200, "text/html", _response); waitAndProcess(true, 1000); restartEsp(RestartReason::OTAReboot); } -void WebCfgServer::processFactoryReset() +void WebCfgServer::processFactoryReset(AsyncWebServerRequest *request) { - bool resetWifi = false; - _response = ""; - if(_server.args() == 0) + String value = ""; + if(request->hasParam("CONFIRMTOKEN", true)) { - buildConfirmHtml("Confirm code is invalid.", 3); - _server.send(200, "text/html", _response); + const AsyncWebParameter* p = request->getParam("CONFIRMTOKEN", true); + if(p->value() != "") value = p->value(); + } + + bool resetWifi = false; + if(value.length() == 0 || value != _confirmCode) + { + buildConfirmHtml(request, "Confirm code is invalid.", 3, true); return; } else { - String key = _server.argName(0); - String value = _server.arg(0); - - if(key != "CONFIRMTOKEN" || value != _confirmCode) + String value2 = ""; + if(request->hasParam("WIFI", true)) { - buildConfirmHtml("Confirm code is invalid.", 3); - _server.send(200, "text/html", _response); - return; + const AsyncWebParameter* p = request->getParam("WIFI", true); + if(p->value() != "") value = p->value(); } - String key2 = _server.argName(2); - String value2 = _server.arg(2); - - if(key2 == "WIFI" && value2 == "1") + if(value2 == "1") { resetWifi = true; - buildConfirmHtml("Factory resetting Nuki Hub, unpairing Nuki Lock and Nuki Opener and resetting WiFi.", 3); + buildConfirmHtml(request, "Factory resetting Nuki Hub, unpairing Nuki Lock and Nuki Opener and resetting WiFi.", 3, true); } - else buildConfirmHtml("Factory resetting Nuki Hub, unpairing Nuki Lock and Nuki Opener.", 3); + else buildConfirmHtml(request, "Factory resetting Nuki Hub, unpairing Nuki Lock and Nuki Opener.", 3, true); } - _server.send(200, "text/html", _response); waitAndProcess(false, 2000); if(_nuki != nullptr) @@ -3320,6 +3821,7 @@ void WebCfgServer::processFactoryReset() _preferences->clear(); + #ifndef CONFIG_IDF_TARGET_ESP32H2 if(resetWifi) { wifi_config_t current_conf; @@ -3329,12 +3831,14 @@ void WebCfgServer::processFactoryReset() esp_wifi_set_config((wifi_interface_t)ESP_IF_WIFI_STA, ¤t_conf); _network->reconfigureDevice(); } + #endif waitAndProcess(false, 3000); restartEsp(RestartReason::NukiHubReset); } -void WebCfgServer::printInputField(const char *token, +void WebCfgServer::printInputField(AsyncResponseStream *response, + const char *token, const char *description, const char *value, const size_t& maxLength, @@ -3346,39 +3850,40 @@ void WebCfgServer::printInputField(const char *token, itoa(maxLength, maxLengthStr, 10); - _response.concat(""); - _response.concat(description); + response->print(""); + response->print(description); if(showLengthRestriction) { - _response.concat(" (Max. "); - _response.concat(maxLength); - _response.concat(" characters)"); + response->print(" (Max. "); + response->print(maxLength); + response->print(" characters)"); } - _response.concat(""); - _response.concat("print(""); + response->print("print(" id=\""); + response->print(id); + response->print("\""); } if(strcmp(value, "") != 0) { - _response.concat(" value=\""); - _response.concat(value); + response->print(" value=\""); + response->print(value); } - _response.concat("\" name=\""); - _response.concat(token); - _response.concat("\" size=\"25\" maxlength=\""); - _response.concat(maxLengthStr); - _response.concat("\"/>"); - _response.concat(""); + response->print("\" name=\""); + response->print(token); + response->print("\" size=\"25\" maxlength=\""); + response->print(maxLengthStr); + response->print("\"/>"); + response->print(""); } -void WebCfgServer::printInputField(const char *token, +void WebCfgServer::printInputField(AsyncResponseStream *response, + const char *token, const char *description, const int value, size_t maxLength, @@ -3386,32 +3891,55 @@ void WebCfgServer::printInputField(const char *token, { char valueStr[20]; itoa(value, valueStr, 10); - printInputField(token, description, valueStr, maxLength, id); + printInputField(response, token, description, valueStr, maxLength, id); } -void WebCfgServer::printCheckBox(const char *token, const char *description, const bool value, const char *htmlClass) +void WebCfgServer::printCheckBox(AsyncResponseStream *response, const char *token, const char *description, const bool value, const char *htmlClass) { - _response.concat(""); - _response.concat(description); - _response.concat(""); + response->print(""); + response->print(description); + response->print(""); - _response.concat(""); + response->print("print(token); + response->print("\" value=\"0\""); + response->print("/>"); - _response.concat("print("print(token); - _response.concat("\" class=\""); - _response.concat(htmlClass); + response->print("\" class=\""); + response->print(htmlClass); - _response.concat("\" value=\"1\""); - _response.concat(value ? " checked=\"checked\"" : ""); - _response.concat("/>"); + response->print("\" value=\"1\""); + response->print(value ? " checked=\"checked\"" : ""); + response->print("/>"); } -void WebCfgServer::printTextarea(const char *token, +void WebCfgServer::printCheckBox(String &partString, const char *token, const char *description, const bool value, const char *htmlClass) +{ + partString.concat(""); + partString.concat(description); + partString.concat(""); + + partString.concat(""); + + partString.concat(""); +} + +void WebCfgServer::printTextarea(AsyncResponseStream *response, + const char *token, const char *description, const char *value, const size_t& maxLength, @@ -3422,111 +3950,106 @@ void WebCfgServer::printTextarea(const char *token, itoa(maxLength, maxLengthStr, 10); - _response.concat(""); - _response.concat(description); + response->print(""); + response->print(description); if(showLengthRestriction) { - _response.concat(" (Max. "); - _response.concat(maxLength); - _response.concat(" characters)"); + response->print(" (Max. "); + response->print(maxLength); + response->print(" characters)"); } - _response.concat(""); - _response.concat(" "); - _response.concat(""); + response->print(" name=\""); + response->print(token); + response->print("\" maxlength=\""); + response->print(maxLengthStr); + response->print("\">"); + response->print(value); + response->print(""); + response->print(""); } -void WebCfgServer::printDropDown(const char *token, const char *description, const String preselectedValue, const std::vector> options) +void WebCfgServer::printDropDown(AsyncResponseStream *response, const char *token, const char *description, const String preselectedValue, const std::vector> options, const String className) { - _response.concat(""); - _response.concat(description); - _response.concat(""); + response->print(""); + response->print(description); + response->print(""); - _response.concat("print(""); - _response.concat(""); + response->print(""); + response->print(""); } -void WebCfgServer::buildNavigationButton(const char *caption, const char *targetPath, const char* labelText) +void WebCfgServer::buildNavigationButton(AsyncResponseStream *response, const char *caption, const char *targetPath, const char* labelText) { - _response.concat("
"); - _response.concat(" "); - _response.concat(labelText); - _response.concat("
"); + response->print("
print(targetPath); + response->print("\">"); + response->print(" "); + response->print(labelText); + response->print("
"); } -void WebCfgServer::buildNavigationMenuEntry(const char *title, const char *targetPath, const char* warningMessage) +void WebCfgServer::buildNavigationMenuEntry(AsyncResponseStream *response, const char *title, const char *targetPath, const char* warningMessage) { - _response.concat(""); - _response.concat("
  • "); - _response.concat(title); + response->print("print(targetPath); + response->print("\">"); + response->print("
  • "); + response->print(title); if(strcmp(warningMessage, "") != 0){ - _response.concat(""); - _response.concat(warningMessage); - _response.concat(""); + response->print(""); + response->print(warningMessage); + response->print(""); } - _response.concat("
  • "); + response->print(""); } -void WebCfgServer::printParameter(const char *description, const char *value, const char *link, const char *id) +void WebCfgServer::printParameter(AsyncResponseStream *response, const char *description, const char *value, const char *link, const char *id) { - _response.concat(""); - _response.concat(""); - _response.concat(description); - _response.concat(""); - if(strcmp(id, "") == 0) _response.concat(""); + response->print(""); + response->print(""); + response->print(description); + response->print(""); + if(strcmp(id, "") == 0) response->print(""); else { - _response.concat(""); + response->print("print(id); + response->print("\">"); } - if(strcmp(link, "") == 0) _response.concat(value); + if(strcmp(link, "") == 0) response->print(value); else { - _response.concat(" "); - _response.concat(value); - _response.concat(""); + response->print("print(link); + response->print("\"> "); + response->print(value); + response->print(""); } - _response.concat(""); - _response.concat(""); + response->print(""); + response->print(""); } @@ -3537,15 +4060,47 @@ const std::vector> WebCfgServer::getNetworkDetectionOp options.push_back(std::make_pair("1", "Wi-Fi only")); options.push_back(std::make_pair("2", "Generic W5500")); options.push_back(std::make_pair("3", "M5Stack Atom POE (W5500)")); + options.push_back(std::make_pair("10", "M5Stack Atom POE S3 (W5500)")); options.push_back(std::make_pair("4", "Olimex ESP32-POE / ESP-POE-ISO")); options.push_back(std::make_pair("5", "WT32-ETH01")); options.push_back(std::make_pair("6", "M5STACK PoESP32 Unit")); options.push_back(std::make_pair("7", "LilyGO T-ETH-POE")); options.push_back(std::make_pair("8", "GL-S10")); + options.push_back(std::make_pair("9", "ETH01-Evo")); + options.push_back(std::make_pair("11", "Custom LAN module")); return options; } +const std::vector> WebCfgServer::getNetworkCustomPHYOptions() const +{ + std::vector> options; + options.push_back(std::make_pair("0", "Disabled")); + options.push_back(std::make_pair("1", "W5500")); + options.push_back(std::make_pair("2", "DN9051")); + options.push_back(std::make_pair("3", "KSZ8851SNL")); + #if defined(CONFIG_IDF_TARGET_ESP32) + options.push_back(std::make_pair("4", "LAN8720")); + options.push_back(std::make_pair("5", "RTL8201")); + options.push_back(std::make_pair("6", "TLK110")); + options.push_back(std::make_pair("7", "DP83848")); + options.push_back(std::make_pair("8", "KSZ8041")); + options.push_back(std::make_pair("9", "KSZ8081")); + #endif + + return options; +} +#if defined(CONFIG_IDF_TARGET_ESP32) +const std::vector> WebCfgServer::getNetworkCustomCLKOptions() const +{ + std::vector> options; + options.push_back(std::make_pair("0", "GPIO0 IN")); + options.push_back(std::make_pair("2", "GPIO16 OUT")); + options.push_back(std::make_pair("3", "GPIO17 OUT")); + return options; +} +#endif + const std::vector> WebCfgServer::getGpioOptions() const { std::vector> options; diff --git a/src/WebCfgServer.h b/src/WebCfgServer.h index 413bb1f..770575e 100644 --- a/src/WebCfgServer.h +++ b/src/WebCfgServer.h @@ -1,8 +1,11 @@ #pragma once #include -#include -#include "Ota.h" +#include +#include +#include +#include "esp_ota_ops.h" +#include "Config.h" #ifndef NUKI_HUB_UPDATER #include "NukiWrapper.h" @@ -34,72 +37,82 @@ class WebCfgServer { public: #ifndef NUKI_HUB_UPDATER - WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, NukiNetwork* network, Gpio* gpio, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal, uint8_t partitionType); + WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, NukiNetwork* network, Gpio* gpio, Preferences* preferences, bool allowRestartToPortal, uint8_t partitionType, AsyncWebServer* asyncServer); #else - WebCfgServer(NukiNetwork* network, EthServer* ethServer, Preferences* preferences, bool allowRestartToPortal, uint8_t partitionType); + WebCfgServer(NukiNetwork* network, Preferences* preferences, bool allowRestartToPortal, uint8_t partitionType, AsyncWebServer* asyncServer); #endif ~WebCfgServer() = default; void initialize(); - void update(); private: #ifndef NUKI_HUB_UPDATER - void sendSettings(); - bool processArgs(String& message); - bool processImport(String& message); - void processGpioArgs(); - void buildHtml(); - void buildAccLvlHtml(); - void buildCredHtml(); - void buildImportExportHtml(); - void buildMqttConfigHtml(); - void buildStatusHtml(); - void buildAdvancedConfigHtml(); - void buildNukiConfigHtml(); - void buildGpioConfigHtml(); - void buildConfigureWifiHtml(); - void buildInfoHtml(); - void processUnpair(bool opener); - void processUpdate(); - void processFactoryReset(); - void printInputField(const char* token, const char* description, const char* value, const size_t& maxLength, const char* id, const bool& isPassword = false, const bool& showLengthRestriction = false); - void printInputField(const char* token, const char* description, const int value, size_t maxLength, const char* id); - void printCheckBox(const char* token, const char* description, const bool value, const char* htmlClass); - void printTextarea(const char *token, const char *description, const char *value, const size_t& maxLength, const bool& enabled = true, const bool& showLengthRestriction = false); - void printDropDown(const char *token, const char *description, const String preselectedValue, std::vector> options); - void buildNavigationButton(const char* caption, const char* targetPath, const char* labelText = ""); - void buildNavigationMenuEntry(const char *title, const char *targetPath, const char* warningMessage = ""); + void sendSettings(AsyncWebServerRequest *request); + bool processArgs(AsyncWebServerRequest *request, String& message); + bool processImport(AsyncWebServerRequest *request, String& message); + void processGpioArgs(AsyncWebServerRequest *request); + void buildHtml(AsyncWebServerRequest *request); + void buildAccLvlHtml(AsyncWebServerRequest *request, int aclPart = 0); + void buildCredHtml(AsyncWebServerRequest *request); + void buildImportExportHtml(AsyncWebServerRequest *request); + void buildMqttConfigHtml(AsyncWebServerRequest *request); + void buildStatusHtml(AsyncWebServerRequest *request); + void buildAdvancedConfigHtml(AsyncWebServerRequest *request); + void buildNukiConfigHtml(AsyncWebServerRequest *request); + void buildGpioConfigHtml(AsyncWebServerRequest *request); + #ifndef CONFIG_IDF_TARGET_ESP32H2 + void buildConfigureWifiHtml(AsyncWebServerRequest *request); + #endif + void buildInfoHtml(AsyncWebServerRequest *request); + void buildCustomNetworkConfigHtml(AsyncWebServerRequest *request); + void processUnpair(AsyncWebServerRequest *request, bool opener); + void processUpdate(AsyncWebServerRequest *request); + void processFactoryReset(AsyncWebServerRequest *request); + void printInputField(AsyncResponseStream *response, const char* token, const char* description, const char* value, const size_t& maxLength, const char* id, const bool& isPassword = false, const bool& showLengthRestriction = false); + void printInputField(AsyncResponseStream *response, const char* token, const char* description, const int value, size_t maxLength, const char* id); + void printCheckBox(AsyncResponseStream *response, const char* token, const char* description, const bool value, const char* htmlClass); + void printCheckBox(String &partString, const char* token, const char* description, const bool value, const char* htmlClass); + void printTextarea(AsyncResponseStream *response, const char *token, const char *description, const char *value, const size_t& maxLength, const bool& enabled = true, const bool& showLengthRestriction = false); + void printDropDown(AsyncResponseStream *response, const char *token, const char *description, const String preselectedValue, std::vector> options, const String className); + void buildNavigationButton(AsyncResponseStream *response, const char* caption, const char* targetPath, const char* labelText = ""); + void buildNavigationMenuEntry(AsyncResponseStream *response, const char *title, const char *targetPath, const char* warningMessage = ""); + void partAccLvlHtml(String &partString, int aclPart); const std::vector> getNetworkDetectionOptions() const; const std::vector> getGpioOptions() const; + const std::vector> getNetworkCustomPHYOptions() const; + #if defined(CONFIG_IDF_TARGET_ESP32) + const std::vector> getNetworkCustomCLKOptions() const; + #endif + String getPreselectionForGpio(const uint8_t& pin); String pinStateToString(uint8_t value); - void printParameter(const char* description, const char* value, const char *link = "", const char *id = ""); + void printParameter(AsyncResponseStream *response, const char* description, const char* value, const char *link = "", const char *id = ""); NukiWrapper* _nuki = nullptr; NukiOpenerWrapper* _nukiOpener = nullptr; Gpio* _gpio = nullptr; bool _pinsConfigured = false; bool _brokerConfigured = false; + bool _rebootRequired = false; #endif String generateConfirmCode(); String _confirmCode = "----"; - void buildConfirmHtml(const String &message, uint32_t redirectDelay = 5, bool redirect = false); - void buildOtaHtml(bool errored, bool debug = false); - void buildOtaCompletedHtml(); - void sendCss(); - void sendFavicon(); - void buildHtmlHeader(String additionalHeader = ""); + void buildConfirmHtml(AsyncWebServerRequest *request, const String &message, uint32_t redirectDelay = 5, bool redirect = false); + void buildOtaHtml(AsyncWebServerRequest *request, bool debug = false); + void buildOtaCompletedHtml(AsyncWebServerRequest *request); + void sendCss(AsyncWebServerRequest *request); + void sendFavicon(AsyncWebServerRequest *request); + void buildHtmlHeader(AsyncResponseStream *response, String additionalHeader = ""); void waitAndProcess(const bool blocking, const uint32_t duration); - void handleOtaUpload(); - - WebServer _server; + void handleOtaUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); + void printProgress(size_t prg, size_t sz); + + AsyncWebServer* _asyncServer = nullptr; NukiNetwork* _network = nullptr; Preferences* _preferences = nullptr; - Ota _ota; bool _hasCredentials = false; char _credUser[31] = {0}; @@ -108,7 +121,7 @@ private: uint8_t _partitionType = 0; uint32_t _transferredSize = 0; int64_t _otaStartTs = 0; + size_t _otaContentLen = 0; String _hostname; - String _response; bool _enabled = true; }; diff --git a/src/main.cpp b/src/main.cpp index dbd6fbe..fba9904 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,16 +1,14 @@ #define IS_VALID_DETECT 0xa00ab00bc00bd00d; #include "Arduino.h" -#include "hardware/W5500EthServer.h" -#include "hardware/WifiEthServer.h" #include "esp_crt_bundle.h" #include "esp_ota_ops.h" #include "esp_http_client.h" #include "esp_https_ota.h" #include +#include "Config.h" #ifndef NUKI_HUB_UPDATER -#include "Config.h" #include "NukiWrapper.h" #include "NukiNetworkLock.h" #include "PresenceDetection.h" @@ -28,7 +26,6 @@ #include #include -AsyncWebServer webserialserver(81); char log_print_buffer[1024]; NukiNetworkLock* networkLock = nullptr; @@ -53,7 +50,6 @@ int64_t restartTs = ((2^64) - (5 * 1000 * 60000)) / 1000; #include "../../src/WebCfgServer.h" #include "../../src/Logger.h" #include "../../src/PreferencesKeys.h" -#include "../../src/Config.h" #include "../../src/RestartReason.h" #include "../../src/NukiNetwork.h" @@ -61,16 +57,17 @@ int64_t restartTs = 10 * 1000 * 60000; #endif +AsyncWebServer* asyncServer = nullptr; NukiNetwork* network = nullptr; WebCfgServer* webCfgServer = nullptr; Preferences* preferences = nullptr; -EthServer* ethServer = nullptr; RTC_NOINIT_ATTR int restartReason; RTC_NOINIT_ATTR uint64_t restartReasonValidDetect; RTC_NOINIT_ATTR bool rebuildGpioRequested; RTC_NOINIT_ATTR uint64_t bootloopValidDetect; RTC_NOINIT_ATTR int8_t bootloopCounter; +RTC_NOINIT_ATTR bool forceEnableWebServer; bool restartReason_isValid; RestartReason currentRestartReason = RestartReason::NotApplicable; @@ -109,7 +106,7 @@ int _log_vprintf(const char *fmt, va_list args) { void setReroute(){ esp_log_set_vprintf(_log_vprintf); if(preferences->getBool(preference_mqtt_log_enabled)) esp_log_level_set("*", ESP_LOG_INFO); - else + else { esp_log_level_set("*", ESP_LOG_DEBUG); esp_log_level_set("nvs", ESP_LOG_INFO); @@ -122,7 +119,6 @@ void networkTask(void *pvParameters) { int64_t networkLoopTs = 0; bool secrets = preferences->getBool(preference_show_secrets, false); - bool webEnabled = preferences->getBool(preference_webserver_enabled, true); bool reroute = true; while(true) @@ -149,9 +145,6 @@ void networkTask(void *pvParameters) } #endif if(connected && openerEnabled) networkOpener->update(); - if(preferences->getBool(preference_webserver_enabled, true)) webCfgServer->update(); - #else - webCfgServer->update(); #endif if((esp_timer_get_time() / 1000) - networkLoopTs > 120000) @@ -328,20 +321,33 @@ void otaTask(void *pvParameter) }; Log->print(F("Attempting to download update from ")); Log->println(config.url); - esp_err_t ret = esp_https_ota(&ota_config); - if (ret == ESP_OK) { - Log->println("OTA Succeeded, Rebooting..."); - esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL)); - restartEsp(RestartReason::OTACompleted); - } else { - Log->println("Firmware upgrade failed"); - restartEsp(RestartReason::OTAAborted); - } - while (1) { - vTaskDelay(1000 / portTICK_PERIOD_MS); - } - esp_task_wdt_reset(); + int retryMax = 3; + int retryCount = 0; + + while (retryCount <= retryMax) + { + esp_err_t ret = esp_https_ota(&ota_config); + if (ret == ESP_OK) { + Log->println("OTA Succeeded, Rebooting..."); + esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL)); + restartEsp(RestartReason::OTACompleted); + break; + } else { + Log->println("Firmware upgrade failed, retrying in 5 seconds"); + retryCount++; + esp_task_wdt_reset(); + delay(5000); + continue; + } + while (1) { + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } + + Log->println("Firmware upgrade failed, restarting"); + esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL)); + restartEsp(RestartReason::OTAAborted); } void setupTasks(bool ota) @@ -370,22 +376,6 @@ void setupTasks(bool ota) } } -void initEthServer(const NetworkDeviceType device) -{ - switch (device) - { - case NetworkDeviceType::W5500: - ethServer = new W5500EthServer(80); - break; - case NetworkDeviceType::WiFi: - ethServer = new WifiEthServer(80); - break; - default: - ethServer = new WifiEthServer(80); - break; - } -} - void setup() { esp_log_level_set("*", ESP_LOG_ERROR); @@ -395,11 +385,13 @@ void setup() preferences = new Preferences(); preferences->begin("nukihub", false); bool firstStart = initPreferences(preferences); - + bool doOta = false; uint8_t partitionType = checkPartition(); initializeRestartReason(); + if((partitionType==1 && preferences->getString(preference_ota_updater_url, "").length() > 0) || (partitionType==2 && preferences->getString(preference_ota_main_url, "").length() > 0)) doOta = true; + #ifndef NUKI_HUB_UPDATER if(preferences->getBool(preference_enable_bootloop_reset, false)) { @@ -408,17 +400,31 @@ void setup() #endif #ifdef NUKI_HUB_UPDATER - Log->print(F("Nuki Hub OTA version ")); Log->println(NUKI_HUB_VERSION); - Log->print(F("Nuki Hub OTA build ")); Log->println(NUKI_HUB_BUILD); + Log->print(F("Nuki Hub OTA version ")); + Log->println(NUKI_HUB_VERSION); + Log->print(F("Nuki Hub OTA build ")); + Log->println(); + + if(preferences->getString(preference_updater_version, "") != NUKI_HUB_VERSION) preferences->putString(preference_updater_version, NUKI_HUB_VERSION); + if(preferences->getString(preference_updater_build, "") != NUKI_HUB_BUILD) preferences->putString(preference_updater_build, NUKI_HUB_BUILD); + if(preferences->getString(preference_updater_date, "") != NUKI_HUB_DATE) preferences->putString(preference_updater_date, NUKI_HUB_DATE); network = new NukiNetwork(preferences); network->initialize(); - initEthServer(network->networkDeviceType()); - webCfgServer = new WebCfgServer(network, ethServer, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType); - webCfgServer->initialize(); + + if(!doOta) + { + asyncServer = new AsyncWebServer(80); + webCfgServer = new WebCfgServer(network, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, asyncServer); + webCfgServer->initialize(); + asyncServer->onNotFound([](AsyncWebServerRequest* request) { request->redirect("/"); }); + asyncServer->begin(); + } #else - Log->print(F("Nuki Hub version ")); Log->println(NUKI_HUB_VERSION); - Log->print(F("Nuki Hub build ")); Log->println(NUKI_HUB_BUILD); + Log->print(F("Nuki Hub version ")); + Log->println(NUKI_HUB_VERSION); + Log->print(F("Nuki Hub build ")); + Log->println(NUKI_HUB_BUILD); uint32_t devIdOpener = preferences->getUInt(preference_device_id_opener); @@ -470,8 +476,6 @@ void setup() networkOpener->initialize(); } - initEthServer(network->networkDeviceType()); - Log->println(lockEnabled ? F("Nuki Lock enabled") : F("Nuki Lock disabled")); if(lockEnabled) { @@ -486,20 +490,33 @@ void setup() nukiOpener->initialize(); } - if(preferences->getBool(preference_webserver_enabled, true)) + if(forceEnableWebServer || preferences->getBool(preference_webserver_enabled, true) || preferences->getBool(preference_webserial_enabled, false)) { - webCfgServer = new WebCfgServer(nuki, nukiOpener, network, gpio, ethServer, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType); - webCfgServer->initialize(); - } + if(!doOta) + { + asyncServer = new AsyncWebServer(80); - WebSerial.setAuthentication(preferences->getString(preference_cred_user), preferences->getString(preference_cred_password)); - WebSerial.begin(&webserialserver); - WebSerial.setBuffer(1024); - webserialserver.onNotFound([](AsyncWebServerRequest* request) { request->redirect("/webserial"); }); - webserialserver.begin(); + if(forceEnableWebServer || preferences->getBool(preference_webserver_enabled, true)) + { + webCfgServer = new WebCfgServer(nuki, nukiOpener, network, gpio, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, asyncServer); + webCfgServer->initialize(); + asyncServer->onNotFound([](AsyncWebServerRequest* request) { request->redirect("/"); }); + } + else asyncServer->onNotFound([](AsyncWebServerRequest* request) { request->redirect("/webserial"); }); + + if(preferences->getBool(preference_webserial_enabled, false)) + { + WebSerial.setAuthentication(preferences->getString(preference_cred_user), preferences->getString(preference_cred_password)); + WebSerial.begin(asyncServer); + WebSerial.setBuffer(1024); + } + + asyncServer->begin(); + } + } #endif - if((partitionType==1 && preferences->getString(preference_ota_updater_url, "").length() > 0) || (partitionType==2 && preferences->getString(preference_ota_main_url, "").length() > 0)) setupTasks(true); + if(doOta) setupTasks(true); else setupTasks(false); } diff --git a/src/networkDevices/ClientSyncW5500.cpp b/src/networkDevices/ClientSyncW5500.cpp deleted file mode 100644 index 8158ec5..0000000 --- a/src/networkDevices/ClientSyncW5500.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* -Copyright (c) 2022 Bert Melis. All rights reserved. - -This work is licensed under the terms of the MIT license. -For a copy, see or -the LICENSE file. -*/ - -#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) - -#include "ClientSyncW5500.h" -#include // socket options - -namespace espMqttClientInternals { - - ClientSyncW5500::ClientSyncW5500() - : client() { - // empty - } - - bool ClientSyncW5500::connect(IPAddress ip, uint16_t port) { - bool ret = client.connect(ip, port); // implicit conversion of return code int --> bool - if (ret) { -#if defined(ARDUINO_ARCH_ESP8266) - client.setNoDelay(true); -#elif defined(ARDUINO_ARCH_ESP32) - // Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure (for consistency also here) - int val = true; - - // TODO -// client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int)); -#endif - } - return ret; - } - - bool ClientSyncW5500::connect(const char* host, uint16_t port) { - bool ret = client.connect(host, port); // implicit conversion of return code int --> bool - if (ret) { -#if defined(ARDUINO_ARCH_ESP8266) - client.setNoDelay(true); -#elif defined(ARDUINO_ARCH_ESP32) - // Set TCP option directly to bypass lack of working setNoDelay for WiFiClientSecure (for consistency also here) - int val = true; - - // TODO -// client.setSocketOption(IPPROTO_TCP, TCP_NODELAY, &val, sizeof(int)); -#endif - } - return ret; - } - - size_t ClientSyncW5500::write(const uint8_t* buf, size_t size) { - return client.write(buf, size); - } - - int ClientSyncW5500::read(uint8_t* buf, size_t size) { - return client.read(buf, size); - } - - void ClientSyncW5500::stop() { - client.stop(); - } - - bool ClientSyncW5500::connected() { - return client.connected(); - } - - bool ClientSyncW5500::disconnected() { - return !client.connected(); - } - -} // namespace espMqttClientInternals - -#endif diff --git a/src/networkDevices/ClientSyncW5500.h b/src/networkDevices/ClientSyncW5500.h deleted file mode 100644 index 673d1f6..0000000 --- a/src/networkDevices/ClientSyncW5500.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) - -#include "Transport/Transport.h" -#include "EthernetClient.h" - -namespace espMqttClientInternals { - - class ClientSyncW5500 : public Transport { - public: - ClientSyncW5500(); - bool connect(IPAddress ip, uint16_t port) override; - bool connect(const char* host, uint16_t port) override; - size_t write(const uint8_t* buf, size_t size) override; - int read(uint8_t* buf, size_t size) override; - void stop() override; - bool connected() override; - bool disconnected() override; - EthernetClient client; - }; - -} // namespace espMqttClientInternals - -#endif \ No newline at end of file diff --git a/src/networkDevices/DM9051Definitions.h b/src/networkDevices/DM9051Definitions.h new file mode 100644 index 0000000..4a3505b --- /dev/null +++ b/src/networkDevices/DM9051Definitions.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#define ETH_PHY_TYPE_DM9051 ETH_PHY_DM9051 +#define ETH_PHY_ADDR_ETH01EVO 1 +#define ETH_PHY_CS_ETH01EVO 9 +#define ETH_PHY_IRQ_ETH01EVO 8 +#define ETH_PHY_RST_ETH01EVO 6 +#define ETH_PHY_SPI_SCK_ETH01EVO 7 +#define ETH_PHY_SPI_MISO_ETH01EVO 3 +#define ETH_PHY_SPI_MOSI_ETH01EVO 10 + diff --git a/src/networkDevices/EthLan8720Device.cpp b/src/networkDevices/EthLan8720Device.cpp deleted file mode 100644 index b4274ad..0000000 --- a/src/networkDevices/EthLan8720Device.cpp +++ /dev/null @@ -1,179 +0,0 @@ -//#define ETH_CLK_MODE ETH_CLOCK_GPIO17_OUT -//#define ETH_PHY_POWER 12 - -#include -#include -#include "EthLan8720Device.h" -#include "../PreferencesKeys.h" -#include "../Logger.h" -#ifndef NUKI_HUB_UPDATER -#include "../MqttTopics.h" -#include "espMqttClient.h" -#endif -#include "../RestartReason.h" - -EthLan8720Device::EthLan8720Device(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration, const std::string& deviceName, uint8_t phy_addr, int power, int mdc, int mdio, eth_phy_type_t ethtype, eth_clock_mode_t clock_mode, bool use_mac_from_efuse) -: NetworkDevice(hostname, ipConfiguration), - _deviceName(deviceName), - _phy_addr(phy_addr), - _power(power), - _mdc(mdc), - _mdio(mdio), - _type(ethtype), - _clock_mode(clock_mode), - _use_mac_from_efuse(use_mac_from_efuse) -{ - _restartOnDisconnect = preferences->getBool(preference_restart_on_disconnect); - - #ifndef NUKI_HUB_UPDATER - size_t caLength = preferences->getString(preference_mqtt_ca, _ca, TLS_CA_MAX_SIZE); - size_t crtLength = preferences->getString(preference_mqtt_crt, _cert, TLS_CERT_MAX_SIZE); - size_t keyLength = preferences->getString(preference_mqtt_key, _key, TLS_KEY_MAX_SIZE); - - _useEncryption = caLength > 1; // length is 1 when empty - - if(_useEncryption) - { - Log->println(F("MQTT over TLS.")); - Log->println(_ca); - _mqttClientSecure = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO); - _mqttClientSecure->setCACert(_ca); - if(crtLength > 1 && keyLength > 1) // length is 1 when empty - { - Log->println(F("MQTT with client certificate.")); - Log->println(_cert); - Log->println(_key); - _mqttClientSecure->setCertificate(_cert); - _mqttClientSecure->setPrivateKey(_key); - } - } else - { - Log->println(F("MQTT without TLS.")); - _mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO); - } - - if(preferences->getBool(preference_mqtt_log_enabled, false) || preferences->getBool(preference_webserial_enabled, false)) - { - MqttLoggerMode mode; - - if(preferences->getBool(preference_mqtt_log_enabled, false) && preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::MqttAndSerialAndWeb; - else if (preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::SerialAndWeb; - else mode = MqttLoggerMode::MqttAndSerial; - - _path = new char[200]; - memset(_path, 0, sizeof(_path)); - - String pathStr = preferences->getString(preference_mqtt_lock_path); - pathStr.concat(mqtt_topic_log); - strcpy(_path, pathStr.c_str()); - Log = new MqttLogger(*getMqttClient(), _path, mode); - } - #endif -} - -const String EthLan8720Device::deviceName() const -{ - return _deviceName.c_str(); -} - -void EthLan8720Device::initialize() -{ - delay(250); - - WiFi.setHostname(_hostname.c_str()); - - #if CONFIG_IDF_TARGET_ESP32 - _hardwareInitialized = ETH.begin(_type, _phy_addr, _mdc, _mdio, _power, _clock_mode); - #else - _hardwareInitialized = false; - #endif - - ETH.setHostname(_hostname.c_str()); - if(!_ipConfiguration->dhcpEnabled()) - { - ETH.config(_ipConfiguration->ipAddress(), _ipConfiguration->defaultGateway(), _ipConfiguration->subnet(), _ipConfiguration->dnsServer()); - } - - WiFi.onEvent([&](WiFiEvent_t event, WiFiEventInfo_t info) - { - if(event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) - { - onDisconnected(); - } - }); - - if(_ipConfiguration->dhcpEnabled()) - { - waitForIpAddressWithTimeout(); - } -} - -void EthLan8720Device::waitForIpAddressWithTimeout() -{ - int count = 100; - - Log->print(F("LAN8720: Obtaining IP address via DHCP")); - - while(count > 0 && localIP().equals("0.0.0.0")) - { - Log->print(F(".")); - count--; - delay(100); - } - - if(localIP().equals("0.0.0.0")) - { - Log->println(F(" Failed")); - } - else - { - Log->println(localIP()); - } -} - -void EthLan8720Device::reconfigure() -{ - delay(200); - restartEsp(RestartReason::ReconfigureLAN8720); -} - -bool EthLan8720Device::supportsEncryption() -{ - return true; -} - -bool EthLan8720Device::isConnected() -{ - return ETH.linkUp(); -} - -ReconnectStatus EthLan8720Device::reconnect(bool force) -{ - if(!_hardwareInitialized) - { - return ReconnectStatus::CriticalFailure; - } - delay(200); - return isConnected() ? ReconnectStatus::Success : ReconnectStatus::Failure; -} - -void EthLan8720Device::onDisconnected() -{ - if(_restartOnDisconnect && ((esp_timer_get_time() / 1000) > 60000)) restartEsp(RestartReason::RestartOnDisconnectWatchdog); - reconnect(); -} - -int8_t EthLan8720Device::signalStrength() -{ - return -1; -} - -String EthLan8720Device::localIP() -{ - return ETH.localIP().toString(); -} - -String EthLan8720Device::BSSIDstr() -{ - return ""; -} \ No newline at end of file diff --git a/src/networkDevices/EthLan8720Device.h b/src/networkDevices/EthLan8720Device.h deleted file mode 100644 index 12a4197..0000000 --- a/src/networkDevices/EthLan8720Device.h +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#ifndef CONFIG_IDF_TARGET_ESP32 - typedef enum { - ETH_CLOCK_GPIO0_IN = 0, - ETH_CLOCK_GPIO16_OUT = 2, - ETH_CLOCK_GPIO17_OUT = 3 - } eth_clock_mode_t; - - #define ETH_PHY_TYPE ETH_PHY_MAX -#else - #define ETH_PHY_TYPE ETH_PHY_LAN8720 -#endif - -#define ETH_CLK_MODE ETH_CLOCK_GPIO0_IN -#define ETH_PHY_ADDR 0 -#define ETH_PHY_MDC 23 -#define ETH_PHY_MDIO 18 -#define ETH_PHY_POWER -1 -#define ETH_RESET_PIN 1 - -#include -#include -#include -#include "NetworkDevice.h" -#ifndef NUKI_HUB_UPDATER -#include "espMqttClient.h" -#endif -#include - -class EthLan8720Device : public NetworkDevice -{ - -public: - EthLan8720Device(const String& hostname, - Preferences* preferences, - const IPConfiguration* ipConfiguration, - const std::string& deviceName, - uint8_t phy_addr = ETH_PHY_ADDR, - int power = ETH_PHY_POWER, - int mdc = ETH_PHY_MDC, - int mdio = ETH_PHY_MDIO, - eth_phy_type_t ethtype = ETH_PHY_TYPE, - eth_clock_mode_t clock_mode = ETH_CLK_MODE, - bool use_mac_from_efuse = false); - - const String deviceName() const override; - - virtual void initialize(); - virtual void reconfigure(); - virtual ReconnectStatus reconnect(bool force = false); - bool supportsEncryption() override; - - virtual bool isConnected(); - - int8_t signalStrength() override; - - String localIP() override; - String BSSIDstr() override; - -private: - void onDisconnected(); - void waitForIpAddressWithTimeout(); - - bool _restartOnDisconnect = false; - bool _startAp = false; - char* _path; - bool _hardwareInitialized = false; - - const std::string _deviceName; - uint8_t _phy_addr; - int _power; - int _mdc; - int _mdio; - eth_phy_type_t _type; - eth_clock_mode_t _clock_mode; - bool _use_mac_from_efuse; - #ifndef NUKI_HUB_UPDATER - char _ca[TLS_CA_MAX_SIZE] = {0}; - char _cert[TLS_CERT_MAX_SIZE] = {0}; - char _key[TLS_KEY_MAX_SIZE] = {0}; - #endif -}; \ No newline at end of file diff --git a/src/networkDevices/EthernetDevice.cpp b/src/networkDevices/EthernetDevice.cpp new file mode 100644 index 0000000..608a13d --- /dev/null +++ b/src/networkDevices/EthernetDevice.cpp @@ -0,0 +1,221 @@ +#include "EthernetDevice.h" +#include "../PreferencesKeys.h" +#include "../Logger.h" +#ifndef NUKI_HUB_UPDATER +#include "../MqttTopics.h" +#include "espMqttClient.h" +#endif +#include "../RestartReason.h" + +EthernetDevice::EthernetDevice(const String& hostname, Preferences* preferences, const IPConfiguration* ipConfiguration, const std::string& deviceName, uint8_t phy_addr, int power, int mdc, int mdio, eth_phy_type_t ethtype, eth_clock_mode_t clock_mode, bool use_mac_from_efuse) +: NetworkDevice(hostname, ipConfiguration), + _deviceName(deviceName), + _phy_addr(phy_addr), + _power(power), + _mdc(mdc), + _mdio(mdio), + _type(ethtype), + _clock_mode(clock_mode), + _use_mac_from_efuse(use_mac_from_efuse), + _useSpi(false), + _preferences(preferences) +{ + init(); +} + +EthernetDevice::EthernetDevice(const String &hostname, + Preferences *preferences, + const IPConfiguration *ipConfiguration, + const std::string &deviceName, + uint8_t phy_addr, + int cs, + int irq, + int rst, + int spi_sck, + int spi_miso, + int spi_mosi, + uint8_t spi_freq_mhz, + eth_phy_type_t ethtype) + : NetworkDevice(hostname, ipConfiguration), + _deviceName(deviceName), + _phy_addr(phy_addr), + _cs(cs), + _irq(irq), + _rst(rst), + _spi_sck(spi_sck), + _spi_miso(spi_miso), + _spi_mosi(spi_mosi), + _spi_freq_mhz(spi_freq_mhz), + _type(ethtype), + _useSpi(true), + _preferences(preferences) +{ + init(); +} + +void EthernetDevice::init() +{ + _restartOnDisconnect = _preferences->getBool(preference_restart_on_disconnect); + +#ifndef NUKI_HUB_UPDATER + size_t caLength = _preferences->getString(preference_mqtt_ca, _ca, TLS_CA_MAX_SIZE); + size_t crtLength = _preferences->getString(preference_mqtt_crt, _cert, TLS_CERT_MAX_SIZE); + size_t keyLength = _preferences->getString(preference_mqtt_key, _key, TLS_KEY_MAX_SIZE); + + _useEncryption = caLength > 1; // length is 1 when empty + + if(_useEncryption) + { + Log->println(F("MQTT over TLS.")); + Log->println(_ca); + _mqttClientSecure = new espMqttClientSecure(espMqttClientTypes::UseInternalTask::NO); + _mqttClientSecure->setCACert(_ca); + if(crtLength > 1 && keyLength > 1) // length is 1 when empty + { + Log->println(F("MQTT with client certificate.")); + Log->println(_cert); + Log->println(_key); + _mqttClientSecure->setCertificate(_cert); + _mqttClientSecure->setPrivateKey(_key); + } + } else + { + Log->println(F("MQTT without TLS.")); + _mqttClient = new espMqttClient(espMqttClientTypes::UseInternalTask::NO); + } + + if(_preferences->getBool(preference_mqtt_log_enabled, false) || _preferences->getBool(preference_webserial_enabled, false)) + { + MqttLoggerMode mode; + + if(_preferences->getBool(preference_mqtt_log_enabled, false) && _preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::MqttAndSerialAndWeb; + else if (_preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::SerialAndWeb; + else mode = MqttLoggerMode::MqttAndSerial; + + _path = new char[200]; + memset(_path, 0, sizeof(_path)); + + String pathStr = _preferences->getString(preference_mqtt_lock_path); + pathStr.concat(mqtt_topic_log); + strcpy(_path, pathStr.c_str()); + Log = new MqttLogger(*getMqttClient(), _path, mode); + } +#endif +} + +const String EthernetDevice::deviceName() const +{ + return _deviceName.c_str(); +} + +void EthernetDevice::initialize() +{ + delay(250); + + Log->println(F("Init Ethernet")); + + if(_useSpi) + { + Log->println(F("Use SPI")); + SPI.begin(_spi_sck, _spi_miso, _spi_mosi); + _hardwareInitialized = ETH.begin(_type, _phy_addr, _cs, _irq, _rst, SPI); + } + #ifdef CONFIG_IDF_TARGET_ESP32 + else + { + Log->println(F("Use RMII")); + _hardwareInitialized = ETH.begin(_type, _phy_addr, _mdc, _mdio, _power, _clock_mode); + } + #endif + + if(_hardwareInitialized) + { + Log->println(F("Ethernet hardware Initialized")); + + Network.onEvent([&](arduino_event_id_t event, arduino_event_info_t info) + { + switch (event) { + case ARDUINO_EVENT_ETH_START: + Log->println("ETH Started"); + ETH.setHostname(_hostname.c_str()); + if(!_ipConfiguration->dhcpEnabled()) ETH.config(_ipConfiguration->ipAddress(), _ipConfiguration->defaultGateway(), _ipConfiguration->subnet(), _ipConfiguration->dnsServer()); + break; + case ARDUINO_EVENT_ETH_CONNECTED: + Log->println("ETH Connected"); + if(!localIP().equals("0.0.0.0")) _connected = true; + break; + case ARDUINO_EVENT_ETH_GOT_IP: + Log->printf("ETH Got IP: '%s'\n", esp_netif_get_desc(info.got_ip.esp_netif)); + Log->println(ETH); + _connected = true; + if(_preferences->getBool(preference_ntw_reconfigure, false)) _preferences->putBool(preference_ntw_reconfigure, false); + break; + case ARDUINO_EVENT_ETH_LOST_IP: + Log->println("ETH Lost IP"); + _connected = false; + onDisconnected(); + break; + case ARDUINO_EVENT_ETH_DISCONNECTED: + Log->println("ETH Disconnected"); + _connected = false; + onDisconnected(); + break; + case ARDUINO_EVENT_ETH_STOP: + Log->println("ETH Stopped"); + _connected = false; + onDisconnected(); + break; + default: + break; + } + }); + } + else Log->println(F("Failed to initialize ethernet hardware")); +} + +void EthernetDevice::reconfigure() +{ + delay(200); + restartEsp(RestartReason::ReconfigureETH); +} + +bool EthernetDevice::supportsEncryption() +{ + return true; +} + +bool EthernetDevice::isConnected() +{ + return _connected; +} + +ReconnectStatus EthernetDevice::reconnect(bool force) +{ + if(!_hardwareInitialized) + { + return ReconnectStatus::CriticalFailure; + } + delay(200); + return isConnected() ? ReconnectStatus::Success : ReconnectStatus::Failure; +} + +void EthernetDevice::onDisconnected() +{ + if(_restartOnDisconnect && ((esp_timer_get_time() / 1000) > 60000)) restartEsp(RestartReason::RestartOnDisconnectWatchdog); + reconnect(); +} + +int8_t EthernetDevice::signalStrength() +{ + return -1; +} + +String EthernetDevice::localIP() +{ + return ETH.localIP().toString(); +} + +String EthernetDevice::BSSIDstr() +{ + return ""; +} diff --git a/src/networkDevices/EthernetDevice.h b/src/networkDevices/EthernetDevice.h new file mode 100644 index 0000000..9582222 --- /dev/null +++ b/src/networkDevices/EthernetDevice.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include +#include "LAN8720Definitions.h" +#include "DM9051Definitions.h" +#include "W5500Definitions.h" +#include +#include +#include +#include "NetworkDevice.h" +#ifndef NUKI_HUB_UPDATER +#include "espMqttClient.h" +#endif + +class EthernetDevice : public NetworkDevice +{ + +public: + EthernetDevice(const String& hostname, + Preferences* preferences, + const IPConfiguration* ipConfiguration, + const std::string& deviceName, + uint8_t phy_addr = ETH_PHY_ADDR_LAN8720, + int power = ETH_PHY_POWER_LAN8720, + int mdc = ETH_PHY_MDC_LAN8720, + int mdio = ETH_PHY_MDIO_LAN8720, + eth_phy_type_t ethtype = ETH_PHY_TYPE_LAN8720, + eth_clock_mode_t clock_mode = ETH_CLK_MODE_LAN8720, + bool use_mac_from_efuse = false); + + EthernetDevice(const String& hostname, + Preferences* preferences, + const IPConfiguration* ipConfiguration, + const std::string& deviceName, + uint8_t phy_addr, + int cs, + int irq, + int rst, + int spi_sck, + int spi_miso, + int spi_mosi, + uint8_t spi_freq_mhz, + eth_phy_type_t ethtype); + + const String deviceName() const override; + + virtual void initialize(); + virtual void reconfigure(); + virtual ReconnectStatus reconnect(bool force = false); + bool supportsEncryption() override; + + virtual bool isConnected(); + + int8_t signalStrength() override; + + String localIP() override; + String BSSIDstr() override; + +private: + Preferences* _preferences; + + void init(); + void onDisconnected(); + void waitForIpAddressWithTimeout(); + + bool _connected = false; + bool _restartOnDisconnect = false; + bool _startAp = false; + char* _path; + bool _hardwareInitialized = false; + + const std::string _deviceName; + uint8_t _phy_addr; + + // LAN8720 + int _power; + int _mdc; + int _mdio; + + // W55000 and DM9051 + int _cs; + int _irq; + int _rst; + int _spi_sck; + int _spi_miso; + int _spi_mosi; + uint8_t _spi_freq_mhz; + + eth_phy_type_t _type; + eth_clock_mode_t _clock_mode; + bool _use_mac_from_efuse; + bool _useSpi = false; + + #ifndef NUKI_HUB_UPDATER + char _ca[TLS_CA_MAX_SIZE] = {0}; + char _cert[TLS_CERT_MAX_SIZE] = {0}; + char _key[TLS_KEY_MAX_SIZE] = {0}; + #endif +}; \ No newline at end of file diff --git a/src/networkDevices/IPConfiguration.cpp b/src/networkDevices/IPConfiguration.cpp index 81bd4b4..4f374ad 100644 --- a/src/networkDevices/IPConfiguration.cpp +++ b/src/networkDevices/IPConfiguration.cpp @@ -5,7 +5,7 @@ IPConfiguration::IPConfiguration(Preferences *preferences) : _preferences(preferences) { - if(_preferences->getString(preference_ip_address, "").length() <= 0) + if(!dhcpEnabled() && _preferences->getString(preference_ip_address, "").length() <= 0) { Log->println("IP address empty, falling back to DHCP."); _preferences->putBool(preference_ip_dhcp_enabled, true); diff --git a/src/networkDevices/LAN8720Definitions.h b/src/networkDevices/LAN8720Definitions.h new file mode 100644 index 0000000..f45f5fc --- /dev/null +++ b/src/networkDevices/LAN8720Definitions.h @@ -0,0 +1,20 @@ +#pragma once + +#ifndef CONFIG_IDF_TARGET_ESP32 +typedef enum { + ETH_CLOCK_GPIO0_IN = 0, + ETH_CLOCK_GPIO16_OUT = 2, + ETH_CLOCK_GPIO17_OUT = 3 + } eth_clock_mode_t; + + #define ETH_PHY_TYPE_LAN8720 ETH_PHY_MAX +#else +#define ETH_PHY_TYPE_LAN8720 ETH_PHY_LAN8720 +#endif + +#define ETH_CLK_MODE_LAN8720 ETH_CLOCK_GPIO0_IN +#define ETH_PHY_ADDR_LAN8720 0 +#define ETH_PHY_MDC_LAN8720 23 +#define ETH_PHY_MDIO_LAN8720 18 +#define ETH_PHY_POWER_LAN8720 -1 +#define ETH_RESET_PIN_LAN8720 1 \ No newline at end of file diff --git a/src/networkDevices/W5500Definitions.h b/src/networkDevices/W5500Definitions.h new file mode 100644 index 0000000..6d08cbd --- /dev/null +++ b/src/networkDevices/W5500Definitions.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#define ETH_PHY_TYPE_W5500 ETH_PHY_W5500 +#define ETH_PHY_ADDR_W5500 1 +#define ETH_PHY_IRQ_M5_W5500 -1 +#define ETH_PHY_RST_M5_W5500 -1 +#define ETH_PHY_CS_M5_W5500 19 +#define ETH_PHY_SPI_SCK_M5_W5500 22 +#define ETH_PHY_SPI_MISO_M5_W5500 23 +#define ETH_PHY_SPI_MOSI_M5_W5500 33 +#define ETH_PHY_CS_M5_W5500_S3 6 +#define ETH_PHY_SPI_SCK_M5_W5500_S3 5 +#define ETH_PHY_SPI_MISO_M5_W5500_S3 7 +#define ETH_PHY_SPI_MOSI_M5_W5500_S3 8 +#define ETH_PHY_IRQ_GENERIC_W5500 3 +#define ETH_PHY_RST_GENERIC_W5500 4 +#define ETH_PHY_CS_GENERIC_W5500 5 +#define ETH_PHY_SPI_SCK_GENERIC_W5500 8 +#define ETH_PHY_SPI_MISO_GENERIC_W5500 9 +#define ETH_PHY_SPI_MOSI_GENERIC_W5500 10 \ No newline at end of file diff --git a/src/networkDevices/W5500Device.cpp b/src/networkDevices/W5500Device.cpp deleted file mode 100644 index 5934e34..0000000 --- a/src/networkDevices/W5500Device.cpp +++ /dev/null @@ -1,238 +0,0 @@ -#include -#include -#include "W5500Device.h" -#include "../PreferencesKeys.h" -#include "../Logger.h" -#ifndef NUKI_HUB_UPDATER -#include "../MqttTopics.h" -#endif -#include "sdkconfig.h" - -W5500Device::W5500Device(const String &hostname, Preferences* preferences, const IPConfiguration* ipConfiguration, int variant) -: NetworkDevice(hostname, ipConfiguration), - _preferences(preferences), - _variant((W5500Variant)variant) -{ - initializeMacAddress(_mac); - - Log->print("MAC Adress: "); - for(int i=0; i < 6; i++) - { - if(_mac[i] < 10) - { - Log->print(F("0")); - } - Log->print(_mac[i], 16); - if(i < 5) - { - Log->print(F(":")); - } - } - Log->println(); - #ifndef NUKI_HUB_UPDATER - _mqttClient = new espMqttClientW5500(); - #endif -} - -W5500Device::~W5500Device() -{} - -const String W5500Device::deviceName() const -{ - return "Wiznet W5500"; -} - -void W5500Device::initialize() -{ - WiFi.mode(WIFI_STA); - - resetDevice(); - - switch(_variant) - { - case W5500Variant::M5StackAtomPoe: - _resetPin = -1; - #if defined(CONFIG_IDF_TARGET_ESP32S3) - Ethernet.init(6, 5, 7, 8); - #else - Ethernet.init(19, 22, 23, 33); - #endif - break; - default: - _resetPin = -1; - Ethernet.init(5); - break; - } - - #ifndef NUKI_HUB_UPDATER - if(_preferences->getBool(preference_mqtt_log_enabled, false) || _preferences->getBool(preference_webserial_enabled, false)) - { - MqttLoggerMode mode; - - if(_preferences->getBool(preference_mqtt_log_enabled, false) && _preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::MqttAndSerialAndWeb; - else if (_preferences->getBool(preference_webserial_enabled, false)) mode = MqttLoggerMode::SerialAndWeb; - else mode = MqttLoggerMode::MqttAndSerial; - - String pathStr = _preferences->getString(preference_mqtt_lock_path); - pathStr.concat(mqtt_topic_log); - _path = new char[pathStr.length() + 1]; - memset(_path, 0, sizeof(_path)); - strcpy(_path, pathStr.c_str()); - Log = new MqttLogger(*getMqttClient(), _path, mode); - } - #endif - - reconnect(); -} - -ReconnectStatus W5500Device::reconnect(bool force) -{ - _hasDHCPAddress = false; - - // start the Ethernet connection: - Log->println(F("Initialize Ethernet with DHCP:")); - - int dhcpRetryCnt = 0; - bool hardwareFound = false; - - while(dhcpRetryCnt < 3) - { - Log->print(F("DHCP connect try #")); - Log->print(dhcpRetryCnt); - Log->println(); - dhcpRetryCnt++; - - if (Ethernet.begin(_mac, 1000, 1000) == 0) - { - Log->println(F("Failed to configure Ethernet using DHCP")); - // Check for Ethernet hardware present - if (Ethernet.hardwareStatus() == EthernetNoHardware) - { - Log->println(F("Ethernet module not found")); - continue; - } - if (Ethernet.linkStatus() == LinkOFF) - { - Log->println(F("Ethernet cable is not connected.")); - } - - hardwareFound = true; - - IPAddress ip; - ip.fromString("192.168.4.1"); - - IPAddress subnet; - subnet.fromString("255.255.255.0"); - - // try to congifure using IP address instead of DHCP: - Ethernet.begin(_mac, ip); - Ethernet.setSubnetMask(subnet); - - delay(1000); - } - else - { - hardwareFound = true; - _hasDHCPAddress = true; - dhcpRetryCnt = 1000; - if(_ipConfiguration->dhcpEnabled()) - { - Log->print(F(" DHCP assigned IP ")); - Log->println(Ethernet.localIP()); - } - } - - if(!_ipConfiguration->dhcpEnabled()) - { - Ethernet.setLocalIP(_ipConfiguration->ipAddress()); - Ethernet.setSubnetMask(_ipConfiguration->subnet()); - Ethernet.setGatewayIP(_ipConfiguration->defaultGateway()); - Ethernet.setDnsServerIP(_ipConfiguration->dnsServer()); - } - } - - if(!hardwareFound) - { - return ReconnectStatus::CriticalFailure; - } - - return _hasDHCPAddress ? ReconnectStatus::Success : ReconnectStatus::Failure; -} - - -void W5500Device::reconfigure() -{ - Log->println(F("Reconfigure W5500 not implemented.")); -} - -void W5500Device::resetDevice() -{ - if(_resetPin == -1) return; - - Log->println(F("Resetting network hardware.")); - pinMode(_resetPin, OUTPUT); - digitalWrite(_resetPin, HIGH); - delay(50); - digitalWrite(_resetPin, LOW); - delay(50); - digitalWrite(_resetPin, HIGH); - delay(50); -} - -bool W5500Device::supportsEncryption() -{ - return false; -} - -bool W5500Device::isConnected() -{ - return (Ethernet.linkStatus() == EthernetLinkStatus::LinkON && _maintainResult == 0 && _hasDHCPAddress); -} - -void W5500Device::initializeMacAddress(byte *mac) -{ - memset(mac, 0, 6); - - mac[0] = 0x00; // wiznet prefix - mac[1] = 0x08; // wiznet prefix - mac[2] = 0xDC; // wiznet prefix - - if(_preferences->getBool(preference_has_mac_saved, false)) - { - mac[3] = _preferences->getChar(preference_has_mac_byte_0); - mac[4] = _preferences->getChar(preference_has_mac_byte_1); - mac[5] = _preferences->getChar(preference_has_mac_byte_2); - } - else - { - mac[3] = random(0,255); - mac[4] = random(0,255); - mac[5] = random(0,255); - - _preferences->putChar(preference_has_mac_byte_0, mac[3]); - _preferences->putChar(preference_has_mac_byte_1, mac[4]); - _preferences->putChar(preference_has_mac_byte_2, mac[5]); - _preferences->putBool(preference_has_mac_saved, true); - } -} - -void W5500Device::update() -{ - _maintainResult = Ethernet.maintain(); - NetworkDevice::update(); -} - -int8_t W5500Device::signalStrength() -{ - return 127; -} - -String W5500Device::localIP() -{ - return Ethernet.localIP().toString(); -} - -String W5500Device::BSSIDstr() -{ - return ""; -} \ No newline at end of file diff --git a/src/networkDevices/W5500Device.h b/src/networkDevices/W5500Device.h deleted file mode 100644 index f0b4e9f..0000000 --- a/src/networkDevices/W5500Device.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include "NetworkDevice.h" -#ifndef NUKI_HUB_UPDATER -#include "espMqttClient.h" -#include "espMqttClientW5500.h" -#endif -#include -#include - -enum class W5500Variant -{ - Generic = 2, - M5StackAtomPoe = 3 -}; - -class W5500Device : public NetworkDevice -{ -public: - explicit W5500Device(const String& hostname, Preferences* _preferences, const IPConfiguration* ipConfiguration, int variant); - ~W5500Device(); - - const String deviceName() const override; - - virtual void initialize(); - virtual ReconnectStatus reconnect(bool force = false); - virtual void reconfigure(); - - bool supportsEncryption() override; - - virtual void update() override; - - virtual bool isConnected(); - - int8_t signalStrength() override; - - String localIP() override; - String BSSIDstr() override; - -private: - void resetDevice(); - void initializeMacAddress(byte* mac); - - Preferences* _preferences = nullptr; - - int _maintainResult = 0; - int _resetPin = -1; - bool _hasDHCPAddress = false; - char* _path; - W5500Variant _variant; - - byte _mac[6]; -}; \ No newline at end of file diff --git a/src/networkDevices/WifiDevice.cpp b/src/networkDevices/WifiDevice.cpp index db3e8f0..1c8c9d3 100644 --- a/src/networkDevices/WifiDevice.cpp +++ b/src/networkDevices/WifiDevice.cpp @@ -72,10 +72,11 @@ const String WifiDevice::deviceName() const void WifiDevice::initialize() { + _wifiFallbackDisabled = _preferences->getBool(preference_network_wifi_fallback_disabled, false); std::vector wm_menu; wm_menu.push_back("wifi"); wm_menu.push_back("exit"); - _wm.setEnableConfigPortal(_startAp || !_preferences->getBool(preference_network_wifi_fallback_disabled, false)); + _wm.setEnableConfigPortal(_startAp || !_wifiFallbackDisabled); // reduced timeout if ESP is set to restart on disconnect _wm.setFindBestRSSI(_preferences->getBool(preference_find_best_rssi)); _wm.setConnectTimeout(20); @@ -92,11 +93,13 @@ void WifiDevice::initialize() _wm.setAPCallback(clearRtcInitVar); bool res = false; - + bool connectedFromPortal = false; + if(_startAp) { Log->println(F("Opening Wi-Fi configuration portal.")); res = _wm.startConfigPortal(); + connectedFromPortal = true; } else { @@ -116,6 +119,13 @@ void WifiDevice::initialize() else { Log->print(F("Wi-Fi connected: ")); Log->println(WiFi.localIP().toString()); + + if(connectedFromPortal) + { + Log->println(F("Connected using WifiManager portal. Wait for ESP restart.")); + delay(1000); + restartEsp(RestartReason::ConfigurationUpdated); + } } WiFi.onEvent([&](WiFiEvent_t event, WiFiEventInfo_t info) @@ -167,14 +177,14 @@ ReconnectStatus WifiDevice::reconnect(bool force) _isReconnecting = false; } - if(!isConnected() && _disconnectTs > (esp_timer_get_time() / 1000) - 120000) _wm.setEnableConfigPortal(_startAp || !_preferences->getBool(preference_network_wifi_fallback_disabled, false)); + if(!isConnected() && _disconnectTs > (esp_timer_get_time() / 1000) - 120000) _wm.setEnableConfigPortal(_startAp || !_wifiFallbackDisabled); return isConnected() ? ReconnectStatus::Success : ReconnectStatus::Failure; } void WifiDevice::onConnected() { _isReconnecting = false; - _wm.setEnableConfigPortal(_startAp || !_preferences->getBool(preference_network_wifi_fallback_disabled, false)); + _wm.setEnableConfigPortal(_startAp || !_wifiFallbackDisabled); } void WifiDevice::onDisconnected() diff --git a/src/networkDevices/WifiDevice.h b/src/networkDevices/WifiDevice.h index 1455eea..cdd5eba 100644 --- a/src/networkDevices/WifiDevice.h +++ b/src/networkDevices/WifiDevice.h @@ -41,6 +41,7 @@ private: bool _restartOnDisconnect = false; bool _startAp = false; bool _isReconnecting = false; + bool _wifiFallbackDisabled = false; char* _path; int64_t _disconnectTs = 0; diff --git a/src/networkDevices/espMqttClientW5500.cpp b/src/networkDevices/espMqttClientW5500.cpp deleted file mode 100644 index 91bfd5a..0000000 --- a/src/networkDevices/espMqttClientW5500.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "espMqttClientW5500.h" - -espMqttClientW5500::espMqttClientW5500() -: espMqttClient(espMqttClientTypes::UseInternalTask::NO), - _client() -{ - _transport = &_client; -} - -void espMqttClientW5500::update() -{ - loop(); -} diff --git a/src/networkDevices/espMqttClientW5500.h b/src/networkDevices/espMqttClientW5500.h deleted file mode 100644 index ba66dd9..0000000 --- a/src/networkDevices/espMqttClientW5500.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "espMqttClient.h" -#include "ClientSyncW5500.h" - -class espMqttClientW5500 : public espMqttClient { -public: -#if defined(ARDUINO_ARCH_ESP32) - explicit espMqttClientW5500(); -#else - espMqttClient(); -#endif - - void update(); - -protected: -#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) - espMqttClientInternals::ClientSyncW5500 _client; -#elif defined(__linux__) - espMqttClientInternals::ClientPosix _client; -#endif -}; diff --git a/updater/pio_package.py b/updater/pio_package.py deleted file mode 100644 index b163694..0000000 --- a/updater/pio_package.py +++ /dev/null @@ -1,52 +0,0 @@ -""" PlatformIO POST script execution to copy updater """ - -Import("env") -import glob, re, shutil, os -from pathlib import Path - -def get_board_name(env): - board = env.get('BOARD_MCU') - if env.get('BOARD') == 'esp32-solo1': - board = env.get('BOARD').replace('-', '') - return board - -def create_target_dir(env): - board = get_board_name(env) - target_dir = env.GetProjectOption("custom_build") + '/' + board - if not os.path.exists(target_dir): - os.makedirs(target_dir) - return target_dir - -def copy_files(source, target, env): - file = Path(target[0].get_abspath()) - target_dir = create_target_dir(env) - board = get_board_name(env) - - if "firmware" in file.stem: - shutil.copy(file, f"{target_dir}/updater.bin") - -def remove_files(source, target, env): - for f in glob.glob("src/*.cpp"): - os.remove(f) - - for f in glob.glob("src/*.h"): - os.remove(f) - - for f in glob.glob("src/networkDevices/*.cpp"): - os.remove(f) - - for f in glob.glob("src/networkDevices/*.h"): - os.remove(f) - -env.AddPostAction("$BUILD_DIR/firmware.bin", copy_files) -env.AddPostAction("$BUILD_DIR/firmware.bin", remove_files) - -regex = r"\#define NUKI_HUB_DATE \"(.*)\"" -content_new = "" - -with open ('../src/Config.h', 'r' ) as readfile: - file_content = readfile.read() - content_new = re.sub(regex, "#define NUKI_HUB_DATE \"unknownbuilddate\"", file_content, flags = re.M) - -with open('../src/Config.h', 'w') as writefile: - writefile.write(content_new) diff --git a/updater/pio_package_post.py b/updater/pio_package_post.py new file mode 100644 index 0000000..159f7c6 --- /dev/null +++ b/updater/pio_package_post.py @@ -0,0 +1,28 @@ +""" PlatformIO POST script execution to copy updater """ + +Import("env") +import glob, re, shutil, os +from pathlib import Path + +def get_board_name(env): + board = env.get('BOARD_MCU') + if env.get('BOARD') == 'esp32-solo1': + board = env.get('BOARD').replace('-', '') + return board + +def create_target_dir(env): + board = get_board_name(env) + target_dir = env.GetProjectOption("custom_build") + '/' + board + if not os.path.exists(target_dir): + os.makedirs(target_dir) + return target_dir + +def copy_files(source, target, env): + file = Path(target[0].get_abspath()) + target_dir = create_target_dir(env) + board = get_board_name(env) + + if "firmware" in file.stem: + shutil.copy(file, f"{target_dir}/updater.bin") + +env.AddPostAction("$BUILD_DIR/firmware.bin", copy_files) \ No newline at end of file diff --git a/updater/pio_package_pre.py b/updater/pio_package_pre.py index 32e9045..5b3d47b 100644 --- a/updater/pio_package_pre.py +++ b/updater/pio_package_pre.py @@ -10,23 +10,50 @@ def recursive_purge(dir, pattern): elif re.search(pattern, os.path.join(dir, f)): os.remove(os.path.join(dir, f)) +if os.path.exists("src/Config.h"): + with open("../src/Config.h", "rb") as file_a, open("src/Config.h", "rb") as file_b: + if file_a.read() != file_b.read(): + shutil.copy2("../src/Config.h", "src/Config.h") +else: + shutil.copy2("../src/Config.h", "src/Config.h") + regex = r"\#define NUKI_HUB_DATE \"(.*)\"" content_new = "" +file_content = "" -with open ('../src/Config.h', 'r' ) as readfile: +with open ('src/Config.h', 'r' ) as readfile: file_content = readfile.read() content_new = re.sub(regex, "#define NUKI_HUB_DATE \"" + datetime.now(timezone.utc).strftime("%Y-%m-%d") + "\"", file_content, flags = re.M) -with open('../src/Config.h', 'w') as writefile: - writefile.write(content_new) +if content_new != file_content: + with open('src/Config.h', 'w') as writefile: + writefile.write(content_new) + +if os.path.exists("src/main.cpp"): + with open("../src/main.cpp", "rb") as file_a, open("src/main.cpp", "rb") as file_b: + if file_a.read() != file_b.read(): + shutil.copy2("../src/main.cpp", "src/main.cpp") +else: + shutil.copy2("../src/main.cpp", "src/main.cpp") -shutil.copy("../src/main.cpp", "src/main.cpp") recursive_purge("managed_components", ".component_hash") -if env.get('BOARD_MCU') == "esp32": - board = "esp32dev" -else: - board = env.get('BOARD_MCU') +board = env.get('BOARD_MCU') if os.path.exists("sdkconfig.updater_" + board): - os.remove("sdkconfig." + board) \ No newline at end of file + f1 = 0; + f2 = 0; + f3 = 0; + f4 = os.path.getmtime("sdkconfig.updater_" + board) + + if os.path.exists("sdkconfig.defaults." + board): + f1 = os.path.getmtime("sdkconfig.defaults." + board) + + if os.path.exists("sdkconfig.release.defaults"): + f2 = os.path.getmtime("sdkconfig.release.defaults") + + if os.path.exists("sdkconfig.defaults"): + f3 = os.path.getmtime("sdkconfig.defaults") + + if(f1 > f4 or f2 > f4 or f3 > f4): + os.remove("sdkconfig.updater_" + board) \ No newline at end of file diff --git a/updater/platformio.ini b/updater/platformio.ini index 7363035..7bf7ec3 100644 --- a/updater/platformio.ini +++ b/updater/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = updater_esp32dev +default_envs = updater_esp32 boards_dir = ../boards [env] @@ -32,6 +32,14 @@ build_flags = -DESP_PLATFORM -DESP32 -DARDUINO_ARCH_ESP32 + -DUSE_ESP_IDF_LOG + -DCONFIG_ASYNC_TCP_MAX_ACK_TIME=3000 + -DCONFIG_ASYNC_TCP_PRIORITY=10 + -DCONFIG_ASYNC_TCP_RUNNING_CORE=1 + -DCONFIG_ASYNC_TCP_QUEUE_SIZE=128 + -DCONFIG_ASYNC_TCP_STACK_SIZE=24576 + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE + -DETH_SPI_SUPPORTS_NO_IRQ -DNUKI_HUB_UPDATER -Wno-ignored-qualifiers -Wno-missing-field-initializers @@ -46,10 +54,7 @@ lib_ignore = WiFiProv lib_deps = AsyncTCP=symlink://../lib/AsyncTCP - Ethernet=symlink://../lib/Ethernet - HTTPClient=symlink://../lib/HTTPClient - NetworkClientSecure=symlink://../lib/NetworkClientSecure - WebServer=symlink://../lib/WebServer + ESPAsyncWebServer=symlink://../lib/ESPAsyncWebServer WiFiManager=symlink://../lib/WiFiManager monitor_speed = 115200 @@ -57,26 +62,35 @@ monitor_filters = esp32_exception_decoder time -[env:updater_esp32dev] +[env:updater_esp32] board = esp32dev extra_scripts = pre:pio_package_pre.py - post:pio_package.py + post:pio_package_post.py [env:updater_esp32-c3] -extends = env:updater_esp32dev +extends = env:updater_esp32 board = esp32-c3-devkitc-02 [env:updater_esp32-s3] -extends = env:updater_esp32dev +extends = env:updater_esp32 board = nuki-esp32-s3 [env:updater_esp32-c6] -extends = env:updater_esp32dev +extends = env:updater_esp32 board = esp32-c6-devkitm-1 +[env:updater_esp32-h2] +extends = env:updater_esp32 +board = esp32-h2-devkitm-1 +board_build.cmake_extra_args = + -DNUKI_TARGET_H2=y +lib_deps = + AsyncTCP=symlink://../lib/AsyncTCP + ESPAsyncWebServer=symlink://../lib/ESPAsyncWebServer + [env:updater_esp32-solo1] -extends = env:updater_esp32dev +extends = env:updater_esp32 board = esp32-solo1 board_build.cmake_extra_args = -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.esp32-solo1" \ No newline at end of file diff --git a/updater/sdkconfig.defaults b/updater/sdkconfig.defaults index d643fde..e60db16 100644 --- a/updater/sdkconfig.defaults +++ b/updater/sdkconfig.defaults @@ -9,8 +9,6 @@ CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE=y CONFIG_BT_ENABLED=n CONFIG_IEEE802154_ENABLED=n CONFIG_ARDUINO_SELECTIVE_COMPILATION=y -CONFIG_ARDUINO_SELECTIVE_HTTPClient=n -CONFIG_ARDUINO_SELECTIVE_WebServer=n CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN=y CONFIG_LOG_COLORS=n @@ -19,4 +17,9 @@ CONFIG_LOG_MAXIMUM_LEVEL=4 CONFIG_LOG_DEFAULT_LEVEL_NONE=y CONFIG_LOG_DEFAULT_LEVEL=0 CONFIG_BOOTLOADER_LOG_LEVEL_ERROR=y -CONFIG_BOOTLOADER_LOG_LEVEL=1 \ No newline at end of file +CONFIG_BOOTLOADER_LOG_LEVEL=1 +CONFIG_ETH_ENABLED=y +CONFIG_ETH_USE_SPI_ETHERNET=y +CONFIG_ETH_SPI_ETHERNET_W5500=y +CONFIG_ETH_SPI_ETHERNET_DM9051=y +CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL=y \ No newline at end of file diff --git a/updater/src/CMakeLists.txt b/updater/src/CMakeLists.txt index 8f4a391..d7ce866 100644 --- a/updater/src/CMakeLists.txt +++ b/updater/src/CMakeLists.txt @@ -1,8 +1,7 @@ list(APPEND app_sources ${CMAKE_SOURCE_DIR}/src/main.cpp) -list(APPEND app_sources ../../src/Config.h) +list(APPEND app_sources ${CMAKE_SOURCE_DIR}/src/Config.h) list(APPEND app_sources ../../src/Logger.h) list(APPEND app_sources ../../src/NukiNetwork.h) -list(APPEND app_sources ../../src/Ota.h) list(APPEND app_sources ../../src/PreferencesKeys.h) list(APPEND app_sources ../../src/RestartReason.h) list(APPEND app_sources ../../src/WebCfgServer.h) @@ -10,19 +9,22 @@ list(APPEND app_sources ../../src/WebCfgServerConstants.h) list(APPEND app_sources ../../src/Logger.cpp) list(APPEND app_sources ../../src/NukiNetwork.cpp) -list(APPEND app_sources ../../src/Ota.cpp) list(APPEND app_sources ../../src/WebCfgServer.cpp) -list(APPEND app_sources ../../src/networkDevices/EthLan8720Device.h) +list(APPEND app_sources ../../src/networkDevices/EthernetDevice.h) list(APPEND app_sources ../../src/networkDevices/IPConfiguration.h) list(APPEND app_sources ../../src/networkDevices/NetworkDevice.h) -list(APPEND app_sources ../../src/networkDevices/W5500Device.h) -list(APPEND app_sources ../../src/networkDevices/WifiDevice.h) -list(APPEND app_sources ../../src/networkDevices/EthLan8720Device.cpp) +if(NOT DEFINED NUKI_TARGET_H2) + list(APPEND app_sources ../../src/networkDevices/WifiDevice.h) +endif() + +list(APPEND app_sources ../../src/networkDevices/EthernetDevice.cpp) list(APPEND app_sources ../../src/networkDevices/IPConfiguration.cpp) list(APPEND app_sources ../../src/networkDevices/NetworkDevice.cpp) -list(APPEND app_sources ../../src/networkDevices/W5500Device.cpp) -list(APPEND app_sources ../../src/networkDevices/WifiDevice.cpp) + +if(NOT DEFINED NUKI_TARGET_H2) + list(APPEND app_sources ../../src/networkDevices/WifiDevice.cpp) +endif() idf_component_register(SRCS ${app_sources})