Browse Source

0.6.0 2020-01-13

* added NTP time sync + config page
* added simple logging function which replaces most Serial.print()'s and can send via Serial, MQTT and WebSocket
* added WebSocketServer and simple web console (experimental)
  * TCP port 81
  * for basic security accessible only with valid session cookie which is generated on load of /console page
  * can send commands similar to MQTT and Serial to device
  * shows log output as long as the connection is up
* access system information at http://[IP_ADDR]/sysinfo in JSON format
* moved HTML contents to .h files
* lots of smaller and bigger changes and improvements
FloKra 4 years ago
parent
commit
b87bb1ebc4
100 changed files with 8990 additions and 514 deletions
  1. 13 0
      CHANGELOG.md
  2. 8 1
      LIBRARIES.md
  3. 6 0
      docs/Getting Started with WebSockets.URL
  4. 2 2
      lib/PersWiFiManagerExt-1.0.0/PersWiFiManagerExt.cpp
  5. 2 2
      lib/PersWiFiManagerExt-1.0.0/PersWiFiManagerExt.h
  6. 63 0
      lib/arduinoWebSockets-2.1.4/.clang-format
  7. 37 0
      lib/arduinoWebSockets-2.1.4/.gitignore
  8. 53 0
      lib/arduinoWebSockets-2.1.4/.travis.yml
  9. 502 0
      lib/arduinoWebSockets-2.1.4/LICENSE
  10. 98 0
      lib/arduinoWebSockets-2.1.4/README.md
  11. 83 0
      lib/arduinoWebSockets-2.1.4/examples/Nginx/esp8266.ssl.reverse.proxy.conf
  12. 84 0
      lib/arduinoWebSockets-2.1.4/examples/avr/WebSocketClientAVR/WebSocketClientAVR.ino
  13. 110 0
      lib/arduinoWebSockets-2.1.4/examples/esp32/WebSocketClient/WebSocketClient.ino
  14. 106 0
      lib/arduinoWebSockets-2.1.4/examples/esp32/WebSocketClientSSL/WebSocketClientSSL.ino
  15. 104 0
      lib/arduinoWebSockets-2.1.4/examples/esp32/WebSocketServer/WebSocketServer.ino
  16. 106 0
      lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketClient/WebSocketClient.ino
  17. 88 0
      lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketClientSSL/WebSocketClientSSL.ino
  18. 125 0
      lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketClientSocketIO/WebSocketClientSocketIO.ino
  19. 149 0
      lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketClientStomp/WebSocketClientStomp.ino
  20. 150 0
      lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketClientStompOverSockJs/WebSocketClientStompOverSockJs.ino
  21. 86 0
      lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketServer/WebSocketServer.ino
  22. 132 0
      lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketServerAllFunctionsDemo/WebSocketServerAllFunctionsDemo.ino
  23. 94 0
      lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketServerFragmentation/WebSocketServerFragmentation.ino
  24. 86 0
      lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketServerHttpHeaderValidation/WebSocketServerHttpHeaderValidation.ino
  25. 121 0
      lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketServer_LEDcontrol/WebSocketServer_LEDcontrol.ino
  26. 46 0
      lib/arduinoWebSockets-2.1.4/examples/particle/ParticleWebSocketClient/application.cpp
  27. 25 0
      lib/arduinoWebSockets-2.1.4/library.json
  28. 9 0
      lib/arduinoWebSockets-2.1.4/library.properties
  29. 174 0
      lib/arduinoWebSockets-2.1.4/src/SocketIOclient.cpp
  30. 80 0
      lib/arduinoWebSockets-2.1.4/src/SocketIOclient.h
  31. 749 0
      lib/arduinoWebSockets-2.1.4/src/WebSockets.cpp
  32. 331 0
      lib/arduinoWebSockets-2.1.4/src/WebSockets.h
  33. 854 0
      lib/arduinoWebSockets-2.1.4/src/WebSocketsClient.cpp
  34. 145 0
      lib/arduinoWebSockets-2.1.4/src/WebSocketsClient.h
  35. 852 0
      lib/arduinoWebSockets-2.1.4/src/WebSocketsServer.cpp
  36. 206 0
      lib/arduinoWebSockets-2.1.4/src/WebSocketsServer.h
  37. 7 0
      lib/arduinoWebSockets-2.1.4/src/libb64/AUTHORS
  38. 29 0
      lib/arduinoWebSockets-2.1.4/src/libb64/LICENSE
  39. 98 0
      lib/arduinoWebSockets-2.1.4/src/libb64/cdecode.c
  40. 28 0
      lib/arduinoWebSockets-2.1.4/src/libb64/cdecode_inc.h
  41. 119 0
      lib/arduinoWebSockets-2.1.4/src/libb64/cencode.c
  42. 31 0
      lib/arduinoWebSockets-2.1.4/src/libb64/cencode_inc.h
  43. 202 0
      lib/arduinoWebSockets-2.1.4/src/libsha1/libsha1.c
  44. 21 0
      lib/arduinoWebSockets-2.1.4/src/libsha1/libsha1.h
  45. 49 0
      lib/arduinoWebSockets-2.1.4/tests/webSocket.html
  46. 57 0
      lib/arduinoWebSockets-2.1.4/tests/webSocketServer/index.js
  47. 27 0
      lib/arduinoWebSockets-2.1.4/tests/webSocketServer/package.json
  48. 53 0
      lib/arduinoWebSockets-2.1.4/travis/common.sh
  49. BIN
      releases/bin/WiFiThermostat.ino.d1_mini.20200113_v0.6.0.bin
  50. BIN
      releases/src/WiFiThermostat_0.6.0.zip
  51. 12 6
      src/WiFiThermostat/Buttonhandling.ino
  52. 163 24
      src/WiFiThermostat/WiFiThermostat.ino
  53. 2 2
      src/WiFiThermostat/commands.ino
  54. 8 0
      src/WiFiThermostat/commonFunctions.ino
  55. 292 1
      src/WiFiThermostat/config.ino
  56. 0 0
      src/WiFiThermostat/html.h
  57. 11 1
      src/WiFiThermostat/html_conf.h
  58. 0 0
      src/WiFiThermostat/html_confAdd.h
  59. 0 0
      src/WiFiThermostat/html_confAdv.h
  60. 0 0
      src/WiFiThermostat/html_confBas.h
  61. 11 9
      src/WiFiThermostat/html_confDevWiFi.h
  62. 93 0
      src/WiFiThermostat/html_confLog.h
  63. 0 0
      src/WiFiThermostat/html_confMqtt.h
  64. 93 0
      src/WiFiThermostat/html_confTime.h
  65. 9 1
      src/WiFiThermostat/html_confWeb.h
  66. 63 0
      src/WiFiThermostat/html_console.h
  67. 43 25
      src/WiFiThermostat/html_main.h
  68. 0 0
      src/WiFiThermostat/html_redTemps.h
  69. 527 82
      src/WiFiThermostat/httpServer.ino
  70. 69 0
      src/WiFiThermostat/logging.ino
  71. 12 0
      src/WiFiThermostat/miscFunctions.ino
  72. 197 168
      src/WiFiThermostat/mqtt.ino
  73. 102 48
      src/WiFiThermostat/mqtt_out.ino
  74. 11 4
      src/WiFiThermostat/outTempHum.ino
  75. 16 0
      src/WiFiThermostat/scheduler.ino
  76. 66 42
      src/WiFiThermostat/thermostat.ino
  77. 95 0
      src/WiFiThermostat/time.ino
  78. 98 0
      src/WiFiThermostat/websockets.ino
  79. 1 1
      src/webinterface/api
  80. 9 3
      src/webinterface/conf
  81. 1 1
      src/webinterface/confDataAdd
  82. 1 1
      src/webinterface/confDataAdv
  83. 1 1
      src/webinterface/confDataBas
  84. 1 1
      src/webinterface/confDataDevWiFi
  85. 1 0
      src/webinterface/confDataLog
  86. 1 1
      src/webinterface/confDataMqtt
  87. 1 0
      src/webinterface/confDataTime
  88. 1 1
      src/webinterface/confDataWeb
  89. 7 7
      src/webinterface/confadd
  90. 6 6
      src/webinterface/confadv
  91. 6 6
      src/webinterface/confbas
  92. 22 20
      src/webinterface/confdevwifi
  93. 99 0
      src/webinterface/conflog
  94. 6 6
      src/webinterface/confmqtt
  95. 98 0
      src/webinterface/conftime
  96. 17 9
      src/webinterface/confweb
  97. 22 0
      src/webinterface/console
  98. 46 30
      src/webinterface/index
  99. 2 2
      src/webinterface/redTemps
  100. 48 0
      src/webinterface/wsapp.js

+ 13 - 0
CHANGELOG.md

@@ -1,5 +1,18 @@
 # WiFiThermostat - Changelog
 
+## 0.6.0 2020-01-13
+
+* added NTP time sync + config page
+* added simple logging function which replaces most Serial.print()'s and can send via Serial, MQTT and WebSocket
+* added WebSocketServer and simple web console (experimental)
+  * TCP port 81
+  * for basic security accessible only with valid session cookie which is generated on load of /console page
+  * can send commands similar to MQTT and Serial to device
+  * shows log output as long as the connection is up
+* access system information at http://[IP_ADDR]/sysinfo in JSON format
+* moved HTML contents to .h files
+* lots of smaller and bigger changes and improvements
+
 ## 0.5.0 2020-01-09
 
 * preparations/changes for building/development using VSCode/PlatformIO

+ 8 - 1
LIBRARIES.md

@@ -48,7 +48,7 @@ https://github.com/adafruit/Adafruit_Sensor
 * pre v0.4.0:  
   Adafruit Unified Sensor 1.0.2  
 
-### PersWiFiManager
+### PersWiFiManager(Ext)
 AP with captive portal for initial (WiFi-) configuration.  
 Used with modification - added #define WIFI_HTM_PROGMEM to PersWiFiManager.cpp (turns off SPIFFS usage and stores HTML page to PROGMEM to simplify firmware upload)  
 https://github.com/r-downing/PersWiFiManager
@@ -98,3 +98,10 @@ https://github.com/thomasfredericks/Bounce2
   Bounce2 2.52
 * pre v0.4.0:  
   Bounce2 2.41  
+
+### arduinoWebSockets 2.1.4
+for experimental Web console  
+https://github.com/Links2004/arduinoWebSockets  
+
+* from v0.6.0
+

+ 6 - 0
docs/Getting Started with WebSockets.URL

@@ -0,0 +1,6 @@
+[InternetShortcut]
+URL=https://www.htmlgoodies.com/html5/getting-started-with-websockets.html
+IDList=
+HotKey=0
+IconFile=C:\Users\Flo\AppData\Local\Mozilla\Firefox\Profiles\q43ra11i.default\shortcutCache\OhpvM9+lNXo6OaGlP1xNgA==.ico
+IconIndex=0

+ 2 - 2
lib/PersWiFiManagerExt-1.0.0/PersWiFiManagerExt.cpp

@@ -242,9 +242,9 @@ void PersWiFiManager::handleWiFi() {
     _connectStartTime = 0;
     _apModeFinished = true; // connection was successful - disable AP until reboot
     _lastSuccessfulConnect = millis();
-//#ifdef DEBUG
+#ifdef DEBUG
     if(_connectSetWifi > 0) Serial.print("WiFi: connected to WiFi-"); Serial.println(_connectSetWifi);
-//#endif
+#endif
     if(_connectSetWifi == 1) _wifi1ConnectAttempts = 0;
     if(_connectSetWifi == 2) _wifi2ConnectAttempts = 0;
     if (_connectHandler) _connectHandler();

+ 2 - 2
lib/PersWiFiManagerExt-1.0.0/PersWiFiManagerExt.h

@@ -120,8 +120,8 @@ class PersWiFiManager {
 
     unsigned long _forceRetryWifi1LastTime;
 
-    bool _apModeEnabled = true;
-    bool _httpAuth = false;
+    bool _apModeEnabled;
+    bool _httpAuth;
 
     WiFiChangeHandlerFunction _connectHandler;
     WiFiChangeHandlerFunction _apHandler;

+ 63 - 0
lib/arduinoWebSockets-2.1.4/.clang-format

@@ -0,0 +1,63 @@
+---
+BasedOnStyle: Google
+AccessModifierOffset: '-2'
+AlignAfterOpenBracket: DontAlign
+AlignConsecutiveAssignments: 'true'
+AlignConsecutiveDeclarations: 'false'
+AlignEscapedNewlines: Left
+AlignTrailingComments: 'true'
+AllowAllParametersOfDeclarationOnNextLine: 'false'
+AllowShortBlocksOnASingleLine: 'false'
+AllowShortCaseLabelsOnASingleLine: 'false'
+AllowShortFunctionsOnASingleLine: InlineOnly
+AllowShortIfStatementsOnASingleLine: 'true'
+AllowShortLoopsOnASingleLine: 'true'
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: 'true'
+AlwaysBreakTemplateDeclarations: 'false'
+BinPackParameters: 'true'
+BreakAfterJavaFieldAnnotations: 'false'
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: 'false'
+BreakBeforeTernaryOperators: 'false'
+BreakConstructorInitializers: BeforeColon
+BreakStringLiterals: 'false'
+ColumnLimit: '0'
+CompactNamespaces: 'true'
+ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
+ConstructorInitializerIndentWidth: '4'
+ContinuationIndentWidth: '4'
+Cpp11BracedListStyle: 'false'
+DerivePointerAlignment: 'false'
+FixNamespaceComments: 'true'
+IndentCaseLabels: 'true'
+IndentWidth: '4'
+IndentWrappedFunctionNames: 'false'
+JavaScriptQuotes: Single
+JavaScriptWrapImports: 'false'
+KeepEmptyLinesAtTheStartOfBlocks: 'false'
+MaxEmptyLinesToKeep: '1'
+NamespaceIndentation: All
+ObjCBlockIndentWidth: '4'
+ObjCSpaceAfterProperty: 'false'
+ObjCSpaceBeforeProtocolList: 'false'
+PointerAlignment: Middle
+SortIncludes: 'false'
+SortUsingDeclarations: 'true'
+SpaceAfterCStyleCast: 'false'
+SpaceAfterTemplateKeyword: 'false'
+SpaceBeforeAssignmentOperators: 'true'
+SpaceBeforeParens: Never
+SpaceInEmptyParentheses: 'false'
+SpacesBeforeTrailingComments: '4'
+SpacesInAngles: 'false'
+SpacesInCStyleCastParentheses: 'false'
+SpacesInContainerLiterals: 'false'
+SpacesInParentheses: 'false'
+SpacesInSquareBrackets: 'false'
+TabWidth: '4'
+UseTab: Never
+
+...

+ 37 - 0
lib/arduinoWebSockets-2.1.4/.gitignore

@@ -0,0 +1,37 @@
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+/tests/webSocketServer/node_modules
+
+# IDE
+.vscode
+.cproject
+.project
+.settings
+*.swp
+

+ 53 - 0
lib/arduinoWebSockets-2.1.4/.travis.yml

@@ -0,0 +1,53 @@
+sudo: false
+dist:
+  - xenial
+addons:
+  apt:
+    packages:
+      - xvfb
+language: bash
+os:
+  - linux
+env:
+  matrix:
+    - CPU="esp8266" BOARD="esp8266com:esp8266:generic:xtal=80" IDE_VERSION=1.6.5
+    - CPU="esp8266" BOARD="esp8266com:esp8266:generic:xtal=80,eesz=1M,FlashMode=qio,FlashFreq=80" IDE_VERSION=1.8.5
+    - CPU="esp8266" BOARD="esp8266com:esp8266:generic:xtal=80,eesz=1M,FlashMode=qio,FlashFreq=80" IDE_VERSION=1.8.9
+    - CPU="esp8266" BOARD="esp8266com:esp8266:generic:xtal=80,dbg=Serial1" IDE_VERSION=1.6.5
+    - CPU="esp32" BOARD="espressif:esp32:esp32:FlashFreq=80" IDE_VERSION=1.6.5
+    - CPU="esp32" BOARD="espressif:esp32:esp32:FlashFreq=80" IDE_VERSION=1.8.5
+    - CPU="esp32" BOARD="espressif:esp32:esp32:FlashFreq=80" IDE_VERSION=1.8.9
+
+script:
+  - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16
+  - export DISPLAY=:1.0
+  - sleep 3
+  - wget http://downloads.arduino.cc/arduino-$IDE_VERSION-linux64.tar.xz
+  - tar xf arduino-$IDE_VERSION-linux64.tar.xz
+  - mv arduino-$IDE_VERSION $HOME/arduino_ide
+  - export PATH="$HOME/arduino_ide:$PATH"
+  - which arduino
+  - mkdir -p $HOME/Arduino/libraries
+
+  - wget https://github.com/bblanchon/ArduinoJson/archive/6.x.zip
+  - unzip 6.x.zip
+  - mv ArduinoJson-6.x $HOME/Arduino/libraries/ArduinoJson
+  - cp -r $TRAVIS_BUILD_DIR $HOME/Arduino/libraries/arduinoWebSockets
+  - source $TRAVIS_BUILD_DIR/travis/common.sh
+  - get_core $CPU
+  - cd $TRAVIS_BUILD_DIR
+  - arduino --board $BOARD --save-prefs
+  - arduino --get-pref sketchbook.path
+  - arduino --pref update.check=false
+  - build_sketches arduino $HOME/Arduino/libraries/arduinoWebSockets/examples/$CPU $CPU
+
+notifications:
+  email:
+    on_success: change
+    on_failure: change
+  webhooks:
+    urls:
+      - https://webhooks.gitter.im/e/1aa78fbe15080b0c2e37
+    on_success: change  # options: [always|never|change] default: always
+    on_failure: always  # options: [always|never|change] default: always
+    on_start: false     # default: false

+ 502 - 0
lib/arduinoWebSockets-2.1.4/LICENSE

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

+ 98 - 0
lib/arduinoWebSockets-2.1.4/README.md

@@ -0,0 +1,98 @@
+WebSocket Server and Client for Arduino [![Build Status](https://travis-ci.org/Links2004/arduinoWebSockets.svg?branch=master)](https://travis-ci.org/Links2004/arduinoWebSockets)
+===========================================
+
+a WebSocket Server and Client for Arduino based on RFC6455.
+
+
+##### Supported features of RFC6455 #####
+ - text frame
+ - binary frame
+ - connection close
+ - ping
+ - pong
+ - continuation frame
+
+##### Limitations #####
+ - max input length is limited to the ram size and the ```WEBSOCKETS_MAX_DATA_SIZE``` define
+ - max output length has no limit (the hardware is the limit)
+ - Client send big frames with mask 0x00000000 (on AVR all frames)
+ - continuation frame reassembly need to be handled in the application code
+
+ ##### Limitations for Async #####
+ - Functions called from within the context of the websocket event might not honor `yield()` and/or `delay()`.  See [this issue](https://github.com/Links2004/arduinoWebSockets/issues/58#issuecomment-192376395) for more info and a potential workaround.
+ - wss / SSL is not possible.
+
+##### Supported Hardware #####
+ - ESP8266 [Arduino for ESP8266](https://github.com/esp8266/Arduino/)
+ - ESP32 [Arduino for ESP32](https://github.com/espressif/arduino-esp32)
+ - ESP31B
+ - Particle with STM32 ARM Cortex M3
+ - ATmega328 with Ethernet Shield (ATmega branch)
+ - ATmega328 with enc28j60 (ATmega branch)
+ - ATmega2560 with Ethernet Shield (ATmega branch)
+ - ATmega2560 with enc28j60 (ATmega branch)
+
+###### Note: ######
+
+  version 2.0 and up is not compatible with AVR/ATmega, check ATmega branch.
+
+  Arduino for AVR not supports std namespace of c++.
+
+### wss / SSL ###
+ supported for:
+ - wss client on the ESP8266
+ - wss / SSL is not natively supported in WebSocketsServer however it is possible to achieve secure websockets
+   by running the device behind an SSL proxy. See [Nginx](examples/Nginx/esp8266.ssl.reverse.proxy.conf) for a
+   sample Nginx server configuration file to enable this.
+
+### ESP Async TCP ###
+
+This libary can run in Async TCP mode on the ESP.
+
+The mode can be activated in the ```WebSockets.h``` (see WEBSOCKETS_NETWORK_TYPE define).
+
+[ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP) libary is required.
+
+
+### High Level Client API ###
+
+ - `begin` : Initiate connection sequence to the websocket host.
+```
+void begin(const char *host, uint16_t port, const char * url = "/", const char * protocol = "arduino");
+void begin(String host, uint16_t port, String url = "/", String protocol = "arduino");
+ ```
+ - `onEvent`: Callback to handle for websocket events
+
+ ```
+ void onEvent(WebSocketClientEvent cbEvent);
+ ```
+
+ - `WebSocketClientEvent`: Handler for websocket events
+ ```
+ void (*WebSocketClientEvent)(WStype_t type, uint8_t * payload, size_t length)
+ ```
+Where `WStype_t type` is defined as:
+  ```
+  typedef enum {
+      WStype_ERROR,
+      WStype_DISCONNECTED,
+      WStype_CONNECTED,
+      WStype_TEXT,
+      WStype_BIN,
+  	WStype_FRAGMENT_TEXT_START,
+  	WStype_FRAGMENT_BIN_START,
+  	WStype_FRAGMENT,
+  	WStype_FRAGMENT_FIN,
+  } WStype_t;
+  ```
+
+### Issues ###
+Submit issues to: https://github.com/Links2004/arduinoWebSockets/issues
+
+[![Join the chat at https://gitter.im/Links2004/arduinoWebSockets](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Links2004/arduinoWebSockets?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+### License and credits ###
+
+The library is licensed under [LGPLv2.1](https://github.com/Links2004/arduinoWebSockets/blob/master/LICENSE)
+
+[libb64](http://libb64.sourceforge.net/) written by Chris Venter. It is distributed under Public Domain see [LICENSE](https://github.com/Links2004/arduinoWebSockets/blob/master/src/libb64/LICENSE).

+ 83 - 0
lib/arduinoWebSockets-2.1.4/examples/Nginx/esp8266.ssl.reverse.proxy.conf

@@ -0,0 +1,83 @@
+# ESP8266 nginx SSL reverse proxy configuration file (tested and working on nginx v1.10.0)
+
+# proxy cache location
+proxy_cache_path /opt/etc/nginx/cache levels=1:2 keys_zone=ESP8266_cache:10m max_size=10g inactive=5m use_temp_path=off;
+
+# webserver proxy
+server {
+
+    # general server parameters
+    listen                      50080;
+    server_name                 myDomain.net;
+    access_log                  /opt/var/log/nginx/myDomain.net.access.log;       
+
+    # SSL configuration
+    ssl                         on;
+    ssl_certificate             /usr/builtin/etc/certificate/lets-encrypt/myDomain.net/fullchain.pem;
+    ssl_certificate_key         /usr/builtin/etc/certificate/lets-encrypt/myDomain.net/privkey.pem;
+    ssl_session_cache           builtin:1000  shared:SSL:10m;
+    ssl_protocols               TLSv1 TLSv1.1 TLSv1.2;
+    ssl_ciphers                 HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
+    ssl_prefer_server_ciphers   on;
+    
+    location / {
+
+      # proxy caching configuration
+      proxy_cache             ESP8266_cache;
+      proxy_cache_revalidate  on;
+      proxy_cache_min_uses    1;
+      proxy_cache_use_stale   off;
+      proxy_cache_lock        on;
+      # proxy_cache_bypass      $http_cache_control;      
+      # include the sessionId cookie value as part of the cache key - keeps the cache per user
+      # proxy_cache_key         $proxy_host$request_uri$cookie_sessionId;
+
+      # header pass through configuration
+      proxy_set_header        Host $host;      
+      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
+      proxy_set_header        X-Forwarded-Proto $scheme;      
+
+      # ESP8266 custom headers which identify to the device that it's running through an SSL proxy     
+      proxy_set_header        X-SSL On;
+      proxy_set_header        X-SSL-WebserverPort 50080;
+      proxy_set_header        X-SSL-WebsocketPort 50081;
+
+      # extra debug headers      
+      add_header              X-Proxy-Cache $upstream_cache_status;
+      add_header              X-Forwarded-For $proxy_add_x_forwarded_for;
+
+      # actual proxying configuration
+      proxy_ssl_session_reuse on;
+      # target the IP address of the device with proxy_pass
+      proxy_pass              http://192.168.0.20;
+      proxy_read_timeout      90;
+    }
+ }
+
+# websocket proxy
+server {
+
+    # general server parameters
+    listen                      50081;
+    server_name                 myDomain.net;
+    access_log                  /opt/var/log/nginx/myDomain.net.wss.access.log;
+
+    # SSL configuration
+    ssl                         on;
+    ssl_certificate             /usr/builtin/etc/certificate/lets-encrypt/myDomain.net/fullchain.pem;
+    ssl_certificate_key         /usr/builtin/etc/certificate/lets-encrypt/myDomain.net/privkey.pem;
+    ssl_session_cache           builtin:1000  shared:SSL:10m;
+    ssl_protocols               TLSv1 TLSv1.1 TLSv1.2;
+    ssl_ciphers                 HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
+    ssl_prefer_server_ciphers   on;
+    
+    location / {     
+
+      # websocket upgrade tunnel configuration
+      proxy_pass                    http://192.168.0.20:81;
+      proxy_http_version            1.1;
+      proxy_set_header Upgrade      $http_upgrade;
+      proxy_set_header Connection   "Upgrade";
+      proxy_read_timeout            86400;
+    }
+ }

+ 84 - 0
lib/arduinoWebSockets-2.1.4/examples/avr/WebSocketClientAVR/WebSocketClientAVR.ino

@@ -0,0 +1,84 @@
+/*
+ * WebSocketClientAVR.ino
+ *
+ *  Created on: 10.12.2015
+ *
+ */
+
+#include <Arduino.h>
+
+#include <SPI.h>
+#include <Ethernet.h>
+
+#include <WebSocketsClient.h>
+
+
+
+// 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);
+
+WebSocketsClient webSocket;
+
+
+
+void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
+
+
+    switch(type) {
+        case WStype_DISCONNECTED:
+            Serial.println("[WSc] Disconnected!\n");
+            break;
+        case WStype_CONNECTED:
+            {
+                Serial.print("[WSc] Connected to url: ");
+                Serial.println((char *)payload);
+                // send message to server when Connected
+                webSocket.sendTXT("Connected");
+            }
+            break;
+        case WStype_TEXT:
+            Serial.print("[WSc] get text: ");
+            Serial.println((char *)payload);
+            // send message to server
+            // webSocket.sendTXT("message here");
+            break;
+        case WStype_BIN:
+            Serial.print("[WSc] get binary length: ");
+            Serial.println(length);
+           // hexdump(payload, length);
+
+            // send data to server
+            // webSocket.sendBIN(payload, length);
+            break;
+    }
+
+}
+
+void setup()
+{
+    // Open serial communications and wait for port to open:
+    Serial.begin(115200);
+    while (!Serial) {}
+
+    // 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:
+      // try to congifure using IP address instead of DHCP:
+      Ethernet.begin(mac, ip);
+    }
+
+    webSocket.begin("192.168.0.123", 8011);
+    webSocket.onEvent(webSocketEvent);
+
+}
+
+
+void loop()
+{
+    webSocket.loop();
+}

+ 110 - 0
lib/arduinoWebSockets-2.1.4/examples/esp32/WebSocketClient/WebSocketClient.ino

@@ -0,0 +1,110 @@
+/*
+ * WebSocketClient.ino
+ *
+ *  Created on: 24.05.2015
+ *
+ */
+
+#include <Arduino.h>
+
+#include <WiFi.h>
+#include <WiFiMulti.h>
+#include <WiFiClientSecure.h>
+
+#include <WebSocketsClient.h>
+
+
+WiFiMulti WiFiMulti;
+WebSocketsClient webSocket;
+
+#define USE_SERIAL Serial1
+
+void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) {
+	const uint8_t* src = (const uint8_t*) mem;
+	USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
+	for(uint32_t i = 0; i < len; i++) {
+		if(i % cols == 0) {
+			USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
+		}
+		USE_SERIAL.printf("%02X ", *src);
+		src++;
+	}
+	USE_SERIAL.printf("\n");
+}
+
+void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
+
+	switch(type) {
+		case WStype_DISCONNECTED:
+			USE_SERIAL.printf("[WSc] Disconnected!\n");
+			break;
+		case WStype_CONNECTED:
+			USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
+
+			// send message to server when Connected
+			webSocket.sendTXT("Connected");
+			break;
+		case WStype_TEXT:
+			USE_SERIAL.printf("[WSc] get text: %s\n", payload);
+
+			// send message to server
+			// webSocket.sendTXT("message here");
+			break;
+		case WStype_BIN:
+			USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
+			hexdump(payload, length);
+
+			// send data to server
+			// webSocket.sendBIN(payload, length);
+			break;
+		case WStype_ERROR:			
+		case WStype_FRAGMENT_TEXT_START:
+		case WStype_FRAGMENT_BIN_START:
+		case WStype_FRAGMENT:
+		case WStype_FRAGMENT_FIN:
+			break;
+	}
+
+}
+
+void setup() {
+	// USE_SERIAL.begin(921600);
+	USE_SERIAL.begin(115200);
+
+	//Serial.setDebugOutput(true);
+	USE_SERIAL.setDebugOutput(true);
+
+	USE_SERIAL.println();
+	USE_SERIAL.println();
+	USE_SERIAL.println();
+
+	for(uint8_t t = 4; t > 0; t--) {
+		USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
+		USE_SERIAL.flush();
+		delay(1000);
+	}
+
+	WiFiMulti.addAP("SSID", "passpasspass");
+
+	//WiFi.disconnect();
+	while(WiFiMulti.run() != WL_CONNECTED) {
+		delay(100);
+	}
+
+	// server address, port and URL
+	webSocket.begin("192.168.0.123", 81, "/");
+
+	// event handler
+	webSocket.onEvent(webSocketEvent);
+
+	// use HTTP Basic Authorization this is optional remove if not needed
+	webSocket.setAuthorization("user", "Password");
+
+	// try ever 5000 again if connection has failed
+	webSocket.setReconnectInterval(5000);
+
+}
+
+void loop() {
+	webSocket.loop();
+}

+ 106 - 0
lib/arduinoWebSockets-2.1.4/examples/esp32/WebSocketClientSSL/WebSocketClientSSL.ino

@@ -0,0 +1,106 @@
+/*
+ * WebSocketClientSSL.ino
+ *
+ *  Created on: 10.12.2015
+ *
+ *  note SSL is only possible with the ESP8266
+ *
+ */
+
+#include <Arduino.h>
+
+#include <WiFi.h>
+#include <WiFiMulti.h>
+#include <WiFiClientSecure.h>
+
+#include <WebSocketsClient.h>
+
+
+WiFiMulti WiFiMulti;
+WebSocketsClient webSocket;
+
+#define USE_SERIAL Serial1
+
+void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) {
+	const uint8_t* src = (const uint8_t*) mem;
+	USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
+	for(uint32_t i = 0; i < len; i++) {
+		if(i % cols == 0) {
+			USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
+		}
+		USE_SERIAL.printf("%02X ", *src);
+		src++;
+	}
+	USE_SERIAL.printf("\n");
+}
+
+void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
+
+
+    switch(type) {
+        case WStype_DISCONNECTED:
+            USE_SERIAL.printf("[WSc] Disconnected!\n");
+            break;
+        case WStype_CONNECTED:
+            {
+                USE_SERIAL.printf("[WSc] Connected to url: %s\n",  payload);
+
+			    // send message to server when Connected
+				webSocket.sendTXT("Connected");
+            }
+            break;
+        case WStype_TEXT:
+            USE_SERIAL.printf("[WSc] get text: %s\n", payload);
+
+			// send message to server
+			// webSocket.sendTXT("message here");
+            break;
+        case WStype_BIN:
+            USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
+            hexdump(payload, length);
+
+            // send data to server
+            // webSocket.sendBIN(payload, length);
+            break;
+		case WStype_ERROR:			
+		case WStype_FRAGMENT_TEXT_START:
+		case WStype_FRAGMENT_BIN_START:
+		case WStype_FRAGMENT:
+		case WStype_FRAGMENT_FIN:
+			break;
+    }
+
+}
+
+void setup() {
+    // USE_SERIAL.begin(921600);
+    USE_SERIAL.begin(115200);
+
+    //Serial.setDebugOutput(true);
+    USE_SERIAL.setDebugOutput(true);
+
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+
+      for(uint8_t t = 4; t > 0; t--) {
+          USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
+          USE_SERIAL.flush();
+          delay(1000);
+      }
+
+    WiFiMulti.addAP("SSID", "passpasspass");
+
+    //WiFi.disconnect();
+    while(WiFiMulti.run() != WL_CONNECTED) {
+        delay(100);
+    }
+
+    webSocket.beginSSL("192.168.0.123", 81);
+    webSocket.onEvent(webSocketEvent);
+
+}
+
+void loop() {
+    webSocket.loop();
+}

+ 104 - 0
lib/arduinoWebSockets-2.1.4/examples/esp32/WebSocketServer/WebSocketServer.ino

@@ -0,0 +1,104 @@
+/*
+ * WebSocketServer.ino
+ *
+ *  Created on: 22.05.2015
+ *
+ */
+
+#include <Arduino.h>
+
+#include <WiFi.h>
+#include <WiFiMulti.h>
+#include <WiFiClientSecure.h>
+
+#include <WebSocketsServer.h>
+
+WiFiMulti WiFiMulti;
+WebSocketsServer webSocket = WebSocketsServer(81);
+
+#define USE_SERIAL Serial1
+
+void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) {
+	const uint8_t* src = (const uint8_t*) mem;
+	USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
+	for(uint32_t i = 0; i < len; i++) {
+		if(i % cols == 0) {
+			USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
+		}
+		USE_SERIAL.printf("%02X ", *src);
+		src++;
+	}
+	USE_SERIAL.printf("\n");
+}
+
+void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
+
+    switch(type) {
+        case WStype_DISCONNECTED:
+            USE_SERIAL.printf("[%u] Disconnected!\n", num);
+            break;
+        case WStype_CONNECTED:
+            {
+                IPAddress ip = webSocket.remoteIP(num);
+                USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
+
+				// send message to client
+				webSocket.sendTXT(num, "Connected");
+            }
+            break;
+        case WStype_TEXT:
+            USE_SERIAL.printf("[%u] get Text: %s\n", num, payload);
+
+            // send message to client
+            // webSocket.sendTXT(num, "message here");
+
+            // send data to all connected clients
+            // webSocket.broadcastTXT("message here");
+            break;
+        case WStype_BIN:
+            USE_SERIAL.printf("[%u] get binary length: %u\n", num, length);
+            hexdump(payload, length);
+
+            // send message to client
+            // webSocket.sendBIN(num, payload, length);
+            break;
+		case WStype_ERROR:			
+		case WStype_FRAGMENT_TEXT_START:
+		case WStype_FRAGMENT_BIN_START:
+		case WStype_FRAGMENT:
+		case WStype_FRAGMENT_FIN:
+			break;
+    }
+
+}
+
+void setup() {
+    // USE_SERIAL.begin(921600);
+    USE_SERIAL.begin(115200);
+
+    //Serial.setDebugOutput(true);
+    USE_SERIAL.setDebugOutput(true);
+
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+
+    for(uint8_t t = 4; t > 0; t--) {
+        USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
+        USE_SERIAL.flush();
+        delay(1000);
+    }
+
+    WiFiMulti.addAP("SSID", "passpasspass");
+
+    while(WiFiMulti.run() != WL_CONNECTED) {
+        delay(100);
+    }
+
+    webSocket.begin();
+    webSocket.onEvent(webSocketEvent);
+}
+
+void loop() {
+    webSocket.loop();
+}

+ 106 - 0
lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketClient/WebSocketClient.ino

@@ -0,0 +1,106 @@
+/*
+ * WebSocketClient.ino
+ *
+ *  Created on: 24.05.2015
+ *
+ */
+
+#include <Arduino.h>
+
+#include <ESP8266WiFi.h>
+#include <ESP8266WiFiMulti.h>
+
+#include <WebSocketsClient.h>
+
+#include <Hash.h>
+
+ESP8266WiFiMulti WiFiMulti;
+WebSocketsClient webSocket;
+
+#define USE_SERIAL Serial1
+
+void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
+
+	switch(type) {
+		case WStype_DISCONNECTED:
+			USE_SERIAL.printf("[WSc] Disconnected!\n");
+			break;
+		case WStype_CONNECTED: {
+			USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
+
+			// send message to server when Connected
+			webSocket.sendTXT("Connected");
+		}
+			break;
+		case WStype_TEXT:
+			USE_SERIAL.printf("[WSc] get text: %s\n", payload);
+
+			// send message to server
+			// webSocket.sendTXT("message here");
+			break;
+		case WStype_BIN:
+			USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
+			hexdump(payload, length);
+
+			// send data to server
+			// webSocket.sendBIN(payload, length);
+			break;
+        case WStype_PING:
+            // pong will be send automatically
+            USE_SERIAL.printf("[WSc] get ping\n");
+            break;
+        case WStype_PONG:
+            // answer to a ping we send
+            USE_SERIAL.printf("[WSc] get pong\n");
+            break;
+    }
+
+}
+
+void setup() {
+	// USE_SERIAL.begin(921600);
+	USE_SERIAL.begin(115200);
+
+	//Serial.setDebugOutput(true);
+	USE_SERIAL.setDebugOutput(true);
+
+	USE_SERIAL.println();
+	USE_SERIAL.println();
+	USE_SERIAL.println();
+
+	for(uint8_t t = 4; t > 0; t--) {
+		USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
+		USE_SERIAL.flush();
+		delay(1000);
+	}
+
+	WiFiMulti.addAP("SSID", "passpasspass");
+
+	//WiFi.disconnect();
+	while(WiFiMulti.run() != WL_CONNECTED) {
+		delay(100);
+	}
+
+	// server address, port and URL
+	webSocket.begin("192.168.0.123", 81, "/");
+
+	// event handler
+	webSocket.onEvent(webSocketEvent);
+
+	// use HTTP Basic Authorization this is optional remove if not needed
+	webSocket.setAuthorization("user", "Password");
+
+	// try ever 5000 again if connection has failed
+	webSocket.setReconnectInterval(5000);
+  
+  // start heartbeat (optional)
+  // ping server every 15000 ms
+  // expect pong from server within 3000 ms
+  // consider connection disconnected if pong is not received 2 times
+  webSocket.enableHeartbeat(15000, 3000, 2);
+
+}
+
+void loop() {
+	webSocket.loop();
+}

+ 88 - 0
lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketClientSSL/WebSocketClientSSL.ino

@@ -0,0 +1,88 @@
+/*
+ * WebSocketClientSSL.ino
+ *
+ *  Created on: 10.12.2015
+ *
+ *  note SSL is only possible with the ESP8266
+ *
+ */
+
+#include <Arduino.h>
+
+#include <ESP8266WiFi.h>
+#include <ESP8266WiFiMulti.h>
+
+#include <WebSocketsClient.h>
+
+#include <Hash.h>
+
+ESP8266WiFiMulti WiFiMulti;
+WebSocketsClient webSocket;
+
+
+#define USE_SERIAL Serial1
+
+void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
+
+
+    switch(type) {
+        case WStype_DISCONNECTED:
+            USE_SERIAL.printf("[WSc] Disconnected!\n");
+            break;
+        case WStype_CONNECTED:
+            {
+                USE_SERIAL.printf("[WSc] Connected to url: %s\n",  payload);
+				
+			    // send message to server when Connected
+				webSocket.sendTXT("Connected");
+            }
+            break;
+        case WStype_TEXT:
+            USE_SERIAL.printf("[WSc] get text: %s\n", payload);
+
+			// send message to server
+			// webSocket.sendTXT("message here");
+            break;
+        case WStype_BIN:
+            USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
+            hexdump(payload, length);
+
+            // send data to server
+            // webSocket.sendBIN(payload, length);
+            break;
+    }
+
+}
+
+void setup() {
+    // USE_SERIAL.begin(921600);
+    USE_SERIAL.begin(115200);
+
+    //Serial.setDebugOutput(true);
+    USE_SERIAL.setDebugOutput(true);
+
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+
+      for(uint8_t t = 4; t > 0; t--) {
+          USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
+          USE_SERIAL.flush();
+          delay(1000);
+      }
+
+    WiFiMulti.addAP("SSID", "passpasspass");
+
+    //WiFi.disconnect();
+    while(WiFiMulti.run() != WL_CONNECTED) {
+        delay(100);
+    }
+
+    webSocket.beginSSL("192.168.0.123", 81);
+    webSocket.onEvent(webSocketEvent);
+
+}
+
+void loop() {
+    webSocket.loop();
+}

+ 125 - 0
lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketClientSocketIO/WebSocketClientSocketIO.ino

@@ -0,0 +1,125 @@
+/*
+ * WebSocketClientSocketIO.ino
+ *
+ *  Created on: 06.06.2016
+ *
+ */
+
+#include <Arduino.h>
+
+#include <ESP8266WiFi.h>
+#include <ESP8266WiFiMulti.h>
+
+#include <ArduinoJson.h>
+
+#include <WebSocketsClient.h>
+#include <SocketIOclient.h>
+
+#include <Hash.h>
+
+ESP8266WiFiMulti WiFiMulti;
+SocketIOclient socketIO;
+
+#define USE_SERIAL Serial1
+
+void socketIOEvent(socketIOmessageType_t type, uint8_t * payload, size_t length) {
+    switch(type) {
+        case sIOtype_DISCONNECT:
+            USE_SERIAL.printf("[IOc] Disconnected!\n");
+            break;
+        case sIOtype_CONNECT:
+            USE_SERIAL.printf("[IOc] Connected to url: %s\n", payload);
+            break;
+        case sIOtype_EVENT:
+            USE_SERIAL.printf("[IOc] get event: %s\n", payload);
+            break;
+        case sIOtype_ACK:
+            USE_SERIAL.printf("[IOc] get ack: %u\n", length);
+            hexdump(payload, length);
+            break;
+        case sIOtype_ERROR:
+            USE_SERIAL.printf("[IOc] get error: %u\n", length);
+            hexdump(payload, length);
+            break;
+        case sIOtype_BINARY_EVENT:
+            USE_SERIAL.printf("[IOc] get binary: %u\n", length);
+            hexdump(payload, length);
+            break;
+        case sIOtype_BINARY_ACK:
+            USE_SERIAL.printf("[IOc] get binary ack: %u\n", length);
+            hexdump(payload, length);
+            break;
+    }
+}
+
+void setup() {
+    // USE_SERIAL.begin(921600);
+    USE_SERIAL.begin(115200);
+
+    //Serial.setDebugOutput(true);
+    USE_SERIAL.setDebugOutput(true);
+
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+
+      for(uint8_t t = 4; t > 0; t--) {
+          USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
+          USE_SERIAL.flush();
+          delay(1000);
+      }
+
+    // disable AP
+    if(WiFi.getMode() & WIFI_AP) {
+        WiFi.softAPdisconnect(true);
+    }
+
+    WiFiMulti.addAP("SSID", "passpasspass");
+
+    //WiFi.disconnect();
+    while(WiFiMulti.run() != WL_CONNECTED) {
+        delay(100);
+    }
+
+    String ip = WiFi.localIP().toString();
+    USE_SERIAL.printf("[SETUP] WiFi Connected %s\n", ip.c_str());
+
+    // server address, port and URL
+    socketIO.begin("10.11.100.100", 8880);
+
+    // event handler
+    socketIO.onEvent(socketIOEvent);
+}
+
+unsigned long messageTimestamp = 0;
+void loop() {
+    socketIO.loop();
+
+    uint64_t now = millis();
+
+    if(now - messageTimestamp > 2000) {
+        messageTimestamp = now;
+
+        // creat JSON message for Socket.IO (event)
+        DynamicJsonDocument doc(1024);
+        JsonArray array = doc.to<JsonArray>();
+        
+        // add evnet name
+        // Hint: socket.on('event_name', ....
+        array.add("event_name");
+
+        // add payload (parameters) for the event
+        JsonObject param1 = array.createNestedObject();
+        param1["now"] = now;
+
+        // JSON to String (serializion)
+        String output;
+        serializeJson(doc, output);
+
+        // Send event        
+        socketIO.sendEVENT(output);
+
+        // Print JSON for debugging
+        USE_SERIAL.println(output);
+    }
+}

+ 149 - 0
lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketClientStomp/WebSocketClientStomp.ino

@@ -0,0 +1,149 @@
+/*
+    WebSocketClientStomp.ino
+
+    Example for connecting and maintining a connection with a STOMP websocket connection.
+    In this example, we connect to a Spring application (see https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html).
+
+    Created on: 25.09.2017
+    Author: Martin Becker <mgbckr>, Contact: becker@informatik.uni-wuerzburg.de
+*/
+
+// PRE
+
+#define USE_SERIAL Serial
+
+
+// LIBRARIES
+
+#include <Arduino.h>
+#include <Hash.h>
+
+#include <ESP8266WiFi.h>
+#include <WebSocketsClient.h>
+
+
+// SETTINGS
+
+const char* wlan_ssid             = "yourssid";
+const char* wlan_password         = "somepassword";
+
+const char* ws_host               = "the.host.net";
+const int   ws_port               = 80;
+
+// URL for STOMP endpoint.
+// For the default config of Spring's STOMP support, the default URL is "/socketentry/websocket".
+const char* stompUrl            = "/socketentry/websocket"; // don't forget the leading "/" !!!
+
+
+// VARIABLES
+
+WebSocketsClient webSocket;
+
+
+// FUNCTIONS
+
+/**
+ * STOMP messages need to be NULL-terminated (i.e., \0 or \u0000).
+ * However, when we send a String or a char[] array without specifying 
+ * a length, the size of the message payload is derived by strlen() internally,
+ * thus dropping any NULL values appended to the "msg"-String.
+ * 
+ * To solve this, we first convert the String to a NULL terminated char[] array
+ * via "c_str" and set the length of the payload to include the NULL value.
+ */
+void sendMessage(String & msg) {
+    webSocket.sendTXT(msg.c_str(), msg.length() + 1);
+}
+
+void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
+
+    switch (type) {
+        case WStype_DISCONNECTED:
+            USE_SERIAL.printf("[WSc] Disconnected!\n");
+            break;
+        case WStype_CONNECTED:
+            {
+                USE_SERIAL.printf("[WSc] Connected to url: %s\n",  payload);
+                
+                String msg = "CONNECT\r\naccept-version:1.1,1.0\r\nheart-beat:10000,10000\r\n\r\n";
+                sendMessage(msg);
+            }
+            break;
+        case WStype_TEXT:
+            {
+                // #####################
+                // handle STOMP protocol
+                // #####################
+
+                String text = (char*) payload;
+                USE_SERIAL.printf("[WSc] get text: %s\n", payload);
+
+                if (text.startsWith("CONNECTED")) {
+
+                    // subscribe to some channels
+
+                    String msg = "SUBSCRIBE\nid:sub-0\ndestination:/user/queue/messages\n\n";
+                    sendMessage(msg);
+                    delay(1000);
+
+                    // and send a message
+
+                    msg = "SEND\ndestination:/app/message\n\n{\"user\":\"esp\",\"message\":\"Hello!\"}";
+                    sendMessage(msg);
+                    delay(1000);
+                    
+                } else {
+
+                    // do something with messages
+                    
+                }
+
+                break;
+            }
+        case WStype_BIN:
+            USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
+            hexdump(payload, length);
+
+            // send data to server
+            // webSocket.sendBIN(payload, length);
+            break;
+    }
+
+}
+
+void setup() {
+
+    // setup serial
+
+    // USE_SERIAL.begin(921600);
+    USE_SERIAL.begin(115200);
+
+    //    USE_SERIAL.setDebugOutput(true);
+
+    USE_SERIAL.println();
+
+
+    // connect to WiFi
+
+    USE_SERIAL.print("Logging into WLAN: "); Serial.print(wlan_ssid); Serial.print(" ...");
+    WiFi.mode(WIFI_STA);
+    WiFi.begin(wlan_ssid, wlan_password);
+
+    while (WiFi.status() != WL_CONNECTED) {
+        delay(500);
+        USE_SERIAL.print(".");
+    }
+    USE_SERIAL.println(" success.");
+    USE_SERIAL.print("IP: "); USE_SERIAL.println(WiFi.localIP());
+
+
+    // connect to websocket
+    webSocket.begin(ws_host, ws_port, stompUrl);
+    webSocket.setExtraHeaders(); // remove "Origin: file://" header because it breaks the connection with Spring's default websocket config
+    //    webSocket.setExtraHeaders("foo: I am so funny\r\nbar: not"); // some headers, in case you feel funny
+    webSocket.onEvent(webSocketEvent);
+}
+
+void loop() {
+    webSocket.loop();
+}

+ 150 - 0
lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketClientStompOverSockJs/WebSocketClientStompOverSockJs.ino

@@ -0,0 +1,150 @@
+/*
+    WebSocketClientStompOverSockJs.ino
+
+    Example for connecting and maintining a connection with a SockJS+STOMP websocket connection.
+    In this example, we connect to a Spring application (see https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html).
+
+    Created on: 18.07.2017
+    Author: Martin Becker <mgbckr>, Contact: becker@informatik.uni-wuerzburg.de
+*/
+
+// PRE
+
+#define USE_SERIAL Serial
+
+
+// LIBRARIES
+
+#include <Arduino.h>
+#include <Hash.h>
+
+#include <ESP8266WiFi.h>
+#include <WebSocketsClient.h>
+
+
+// SETTINGS
+
+const char* wlan_ssid             = "yourssid";
+const char* wlan_password         = "somepassword";
+
+const char* ws_host               = "the.host.net";
+const int   ws_port               = 80;
+
+// base URL for SockJS (websocket) connection
+// The complete URL will look something like this(cf. http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-36):
+// ws://<ws_host>:<ws_port>/<ws_baseurl>/<3digits>/<randomstring>/websocket
+// For the default config of Spring's SockJS/STOMP support, the default base URL is "/socketentry/".
+const char* ws_baseurl            = "/socketentry/"; // don't forget leading and trailing "/" !!!
+
+
+// VARIABLES
+
+WebSocketsClient webSocket;
+
+
+// FUNCTIONS
+
+void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
+
+    switch (type) {
+        case WStype_DISCONNECTED:
+            USE_SERIAL.printf("[WSc] Disconnected!\n");
+            break;
+        case WStype_CONNECTED:
+            {
+                USE_SERIAL.printf("[WSc] Connected to url: %s\n",  payload);
+            }
+            break;
+        case WStype_TEXT:
+            {
+                // #####################
+                // handle SockJs+STOMP protocol
+                // #####################
+
+                String text = (char*) payload;
+
+                USE_SERIAL.printf("[WSc] get text: %s\n", payload);
+
+                if (payload[0] == 'h') {
+
+                    USE_SERIAL.println("Heartbeat!");
+
+                } else if (payload[0] == 'o') {
+
+                    // on open connection
+                    char *msg = "[\"CONNECT\\naccept-version:1.1,1.0\\nheart-beat:10000,10000\\n\\n\\u0000\"]";
+                    webSocket.sendTXT(msg);
+
+                } else if (text.startsWith("a[\"CONNECTED")) {
+
+                    // subscribe to some channels
+
+                    char *msg = "[\"SUBSCRIBE\\nid:sub-0\\ndestination:/user/queue/messages\\n\\n\\u0000\"]";
+                    webSocket.sendTXT(msg);
+                    delay(1000);
+
+                    // and send a message
+
+                    msg = "[\"SEND\\ndestination:/app/message\\n\\n{\\\"user\\\":\\\"esp\\\",\\\"message\\\":\\\"Hello!\\\"}\\u0000\"]";
+                    webSocket.sendTXT(msg);
+                    delay(1000);
+                }
+
+                break;
+            }
+        case WStype_BIN:
+            USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
+            hexdump(payload, length);
+
+            // send data to server
+            // webSocket.sendBIN(payload, length);
+            break;
+    }
+
+}
+
+void setup() {
+
+    // setup serial
+
+    // USE_SERIAL.begin(921600);
+    USE_SERIAL.begin(115200);
+
+    //    USE_SERIAL.setDebugOutput(true);
+
+    USE_SERIAL.println();
+
+
+    // connect to WiFi
+
+    USE_SERIAL.print("Logging into WLAN: "); Serial.print(wlan_ssid); Serial.print(" ...");
+    WiFi.mode(WIFI_STA);
+    WiFi.begin(wlan_ssid, wlan_password);
+
+    while (WiFi.status() != WL_CONNECTED) {
+        delay(500);
+        USE_SERIAL.print(".");
+    }
+    USE_SERIAL.println(" success.");
+    USE_SERIAL.print("IP: "); USE_SERIAL.println(WiFi.localIP());
+
+
+    // #####################
+    // create socket url according to SockJS protocol (cf. http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-36)
+    // #####################
+    String socketUrl = ws_baseurl;
+    socketUrl += random(0, 999);
+    socketUrl += "/";
+    socketUrl += random(0, 999999); // should be a random string, but this works (see )
+    socketUrl += "/websocket";
+
+    // connect to websocket
+    webSocket.begin(ws_host, ws_port, socketUrl);
+    webSocket.setExtraHeaders(); // remove "Origin: file://" header because it breaks the connection with Spring's default websocket config
+    //    webSocket.setExtraHeaders("foo: I am so funny\r\nbar: not"); // some headers, in case you feel funny
+    webSocket.onEvent(webSocketEvent);
+}
+
+void loop() {
+    webSocket.loop();
+}

+ 86 - 0
lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketServer/WebSocketServer.ino

@@ -0,0 +1,86 @@
+/*
+ * WebSocketServer.ino
+ *
+ *  Created on: 22.05.2015
+ *
+ */
+
+#include <Arduino.h>
+
+#include <ESP8266WiFi.h>
+#include <ESP8266WiFiMulti.h>
+#include <WebSocketsServer.h>
+#include <Hash.h>
+
+ESP8266WiFiMulti WiFiMulti;
+
+WebSocketsServer webSocket = WebSocketsServer(81);
+
+#define USE_SERIAL Serial1
+
+void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
+
+    switch(type) {
+        case WStype_DISCONNECTED:
+            USE_SERIAL.printf("[%u] Disconnected!\n", num);
+            break;
+        case WStype_CONNECTED:
+            {
+                IPAddress ip = webSocket.remoteIP(num);
+                USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
+				
+				// send message to client
+				webSocket.sendTXT(num, "Connected");
+            }
+            break;
+        case WStype_TEXT:
+            USE_SERIAL.printf("[%u] get Text: %s\n", num, payload);
+
+            // send message to client
+            // webSocket.sendTXT(num, "message here");
+
+            // send data to all connected clients
+            // webSocket.broadcastTXT("message here");
+            break;
+        case WStype_BIN:
+            USE_SERIAL.printf("[%u] get binary length: %u\n", num, length);
+            hexdump(payload, length);
+
+            // send message to client
+            // webSocket.sendBIN(num, payload, length);
+            break;
+    }
+
+}
+
+void setup() {
+    // USE_SERIAL.begin(921600);
+    USE_SERIAL.begin(115200);
+
+    //Serial.setDebugOutput(true);
+    USE_SERIAL.setDebugOutput(true);
+
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+
+    for(uint8_t t = 4; t > 0; t--) {
+        USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
+        USE_SERIAL.flush();
+        delay(1000);
+    }
+
+    WiFiMulti.addAP("SSID", "passpasspass");
+
+    while(WiFiMulti.run() != WL_CONNECTED) {
+        delay(100);
+    }
+
+    webSocket.begin();
+    webSocket.onEvent(webSocketEvent);
+}
+
+void loop() {
+    webSocket.loop();
+}
+

+ 132 - 0
lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketServerAllFunctionsDemo/WebSocketServerAllFunctionsDemo.ino

@@ -0,0 +1,132 @@
+/*
+ * WebSocketServerAllFunctionsDemo.ino
+ *
+ *  Created on: 10.05.2018
+ *
+ */
+
+#include <Arduino.h>
+
+#include <ESP8266WiFi.h>
+#include <ESP8266WiFiMulti.h>
+#include <WebSocketsServer.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+#include <Hash.h>
+
+#define LED_RED     15
+#define LED_GREEN   12
+#define LED_BLUE    13
+
+#define USE_SERIAL Serial
+
+ESP8266WiFiMulti WiFiMulti;
+
+ESP8266WebServer server(80);
+WebSocketsServer webSocket = WebSocketsServer(81);
+
+void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
+
+    switch(type) {
+        case WStype_DISCONNECTED:
+            USE_SERIAL.printf("[%u] Disconnected!\n", num);
+            break;
+        case WStype_CONNECTED: {
+            IPAddress ip = webSocket.remoteIP(num);
+            USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
+
+            // send message to client
+            webSocket.sendTXT(num, "Connected");
+        }
+            break;
+        case WStype_TEXT:
+            USE_SERIAL.printf("[%u] get Text: %s\n", num, payload);
+
+            if(payload[0] == '#') {
+                // we get RGB data
+
+                // decode rgb data
+                uint32_t rgb = (uint32_t) strtol((const char *) &payload[1], NULL, 16);
+
+                analogWrite(LED_RED, ((rgb >> 16) & 0xFF));
+                analogWrite(LED_GREEN, ((rgb >> 8) & 0xFF));
+                analogWrite(LED_BLUE, ((rgb >> 0) & 0xFF));
+            }
+
+            break;
+    }
+
+}
+
+void setup() {
+    //USE_SERIAL.begin(921600);
+    USE_SERIAL.begin(115200);
+
+    //USE_SERIAL.setDebugOutput(true);
+
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+
+    for(uint8_t t = 4; t > 0; t--) {
+        USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
+        USE_SERIAL.flush();
+        delay(1000);
+    }
+
+    pinMode(LED_RED, OUTPUT);
+    pinMode(LED_GREEN, OUTPUT);
+    pinMode(LED_BLUE, OUTPUT);
+
+    digitalWrite(LED_RED, 1);
+    digitalWrite(LED_GREEN, 1);
+    digitalWrite(LED_BLUE, 1);
+
+    WiFiMulti.addAP("SSID", "passpasspass");
+
+    while(WiFiMulti.run() != WL_CONNECTED) {
+        delay(100);
+    }
+
+    // start webSocket server
+    webSocket.begin();
+    webSocket.onEvent(webSocketEvent);
+
+    if(MDNS.begin("esp8266")) {
+        USE_SERIAL.println("MDNS responder started");
+    }
+
+    // handle index
+    server.on("/", []() {
+        // send index.html
+        server.send(200, "text/html", "<html><head><script>var connection = new WebSocket('ws://'+location.hostname+':81/', ['arduino']);connection.onopen = function () {  connection.send('Connect ' + new Date()); }; connection.onerror = function (error) {    console.log('WebSocket Error ', error);};connection.onmessage = function (e) {  console.log('Server: ', e.data);};function sendRGB() {  var r = parseInt(document.getElementById('r').value).toString(16);  var g = parseInt(document.getElementById('g').value).toString(16);  var b = parseInt(document.getElementById('b').value).toString(16);  if(r.length < 2) { r = '0' + r; }   if(g.length < 2) { g = '0' + g; }   if(b.length < 2) { b = '0' + b; }   var rgb = '#'+r+g+b;    console.log('RGB: ' + rgb); connection.send(rgb); }</script></head><body>LED Control:<br/><br/>R: <input id=\"r\" type=\"range\" min=\"0\" max=\"255\" step=\"1\" oninput=\"sendRGB();\" /><br/>G: <input id=\"g\" type=\"range\" min=\"0\" max=\"255\" step=\"1\" oninput=\"sendRGB();\" /><br/>B: <input id=\"b\" type=\"range\" min=\"0\" max=\"255\" step=\"1\" oninput=\"sendRGB();\" /><br/></body></html>");
+    });
+
+    server.begin();
+
+    // Add service to MDNS
+    MDNS.addService("http", "tcp", 80);
+    MDNS.addService("ws", "tcp", 81);
+
+    digitalWrite(LED_RED, 0);
+    digitalWrite(LED_GREEN, 0);
+    digitalWrite(LED_BLUE, 0);
+
+}
+
+unsigned long last_10sec = 0;
+unsigned int counter = 0;
+
+void loop() {
+    unsigned long t = millis();
+    webSocket.loop();
+    server.handleClient();
+
+    if((t - last_10sec) > 10 * 1000) {
+        counter++;
+        bool ping = (counter % 2);
+        int i = webSocket.connectedClients(ping);
+        USE_SERIAL.printf("%d Connected websocket clients ping: %d\n", i, ping);
+        last_10sec = millis();
+    }
+}

+ 94 - 0
lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketServerFragmentation/WebSocketServerFragmentation.ino

@@ -0,0 +1,94 @@
+/*
+ * WebSocketServer.ino
+ *
+ *  Created on: 22.05.2015
+ *
+ */
+
+#include <Arduino.h>
+
+#include <ESP8266WiFi.h>
+#include <ESP8266WiFiMulti.h>
+#include <WebSocketsServer.h>
+#include <Hash.h>
+
+ESP8266WiFiMulti WiFiMulti;
+
+WebSocketsServer webSocket = WebSocketsServer(81);
+
+#define USE_SERIAL Serial
+
+String fragmentBuffer = "";
+
+void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
+
+	switch(type) {
+		case WStype_DISCONNECTED:
+			USE_SERIAL.printf("[%u] Disconnected!\n", num);
+			break;
+		case WStype_CONNECTED: {
+			IPAddress ip = webSocket.remoteIP(num);
+			USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
+
+			// send message to client
+			webSocket.sendTXT(num, "Connected");
+		}
+			break;
+		case WStype_TEXT:
+			USE_SERIAL.printf("[%u] get Text: %s\n", num, payload);
+			break;
+		case WStype_BIN:
+			USE_SERIAL.printf("[%u] get binary length: %u\n", num, length);
+			hexdump(payload, length);
+			break;
+
+		// Fragmentation / continuation opcode handling
+		// case WStype_FRAGMENT_BIN_START:
+		case WStype_FRAGMENT_TEXT_START:
+			fragmentBuffer = (char*)payload;
+			USE_SERIAL.printf("[%u] get start start of Textfragment: %s\n", num, payload);
+			break;
+		case WStype_FRAGMENT:
+			fragmentBuffer += (char*)payload;
+			USE_SERIAL.printf("[%u] get Textfragment : %s\n", num, payload);
+			break;
+		case WStype_FRAGMENT_FIN:
+			fragmentBuffer += (char*)payload;
+			USE_SERIAL.printf("[%u] get end of Textfragment: %s\n", num, payload);
+			USE_SERIAL.printf("[%u] full frame: %s\n", num, fragmentBuffer.c_str());
+			break;
+	}
+
+}
+
+void setup() {
+	// USE_SERIAL.begin(921600);
+	USE_SERIAL.begin(115200);
+
+	//Serial.setDebugOutput(true);
+	USE_SERIAL.setDebugOutput(true);
+
+	USE_SERIAL.println();
+	USE_SERIAL.println();
+	USE_SERIAL.println();
+
+	for(uint8_t t = 4; t > 0; t--) {
+		USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
+		USE_SERIAL.flush();
+		delay(1000);
+	}
+
+	WiFiMulti.addAP("SSID", "passpasspass");
+
+	while(WiFiMulti.run() != WL_CONNECTED) {
+		delay(100);
+	}
+
+	webSocket.begin();
+	webSocket.onEvent(webSocketEvent);
+}
+
+void loop() {
+	webSocket.loop();
+}
+

+ 86 - 0
lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketServerHttpHeaderValidation/WebSocketServerHttpHeaderValidation.ino

@@ -0,0 +1,86 @@
+/*
+ * WebSocketServerHttpHeaderValidation.ino
+ *
+ *  Created on: 08.06.2016
+ *
+ */
+
+#include <Arduino.h>
+
+#include <ESP8266WiFi.h>
+#include <ESP8266WiFiMulti.h>
+#include <WebSocketsServer.h>
+#include <Hash.h>
+
+ESP8266WiFiMulti WiFiMulti;
+
+WebSocketsServer webSocket = WebSocketsServer(81);
+
+#define USE_SERIAL Serial1
+
+const unsigned long int validSessionId = 12345; //some arbitrary value to act as a valid sessionId
+
+/*
+ * Returns a bool value as an indicator to describe whether a user is allowed to initiate a websocket upgrade
+ * based on the value of a cookie. This function expects the rawCookieHeaderValue to look like this "sessionId=<someSessionIdNumberValue>|"
+ */
+bool isCookieValid(String rawCookieHeaderValue) {
+
+	if (rawCookieHeaderValue.indexOf("sessionId") != -1) {
+		String sessionIdStr = rawCookieHeaderValue.substring(rawCookieHeaderValue.indexOf("sessionId=") + 10, rawCookieHeaderValue.indexOf("|"));
+		unsigned long int sessionId = strtoul(sessionIdStr.c_str(), NULL, 10);
+		return sessionId == validSessionId;
+	}
+	return false;
+}
+
+/*
+ * The WebSocketServerHttpHeaderValFunc delegate passed to webSocket.onValidateHttpHeader
+ */
+bool validateHttpHeader(String headerName, String headerValue) {
+
+	//assume a true response for any headers not handled by this validator
+	bool valid = true;
+
+	if(headerName.equalsIgnoreCase("Cookie")) {
+		//if the header passed is the Cookie header, validate it according to the rules in 'isCookieValid' function
+		valid = isCookieValid(headerValue);
+	}
+
+	return valid;
+}
+
+void setup() {
+    // USE_SERIAL.begin(921600);
+    USE_SERIAL.begin(115200);
+
+    //Serial.setDebugOutput(true);
+    USE_SERIAL.setDebugOutput(true);
+
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+
+    for(uint8_t t = 4; t > 0; t--) {
+        USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
+        USE_SERIAL.flush();
+        delay(1000);
+    }
+
+    WiFiMulti.addAP("SSID", "passpasspass");
+
+    while(WiFiMulti.run() != WL_CONNECTED) {
+        delay(100);
+    }
+
+    //connecting clients must supply a valid session cookie at websocket upgrade handshake negotiation time
+    const char * headerkeys[] = { "Cookie" };
+    size_t headerKeyCount = sizeof(headerkeys) / sizeof(char*);
+    webSocket.onValidateHttpHeader(validateHttpHeader, headerkeys, headerKeyCount);
+    webSocket.begin();
+}
+
+void loop() {
+    webSocket.loop();
+}
+

+ 121 - 0
lib/arduinoWebSockets-2.1.4/examples/esp8266/WebSocketServer_LEDcontrol/WebSocketServer_LEDcontrol.ino

@@ -0,0 +1,121 @@
+/*
+ * WebSocketServer_LEDcontrol.ino
+ *
+ *  Created on: 26.11.2015
+ *
+ */
+
+#include <Arduino.h>
+
+#include <ESP8266WiFi.h>
+#include <ESP8266WiFiMulti.h>
+#include <WebSocketsServer.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+#include <Hash.h>
+
+#define LED_RED     15
+#define LED_GREEN   12
+#define LED_BLUE    13
+
+#define USE_SERIAL Serial
+
+
+ESP8266WiFiMulti WiFiMulti;
+
+ESP8266WebServer server(80);
+WebSocketsServer webSocket = WebSocketsServer(81);
+
+void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
+
+    switch(type) {
+        case WStype_DISCONNECTED:
+            USE_SERIAL.printf("[%u] Disconnected!\n", num);
+            break;
+        case WStype_CONNECTED: {
+            IPAddress ip = webSocket.remoteIP(num);
+            USE_SERIAL.printf("[%u] Connected from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
+
+            // send message to client
+            webSocket.sendTXT(num, "Connected");
+        }
+            break;
+        case WStype_TEXT:
+            USE_SERIAL.printf("[%u] get Text: %s\n", num, payload);
+
+            if(payload[0] == '#') {
+                // we get RGB data
+
+                // decode rgb data
+                uint32_t rgb = (uint32_t) strtol((const char *) &payload[1], NULL, 16);
+
+                analogWrite(LED_RED,    ((rgb >> 16) & 0xFF));
+                analogWrite(LED_GREEN,  ((rgb >> 8) & 0xFF));
+                analogWrite(LED_BLUE,   ((rgb >> 0) & 0xFF));
+            }
+
+            break;
+    }
+
+}
+
+void setup() {
+    //USE_SERIAL.begin(921600);
+    USE_SERIAL.begin(115200);
+
+    //USE_SERIAL.setDebugOutput(true);
+
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+    USE_SERIAL.println();
+
+    for(uint8_t t = 4; t > 0; t--) {
+        USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
+        USE_SERIAL.flush();
+        delay(1000);
+    }
+
+    pinMode(LED_RED, OUTPUT);
+    pinMode(LED_GREEN, OUTPUT);
+    pinMode(LED_BLUE, OUTPUT);
+
+    digitalWrite(LED_RED, 1);
+    digitalWrite(LED_GREEN, 1);
+    digitalWrite(LED_BLUE, 1);
+
+    WiFiMulti.addAP("SSID", "passpasspass");
+
+    while(WiFiMulti.run() != WL_CONNECTED) {
+        delay(100);
+    }
+
+    // start webSocket server
+    webSocket.begin();
+    webSocket.onEvent(webSocketEvent);
+
+    if(MDNS.begin("esp8266")) {
+        USE_SERIAL.println("MDNS responder started");
+    }
+
+    // handle index
+    server.on("/", []() {
+        // send index.html
+        server.send(200, "text/html", "<html><head><script>var connection = new WebSocket('ws://'+location.hostname+':81/', ['arduino']);connection.onopen = function () {  connection.send('Connect ' + new Date()); }; connection.onerror = function (error) {    console.log('WebSocket Error ', error);};connection.onmessage = function (e) {  console.log('Server: ', e.data);};function sendRGB() {  var r = parseInt(document.getElementById('r').value).toString(16);  var g = parseInt(document.getElementById('g').value).toString(16);  var b = parseInt(document.getElementById('b').value).toString(16);  if(r.length < 2) { r = '0' + r; }   if(g.length < 2) { g = '0' + g; }   if(b.length < 2) { b = '0' + b; }   var rgb = '#'+r+g+b;    console.log('RGB: ' + rgb); connection.send(rgb); }</script></head><body>LED Control:<br/><br/>R: <input id=\"r\" type=\"range\" min=\"0\" max=\"255\" step=\"1\" oninput=\"sendRGB();\" /><br/>G: <input id=\"g\" type=\"range\" min=\"0\" max=\"255\" step=\"1\" oninput=\"sendRGB();\" /><br/>B: <input id=\"b\" type=\"range\" min=\"0\" max=\"255\" step=\"1\" oninput=\"sendRGB();\" /><br/></body></html>");
+    });
+
+    server.begin();
+
+    // Add service to MDNS
+    MDNS.addService("http", "tcp", 80);
+    MDNS.addService("ws", "tcp", 81);
+
+    digitalWrite(LED_RED, 0);
+    digitalWrite(LED_GREEN, 0);
+    digitalWrite(LED_BLUE, 0);
+
+}
+
+void loop() {
+    webSocket.loop();
+    server.handleClient();
+}

+ 46 - 0
lib/arduinoWebSockets-2.1.4/examples/particle/ParticleWebSocketClient/application.cpp

@@ -0,0 +1,46 @@
+/* To compile using make CLI, create a folder under \firmware\user\applications and copy application.cpp there.
+*  Then, copy src files under particleWebSocket folder.
+*/
+
+#include "application.h"
+#include "particleWebSocket/WebSocketsClient.h"
+
+WebSocketsClient webSocket;
+
+void webSocketEvent(WStype_t type, uint8_t* payload, size_t length)
+{
+	switch (type)
+	{
+	case WStype_DISCONNECTED:
+		Serial.printlnf("[WSc] Disconnected!");
+		break;
+	case WStype_CONNECTED:
+		Serial.printlnf("[WSc] Connected to URL: %s", payload);
+		webSocket.sendTXT("Connected\r\n");
+		break;
+	case WStype_TEXT:
+		Serial.printlnf("[WSc] get text: %s", payload);
+		break;
+	case WStype_BIN:
+		Serial.printlnf("[WSc] get binary length: %u", length);
+		break;
+	}
+}
+
+void setup()
+{
+	Serial.begin(9600);
+	
+	WiFi.setCredentials("[SSID]", "[PASSWORD]", WPA2, WLAN_CIPHER_AES_TKIP);
+	WiFi.connect();
+	    
+	webSocket.begin("192.168.1.153", 85, "/ClientService/?variable=Test1212");
+	webSocket.onEvent(webSocketEvent);
+}
+
+void loop()
+{
+	webSocket.sendTXT("Hello world!");
+	delay(500);
+	webSocket.loop();
+}

+ 25 - 0
lib/arduinoWebSockets-2.1.4/library.json

@@ -0,0 +1,25 @@
+{
+    "name": "WebSockets",
+    "description": "WebSocket Server and Client for Arduino based on RFC6455",
+    "keywords": "wifi, http, web, server, client, websocket",
+    "authors": [
+        {
+            "name": "Markus Sattler",
+            "url": "https://github.com/Links2004",
+            "maintainer": true
+        }
+    ],
+    "repository": {
+        "type": "git",
+        "url": "https://github.com/Links2004/arduinoWebSockets.git"
+    },
+    "version": "2.1.4",
+    "license": "LGPL-2.1",
+    "export": {
+        "exclude": [
+            "tests"
+        ]
+    },
+    "frameworks": "arduino",
+    "platforms": "atmelavr, espressif8266, espressif32"
+}

+ 9 - 0
lib/arduinoWebSockets-2.1.4/library.properties

@@ -0,0 +1,9 @@
+name=WebSockets
+version=2.1.4
+author=Markus Sattler
+maintainer=Markus Sattler
+sentence=WebSockets for Arduino (Server + Client)
+paragraph=use 2.x.x for ESP and 1.3 for AVR
+category=Communication
+url=https://github.com/Links2004/arduinoWebSockets
+architectures=*

+ 174 - 0
lib/arduinoWebSockets-2.1.4/src/SocketIOclient.cpp

@@ -0,0 +1,174 @@
+/*
+ * SocketIOclient.cpp
+ *
+ *  Created on: May 12, 2018
+ *      Author: links
+ */
+
+#include "WebSockets.h"
+#include "WebSocketsClient.h"
+#include "SocketIOclient.h"
+
+SocketIOclient::SocketIOclient() {
+}
+
+SocketIOclient::~SocketIOclient() {
+}
+
+void SocketIOclient::begin(const char * host, uint16_t port, const char * url, const char * protocol) {
+    WebSocketsClient::beginSocketIO(host, port, url, protocol);
+    WebSocketsClient::enableHeartbeat(60 * 1000, 90 * 1000, 5);
+}
+
+void SocketIOclient::begin(String host, uint16_t port, String url, String protocol) {
+    WebSocketsClient::beginSocketIO(host, port, url, protocol);
+    WebSocketsClient::enableHeartbeat(60 * 1000, 90 * 1000, 5);
+}
+
+/**
+ * set callback function
+ * @param cbEvent SocketIOclientEvent
+ */
+void SocketIOclient::onEvent(SocketIOclientEvent cbEvent) {
+    _cbEvent = cbEvent;
+}
+
+bool SocketIOclient::isConnected(void) {
+    return WebSocketsClient::isConnected();
+}
+
+/**
+ * send text data to client
+ * @param num uint8_t client id
+ * @param payload uint8_t *
+ * @param length size_t
+ * @param headerToPayload bool  (see sendFrame for more details)
+ * @return true if ok
+ */
+bool SocketIOclient::sendEVENT(uint8_t * payload, size_t length, bool headerToPayload) {
+    bool ret = false;
+    if(length == 0) {
+        length = strlen((const char *)payload);
+    }
+    if(clientIsConnected(&_client)) {
+        if(!headerToPayload) {
+            // webSocket Header
+            ret = WebSocketsClient::sendFrameHeader(&_client, WSop_text, length + 2, true);
+            // Engine.IO / Socket.IO Header
+            if(ret) {
+                uint8_t buf[3] = { eIOtype_MESSAGE, sIOtype_EVENT, 0x00 };
+                ret            = WebSocketsClient::write(&_client, buf, 2);
+            }
+            if(ret && payload && length > 0) {
+                ret = WebSocketsClient::write(&_client, payload, length);
+            }
+            return ret;
+        } else {
+            // TODO implement
+        }
+
+        // return WebSocketsClient::sendFrame(&_client, WSop_text, payload, length, true, true, headerToPayload);
+    }
+    return false;
+}
+
+bool SocketIOclient::sendEVENT(const uint8_t * payload, size_t length) {
+    return sendEVENT((uint8_t *)payload, length);
+}
+
+bool SocketIOclient::sendEVENT(char * payload, size_t length, bool headerToPayload) {
+    return sendEVENT((uint8_t *)payload, length, headerToPayload);
+}
+
+bool SocketIOclient::sendEVENT(const char * payload, size_t length) {
+    return sendEVENT((uint8_t *)payload, length);
+}
+
+bool SocketIOclient::sendEVENT(String & payload) {
+    return sendEVENT((uint8_t *)payload.c_str(), payload.length());
+}
+
+void SocketIOclient::loop(void) {
+    WebSocketsClient::loop();
+    unsigned long t = millis();
+    if((t - _lastConnectionFail) > EIO_HEARTBEAT_INTERVAL) {
+        _lastConnectionFail = t;
+        DEBUG_WEBSOCKETS("[wsIOc] send ping\n");
+        WebSocketsClient::sendTXT(eIOtype_PING);
+    }
+}
+
+void SocketIOclient::handleCbEvent(WStype_t type, uint8_t * payload, size_t length) {
+    switch(type) {
+        case WStype_DISCONNECTED:
+            runIOCbEvent(sIOtype_DISCONNECT, NULL, 0);
+            DEBUG_WEBSOCKETS("[wsIOc] Disconnected!\n");
+            break;
+        case WStype_CONNECTED: {
+            DEBUG_WEBSOCKETS("[wsIOc] Connected to url: %s\n", payload);
+            // send message to server when Connected
+            // Engine.io upgrade confirmation message (required)
+            WebSocketsClient::sendTXT(eIOtype_UPGRADE);
+            runIOCbEvent(sIOtype_CONNECT, payload, length);
+        } break;
+        case WStype_TEXT: {
+            if(length < 1) {
+                break;
+            }
+
+            engineIOmessageType_t eType = (engineIOmessageType_t)payload[0];
+            switch(eType) {
+                case eIOtype_PING:
+                    payload[0] = eIOtype_PONG;
+                    DEBUG_WEBSOCKETS("[wsIOc] get ping send pong (%s)\n", payload);
+                    WebSocketsClient::sendTXT(payload, length, false);
+                    break;
+                case eIOtype_PONG:
+                    DEBUG_WEBSOCKETS("[wsIOc] get pong\n");
+                    break;
+                case eIOtype_MESSAGE: {
+                    if(length < 2) {
+                        break;
+                    }
+                    socketIOmessageType_t ioType = (socketIOmessageType_t)payload[1];
+                    uint8_t * data               = &payload[2];
+                    size_t lData                 = length - 2;
+                    switch(ioType) {
+                        case sIOtype_EVENT:
+                            DEBUG_WEBSOCKETS("[wsIOc] get event (%d): %s\n", lData, data);
+                            break;
+                        case sIOtype_CONNECT:
+                        case sIOtype_DISCONNECT:
+                        case sIOtype_ACK:
+                        case sIOtype_ERROR:
+                        case sIOtype_BINARY_EVENT:
+                        case sIOtype_BINARY_ACK:
+                        default:
+                            DEBUG_WEBSOCKETS("[wsIOc] Socket.IO Message Type %c (%02X) is not implemented\n", ioType, ioType);
+                            DEBUG_WEBSOCKETS("[wsIOc] get text: %s\n", payload);
+                            break;
+                    }
+
+                    runIOCbEvent(ioType, data, lData);
+                } break;
+                case eIOtype_OPEN:
+                case eIOtype_CLOSE:
+                case eIOtype_UPGRADE:
+                case eIOtype_NOOP:
+                default:
+                    DEBUG_WEBSOCKETS("[wsIOc] Engine.IO Message Type %c (%02X) is not implemented\n", eType, eType);
+                    DEBUG_WEBSOCKETS("[wsIOc] get text: %s\n", payload);
+                    break;
+            }
+        } break;
+
+        case WStype_BIN:
+        case WStype_FRAGMENT_TEXT_START:
+        case WStype_FRAGMENT_BIN_START:
+        case WStype_FRAGMENT:
+        case WStype_FRAGMENT_FIN:
+        case WStype_PING:
+        case WStype_PONG:
+            break;
+    }
+}

+ 80 - 0
lib/arduinoWebSockets-2.1.4/src/SocketIOclient.h

@@ -0,0 +1,80 @@
+/**
+ * SocketIOclient.h
+ *
+ *  Created on: May 12, 2018
+ *      Author: links
+ */
+
+#ifndef SOCKETIOCLIENT_H_
+#define SOCKETIOCLIENT_H_
+
+#include "WebSockets.h"
+
+#define EIO_HEARTBEAT_INTERVAL 20000
+
+#define EIO_MAX_HEADER_SIZE (WEBSOCKETS_MAX_HEADER_SIZE + 1)
+#define SIO_MAX_HEADER_SIZE (EIO_MAX_HEADER_SIZE + 1)
+
+typedef enum {
+    eIOtype_OPEN    = '0',    ///< Sent from the server when a new transport is opened (recheck)
+    eIOtype_CLOSE   = '1',    ///< Request the close of this transport but does not shutdown the connection itself.
+    eIOtype_PING    = '2',    ///< Sent by the client. Server should answer with a pong packet containing the same data
+    eIOtype_PONG    = '3',    ///< Sent by the server to respond to ping packets.
+    eIOtype_MESSAGE = '4',    ///< actual message, client and server should call their callbacks with the data
+    eIOtype_UPGRADE = '5',    ///< Before engine.io switches a transport, it tests, if server and client can communicate over this transport. If this test succeed, the client sends an upgrade packets which requests the server to flush its cache on the old transport and switch to the new transport.
+    eIOtype_NOOP    = '6',    ///< A noop packet. Used primarily to force a poll cycle when an incoming websocket connection is received.
+} engineIOmessageType_t;
+
+typedef enum {
+    sIOtype_CONNECT      = '0',
+    sIOtype_DISCONNECT   = '1',
+    sIOtype_EVENT        = '2',
+    sIOtype_ACK          = '3',
+    sIOtype_ERROR        = '4',
+    sIOtype_BINARY_EVENT = '5',
+    sIOtype_BINARY_ACK   = '6',
+} socketIOmessageType_t;
+
+class SocketIOclient : protected WebSocketsClient {
+  public:
+#ifdef __AVR__
+    typedef void (*SocketIOclientEvent)(socketIOmessageType_t type, uint8_t * payload, size_t length);
+#else
+    typedef std::function<void(socketIOmessageType_t type, uint8_t * payload, size_t length)> SocketIOclientEvent;
+#endif
+
+    SocketIOclient(void);
+    virtual ~SocketIOclient(void);
+
+    void begin(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino");
+    void begin(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino");
+
+    bool isConnected(void);
+
+    void onEvent(SocketIOclientEvent cbEvent);
+
+    bool sendEVENT(uint8_t * payload, size_t length = 0, bool headerToPayload = false);
+    bool sendEVENT(const uint8_t * payload, size_t length = 0);
+    bool sendEVENT(char * payload, size_t length = 0, bool headerToPayload = false);
+    bool sendEVENT(const char * payload, size_t length = 0);
+    bool sendEVENT(String & payload);
+
+    void loop(void);
+
+  protected:
+    uint64_t _lastHeartbeat = 0;
+    SocketIOclientEvent _cbEvent;
+    virtual void runIOCbEvent(socketIOmessageType_t type, uint8_t * payload, size_t length) {
+        if(_cbEvent) {
+            _cbEvent(type, payload, length);
+        }
+    }
+
+    // Handeling events from websocket layer
+    virtual void runCbEvent(WStype_t type, uint8_t * payload, size_t length) {
+        handleCbEvent(type, payload, length);
+    }
+    void handleCbEvent(WStype_t type, uint8_t * payload, size_t length);
+};
+
+#endif /* SOCKETIOCLIENT_H_ */

+ 749 - 0
lib/arduinoWebSockets-2.1.4/src/WebSockets.cpp

@@ -0,0 +1,749 @@
+/**
+ * @file WebSockets.cpp
+ * @date 20.05.2015
+ * @author Markus Sattler
+ *
+ * Copyright (c) 2015 Markus Sattler. All rights reserved.
+ * This file is part of the WebSockets for Arduino.
+ *
+ * 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 "WebSockets.h"
+
+#ifdef ESP8266
+#include <core_esp8266_features.h>
+#endif
+
+extern "C" {
+#ifdef CORE_HAS_LIBB64
+#include <libb64/cencode.h>
+#else
+#include "libb64/cencode_inc.h"
+#endif
+}
+
+#ifdef ESP8266
+#include <Hash.h>
+#elif defined(ESP32)
+#include <hwcrypto/sha.h>
+#else
+
+extern "C" {
+#include "libsha1/libsha1.h"
+}
+
+#endif
+
+/**
+ *
+ * @param client WSclient_t *  ptr to the client struct
+ * @param code uint16_t see RFC
+ * @param reason ptr to the disconnect reason message
+ * @param reasonLen length of the disconnect reason message
+ */
+void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * reason, size_t reasonLen) {
+    DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] clientDisconnect code: %u\n", client->num, code);
+    if(client->status == WSC_CONNECTED && code) {
+        if(reason) {
+            sendFrame(client, WSop_close, (uint8_t *)reason, reasonLen);
+        } else {
+            uint8_t buffer[2];
+            buffer[0] = ((code >> 8) & 0xFF);
+            buffer[1] = (code & 0xFF);
+            sendFrame(client, WSop_close, &buffer[0], 2);
+        }
+    }
+    clientDisconnect(client);
+}
+
+/**
+ *
+ * @param buf uint8_t *         ptr to the buffer for writing
+ * @param opcode WSopcode_t
+ * @param length size_t         length of the payload
+ * @param mask bool             add dummy mask to the frame (needed for web browser)
+ * @param maskkey uint8_t[4]    key used for payload
+ * @param fin bool              can be used to send data in more then one frame (set fin on the last frame)
+ */
+uint8_t WebSockets::createHeader(uint8_t * headerPtr, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin) {
+    uint8_t headerSize;
+    // calculate header Size
+    if(length < 126) {
+        headerSize = 2;
+    } else if(length < 0xFFFF) {
+        headerSize = 4;
+    } else {
+        headerSize = 10;
+    }
+
+    if(mask) {
+        headerSize += 4;
+    }
+
+    // create header
+
+    // byte 0
+    *headerPtr = 0x00;
+    if(fin) {
+        *headerPtr |= bit(7);    ///< set Fin
+    }
+    *headerPtr |= opcode;    ///< set opcode
+    headerPtr++;
+
+    // byte 1
+    *headerPtr = 0x00;
+    if(mask) {
+        *headerPtr |= bit(7);    ///< set mask
+    }
+
+    if(length < 126) {
+        *headerPtr |= length;
+        headerPtr++;
+    } else if(length < 0xFFFF) {
+        *headerPtr |= 126;
+        headerPtr++;
+        *headerPtr = ((length >> 8) & 0xFF);
+        headerPtr++;
+        *headerPtr = (length & 0xFF);
+        headerPtr++;
+    } else {
+        // Normally we never get here (to less memory)
+        *headerPtr |= 127;
+        headerPtr++;
+        *headerPtr = 0x00;
+        headerPtr++;
+        *headerPtr = 0x00;
+        headerPtr++;
+        *headerPtr = 0x00;
+        headerPtr++;
+        *headerPtr = 0x00;
+        headerPtr++;
+        *headerPtr = ((length >> 24) & 0xFF);
+        headerPtr++;
+        *headerPtr = ((length >> 16) & 0xFF);
+        headerPtr++;
+        *headerPtr = ((length >> 8) & 0xFF);
+        headerPtr++;
+        *headerPtr = (length & 0xFF);
+        headerPtr++;
+    }
+
+    if(mask) {
+        *headerPtr = maskKey[0];
+        headerPtr++;
+        *headerPtr = maskKey[1];
+        headerPtr++;
+        *headerPtr = maskKey[2];
+        headerPtr++;
+        *headerPtr = maskKey[3];
+        headerPtr++;
+    }
+    return headerSize;
+}
+
+/**
+ *
+ * @param client WSclient_t *   ptr to the client struct
+ * @param opcode WSopcode_t
+ * @param length size_t         length of the payload
+ * @param fin bool              can be used to send data in more then one frame (set fin on the last frame)
+ * @return true if ok
+ */
+bool WebSockets::sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length, bool fin) {
+    uint8_t maskKey[4]                         = { 0x00, 0x00, 0x00, 0x00 };
+    uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 };
+
+    uint8_t headerSize = createHeader(&buffer[0], opcode, length, client->cIsClient, maskKey, fin);
+
+    if(write(client, &buffer[0], headerSize) != headerSize) {
+        return false;
+    }
+
+    return true;
+}
+
+/**
+ *
+ * @param client WSclient_t *   ptr to the client struct
+ * @param opcode WSopcode_t
+ * @param payload uint8_t *     ptr to the payload
+ * @param length size_t         length of the payload
+ * @param fin bool              can be used to send data in more then one frame (set fin on the last frame)
+ * @param headerToPayload bool  set true if the payload has reserved 14 Byte at the beginning to dynamically add the Header (payload neet to be in RAM!)
+ * @return true if ok
+ */
+bool WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin, bool headerToPayload) {
+    if(client->tcp && !client->tcp->connected()) {
+        DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not Connected!?\n", client->num);
+        return false;
+    }
+
+    if(client->status != WSC_CONNECTED) {
+        DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not in WSC_CONNECTED state!?\n", client->num);
+        return false;
+    }
+
+    DEBUG_WEBSOCKETS("[WS][%d][sendFrame] ------- send message frame -------\n", client->num);
+    DEBUG_WEBSOCKETS("[WS][%d][sendFrame] fin: %u opCode: %u mask: %u length: %u headerToPayload: %u\n", client->num, fin, opcode, client->cIsClient, length, headerToPayload);
+
+    if(opcode == WSop_text) {
+        DEBUG_WEBSOCKETS("[WS][%d][sendFrame] text: %s\n", client->num, (payload + (headerToPayload ? 14 : 0)));
+    }
+
+    uint8_t maskKey[4]                         = { 0x00, 0x00, 0x00, 0x00 };
+    uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 };
+
+    uint8_t headerSize;
+    uint8_t * headerPtr;
+    uint8_t * payloadPtr = payload;
+    bool useInternBuffer = false;
+    bool ret             = true;
+
+    // calculate header Size
+    if(length < 126) {
+        headerSize = 2;
+    } else if(length < 0xFFFF) {
+        headerSize = 4;
+    } else {
+        headerSize = 10;
+    }
+
+    if(client->cIsClient) {
+        headerSize += 4;
+    }
+
+#ifdef WEBSOCKETS_USE_BIG_MEM
+    // only for ESP since AVR has less HEAP
+    // try to send data in one TCP package (only if some free Heap is there)
+    if(!headerToPayload && ((length > 0) && (length < 1400)) && (GET_FREE_HEAP > 6000)) {
+        DEBUG_WEBSOCKETS("[WS][%d][sendFrame] pack to one TCP package...\n", client->num);
+        uint8_t * dataPtr = (uint8_t *)malloc(length + WEBSOCKETS_MAX_HEADER_SIZE);
+        if(dataPtr) {
+            memcpy((dataPtr + WEBSOCKETS_MAX_HEADER_SIZE), payload, length);
+            headerToPayload = true;
+            useInternBuffer = true;
+            payloadPtr      = dataPtr;
+        }
+    }
+#endif
+
+    // set Header Pointer
+    if(headerToPayload) {
+        // calculate offset in payload
+        headerPtr = (payloadPtr + (WEBSOCKETS_MAX_HEADER_SIZE - headerSize));
+    } else {
+        headerPtr = &buffer[0];
+    }
+
+    if(client->cIsClient && useInternBuffer) {
+        // if we use a Intern Buffer we can modify the data
+        // by this fact its possible the do the masking
+        for(uint8_t x = 0; x < sizeof(maskKey); x++) {
+            maskKey[x] = random(0xFF);
+        }
+    }
+
+    createHeader(headerPtr, opcode, length, client->cIsClient, maskKey, fin);
+
+    if(client->cIsClient && useInternBuffer) {
+        uint8_t * dataMaskPtr;
+
+        if(headerToPayload) {
+            dataMaskPtr = (payloadPtr + WEBSOCKETS_MAX_HEADER_SIZE);
+        } else {
+            dataMaskPtr = payloadPtr;
+        }
+
+        for(size_t x = 0; x < length; x++) {
+            dataMaskPtr[x] = (dataMaskPtr[x] ^ maskKey[x % 4]);
+        }
+    }
+
+#ifndef NODEBUG_WEBSOCKETS
+    unsigned long start = micros();
+#endif
+
+    if(headerToPayload) {
+        // header has be added to payload
+        // payload is forced to reserved 14 Byte but we may not need all based on the length and mask settings
+        // offset in payload is calculatetd 14 - headerSize
+        if(write(client, &payloadPtr[(WEBSOCKETS_MAX_HEADER_SIZE - headerSize)], (length + headerSize)) != (length + headerSize)) {
+            ret = false;
+        }
+    } else {
+        // send header
+        if(write(client, &buffer[0], headerSize) != headerSize) {
+            ret = false;
+        }
+
+        if(payloadPtr && length > 0) {
+            // send payload
+            if(write(client, &payloadPtr[0], length) != length) {
+                ret = false;
+            }
+        }
+    }
+
+    DEBUG_WEBSOCKETS("[WS][%d][sendFrame] sending Frame Done (%luus).\n", client->num, (micros() - start));
+
+#ifdef WEBSOCKETS_USE_BIG_MEM
+    if(useInternBuffer && payloadPtr) {
+        free(payloadPtr);
+    }
+#endif
+
+    return ret;
+}
+
+/**
+ * callen when HTTP header is done
+ * @param client WSclient_t *  ptr to the client struct
+ */
+void WebSockets::headerDone(WSclient_t * client) {
+    client->status    = WSC_CONNECTED;
+    client->cWsRXsize = 0;
+    DEBUG_WEBSOCKETS("[WS][%d][headerDone] Header Handling Done.\n", client->num);
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+    client->cHttpLine = "";
+    handleWebsocket(client);
+#endif
+}
+
+/**
+ * handle the WebSocket stream
+ * @param client WSclient_t *  ptr to the client struct
+ */
+void WebSockets::handleWebsocket(WSclient_t * client) {
+    if(client->cWsRXsize == 0) {
+        handleWebsocketCb(client);
+    }
+}
+
+/**
+ * wait for
+ * @param client
+ * @param size
+ */
+bool WebSockets::handleWebsocketWaitFor(WSclient_t * client, size_t size) {
+    if(!client->tcp || !client->tcp->connected()) {
+        return false;
+    }
+
+    if(size > WEBSOCKETS_MAX_HEADER_SIZE) {
+        DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d too big!\n", client->num, size);
+        return false;
+    }
+
+    if(client->cWsRXsize >= size) {
+        return true;
+    }
+
+    DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d cWsRXsize: %d\n", client->num, size, client->cWsRXsize);
+    readCb(client, &client->cWsHeader[client->cWsRXsize], (size - client->cWsRXsize), std::bind([](WebSockets * server, size_t size, WSclient_t * client, bool ok) {
+        DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor][readCb] size: %d ok: %d\n", client->num, size, ok);
+        if(ok) {
+            client->cWsRXsize = size;
+            server->handleWebsocketCb(client);
+        } else {
+            DEBUG_WEBSOCKETS("[WS][%d][readCb] failed.\n", client->num);
+            client->cWsRXsize = 0;
+            // timeout or error
+            server->clientDisconnect(client, 1002);
+        }
+    },
+                                                                                          this, size, std::placeholders::_1, std::placeholders::_2));
+    return false;
+}
+
+void WebSockets::handleWebsocketCb(WSclient_t * client) {
+    if(!client->tcp || !client->tcp->connected()) {
+        return;
+    }
+
+    uint8_t * buffer = client->cWsHeader;
+
+    WSMessageHeader_t * header = &client->cWsHeaderDecode;
+    uint8_t * payload          = NULL;
+
+    uint8_t headerLen = 2;
+
+    if(!handleWebsocketWaitFor(client, headerLen)) {
+        return;
+    }
+
+    // split first 2 bytes in the data
+    header->fin    = ((*buffer >> 7) & 0x01);
+    header->rsv1   = ((*buffer >> 6) & 0x01);
+    header->rsv2   = ((*buffer >> 5) & 0x01);
+    header->rsv3   = ((*buffer >> 4) & 0x01);
+    header->opCode = (WSopcode_t)(*buffer & 0x0F);
+    buffer++;
+
+    header->mask       = ((*buffer >> 7) & 0x01);
+    header->payloadLen = (WSopcode_t)(*buffer & 0x7F);
+    buffer++;
+
+    if(header->payloadLen == 126) {
+        headerLen += 2;
+        if(!handleWebsocketWaitFor(client, headerLen)) {
+            return;
+        }
+        header->payloadLen = buffer[0] << 8 | buffer[1];
+        buffer += 2;
+    } else if(header->payloadLen == 127) {
+        headerLen += 8;
+        // read 64bit integer as length
+        if(!handleWebsocketWaitFor(client, headerLen)) {
+            return;
+        }
+
+        if(buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0 || buffer[3] != 0) {
+            // really too big!
+            header->payloadLen = 0xFFFFFFFF;
+        } else {
+            header->payloadLen = buffer[4] << 24 | buffer[5] << 16 | buffer[6] << 8 | buffer[7];
+        }
+        buffer += 8;
+    }
+
+    DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ------- read massage frame -------\n", client->num);
+    DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] fin: %u rsv1: %u rsv2: %u rsv3 %u  opCode: %u\n", client->num, header->fin, header->rsv1, header->rsv2, header->rsv3, header->opCode);
+    DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] mask: %u payloadLen: %u\n", client->num, header->mask, header->payloadLen);
+
+    if(header->payloadLen > WEBSOCKETS_MAX_DATA_SIZE) {
+        DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] payload too big! (%u)\n", client->num, header->payloadLen);
+        clientDisconnect(client, 1009);
+        return;
+    }
+
+    if(header->mask) {
+        headerLen += 4;
+        if(!handleWebsocketWaitFor(client, headerLen)) {
+            return;
+        }
+        header->maskKey = buffer;
+        buffer += 4;
+    }
+
+    if(header->payloadLen > 0) {
+        // if text data we need one more
+        payload = (uint8_t *)malloc(header->payloadLen + 1);
+
+        if(!payload) {
+            DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] to less memory to handle payload %d!\n", client->num, header->payloadLen);
+            clientDisconnect(client, 1011);
+            return;
+        }
+        readCb(client, payload, header->payloadLen, std::bind(&WebSockets::handleWebsocketPayloadCb, this, std::placeholders::_1, std::placeholders::_2, payload));
+    } else {
+        handleWebsocketPayloadCb(client, true, NULL);
+    }
+}
+
+void WebSockets::handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload) {
+    WSMessageHeader_t * header = &client->cWsHeaderDecode;
+    if(ok) {
+        if(header->payloadLen > 0) {
+            payload[header->payloadLen] = 0x00;
+
+            if(header->mask) {
+                //decode XOR
+                for(size_t i = 0; i < header->payloadLen; i++) {
+                    payload[i] = (payload[i] ^ header->maskKey[i % 4]);
+                }
+            }
+        }
+
+        switch(header->opCode) {
+            case WSop_text:
+                DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] text: %s\n", client->num, payload);
+                // no break here!
+            case WSop_binary:
+            case WSop_continuation:
+                messageReceived(client, header->opCode, payload, header->payloadLen, header->fin);
+                break;
+            case WSop_ping:
+                // send pong back
+                DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ping received (%s)\n", client->num, payload ? (const char *)payload : "");
+                sendFrame(client, WSop_pong, payload, header->payloadLen);
+                messageReceived(client, header->opCode, payload, header->payloadLen, header->fin);
+                break;
+            case WSop_pong:
+                DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get pong (%s)\n", client->num, payload ? (const char *)payload : "");
+                client->pongReceived = true;
+                messageReceived(client, header->opCode, payload, header->payloadLen, header->fin);
+                break;
+            case WSop_close: {
+#ifndef NODEBUG_WEBSOCKETS
+                uint16_t reasonCode = 1000;
+                if(header->payloadLen >= 2) {
+                    reasonCode = payload[0] << 8 | payload[1];
+                }
+#endif
+                DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get ask for close. Code: %d", client->num, reasonCode);
+                if(header->payloadLen > 2) {
+                    DEBUG_WEBSOCKETS(" (%s)\n", (payload + 2));
+                } else {
+                    DEBUG_WEBSOCKETS("\n");
+                }
+                clientDisconnect(client, 1000);
+            } break;
+            default:
+                clientDisconnect(client, 1002);
+                break;
+        }
+
+        if(payload) {
+            free(payload);
+        }
+
+        // reset input
+        client->cWsRXsize = 0;
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+        //register callback for next message
+        handleWebsocketWaitFor(client, 2);
+#endif
+
+    } else {
+        DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] missing data!\n", client->num);
+        free(payload);
+        clientDisconnect(client, 1002);
+    }
+}
+
+/**
+ * generate the key for Sec-WebSocket-Accept
+ * @param clientKey String
+ * @return String Accept Key
+ */
+String WebSockets::acceptKey(String & clientKey) {
+    uint8_t sha1HashBin[20] = { 0 };
+#ifdef ESP8266
+    sha1(clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", &sha1HashBin[0]);
+#elif defined(ESP32)
+    String data = clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+    esp_sha(SHA1, (unsigned char *)data.c_str(), data.length(), &sha1HashBin[0]);
+#else
+    clientKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+    SHA1_CTX ctx;
+    SHA1Init(&ctx);
+    SHA1Update(&ctx, (const unsigned char *)clientKey.c_str(), clientKey.length());
+    SHA1Final(&sha1HashBin[0], &ctx);
+#endif
+
+    String key = base64_encode(sha1HashBin, 20);
+    key.trim();
+
+    return key;
+}
+
+/**
+ * base64_encode
+ * @param data uint8_t *
+ * @param length size_t
+ * @return base64 encoded String
+ */
+String WebSockets::base64_encode(uint8_t * data, size_t length) {
+    size_t size   = ((length * 1.6f) + 1);
+    char * buffer = (char *)malloc(size);
+    if(buffer) {
+        base64_encodestate _state;
+        base64_init_encodestate(&_state);
+        int len = base64_encode_block((const char *)&data[0], length, &buffer[0], &_state);
+        len     = base64_encode_blockend((buffer + len), &_state);
+
+        String base64 = String(buffer);
+        free(buffer);
+        return base64;
+    }
+    return String("-FAIL-");
+}
+
+/**
+ * read x byte from tcp or get timeout
+ * @param client WSclient_t *
+ * @param out  uint8_t * data buffer
+ * @param n size_t byte count
+ * @return true if ok
+ */
+bool WebSockets::readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb) {
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+    if(!client->tcp || !client->tcp->connected()) {
+        return false;
+    }
+
+    client->tcp->readBytes(out, n, std::bind([](WSclient_t * client, bool ok, WSreadWaitCb cb) {
+        if(cb) {
+            cb(client, ok);
+        }
+    },
+                                       client, std::placeholders::_1, cb));
+
+#else
+    unsigned long t = millis();
+    size_t len;
+    DEBUG_WEBSOCKETS("[readCb] n: %zu t: %lu\n", n, t);
+    while(n > 0) {
+        if(client->tcp == NULL) {
+            DEBUG_WEBSOCKETS("[readCb] tcp is null!\n");
+            if(cb) {
+                cb(client, false);
+            }
+            return false;
+        }
+
+        if(!client->tcp->connected()) {
+            DEBUG_WEBSOCKETS("[readCb] not connected!\n");
+            if(cb) {
+                cb(client, false);
+            }
+            return false;
+        }
+
+        if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) {
+            DEBUG_WEBSOCKETS("[readCb] receive TIMEOUT! %lu\n", (millis() - t));
+            if(cb) {
+                cb(client, false);
+            }
+            return false;
+        }
+
+        if(!client->tcp->available()) {
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266)
+            delay(0);
+#endif
+            continue;
+        }
+
+        len = client->tcp->read((uint8_t *)out, n);
+        if(len) {
+            t = millis();
+            out += len;
+            n -= len;
+            //DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n);
+        } else {
+            //DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n);
+        }
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266)
+        delay(0);
+#endif
+    }
+    if(cb) {
+        cb(client, true);
+    }
+#endif
+    return true;
+}
+
+/**
+ * write x byte to tcp or get timeout
+ * @param client WSclient_t *
+ * @param out  uint8_t * data buffer
+ * @param n size_t byte count
+ * @return bytes send
+ */
+size_t WebSockets::write(WSclient_t * client, uint8_t * out, size_t n) {
+    if(out == NULL)
+        return 0;
+    if(client == NULL)
+        return 0;
+    unsigned long t = millis();
+    size_t len      = 0;
+    size_t total    = 0;
+    DEBUG_WEBSOCKETS("[write] n: %zu t: %lu\n", n, t);
+    while(n > 0) {
+        if(client->tcp == NULL) {
+            DEBUG_WEBSOCKETS("[write] tcp is null!\n");
+            break;
+        }
+
+        if(!client->tcp->connected()) {
+            DEBUG_WEBSOCKETS("[write] not connected!\n");
+            break;
+        }
+
+        if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) {
+            DEBUG_WEBSOCKETS("[write] write TIMEOUT! %lu\n", (millis() - t));
+            break;
+        }
+
+        len = client->tcp->write((const uint8_t *)out, n);
+        if(len) {
+            t = millis();
+            out += len;
+            n -= len;
+            total += len;
+            //DEBUG_WEBSOCKETS("write %d left %d!\n", len, n);
+        } else {
+            //DEBUG_WEBSOCKETS("write %d failed left %d!\n", len, n);
+        }
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266)
+        delay(0);
+#endif
+    }
+    return total;
+}
+
+size_t WebSockets::write(WSclient_t * client, const char * out) {
+    if(client == NULL)
+        return 0;
+    if(out == NULL)
+        return 0;
+    return write(client, (uint8_t *)out, strlen(out));
+}
+
+/**
+ * enable ping/pong heartbeat process
+ * @param client WSclient_t *
+ * @param pingInterval uint32_t how often ping will be sent
+ * @param pongTimeout uint32_t millis after which pong should timout if not received
+ * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect
+ */
+void WebSockets::enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {
+    if(client == NULL)
+        return;
+    client->pingInterval           = pingInterval;
+    client->pongTimeout            = pongTimeout;
+    client->disconnectTimeoutCount = disconnectTimeoutCount;
+    client->pongReceived           = false;
+}
+
+/**
+ * handle ping/pong heartbeat timeout process
+ * @param client WSclient_t *
+ */
+void WebSockets::handleHBTimeout(WSclient_t * client) {
+    if(client->pingInterval) {    // if heartbeat is enabled
+        uint32_t pi = millis() - client->lastPing;
+
+        if(client->pongReceived) {
+            client->pongTimeoutCount = 0;
+        } else {
+            if(pi > client->pongTimeout) {    // pong not received in time
+                client->pongTimeoutCount++;
+                client->lastPing = millis() - client->pingInterval - 500;    // force ping on the next run
+
+                DEBUG_WEBSOCKETS("[HBtimeout] pong TIMEOUT! lp=%d millis=%d pi=%d count=%d\n", client->lastPing, millis(), pi, client->pongTimeoutCount);
+
+                if(client->disconnectTimeoutCount && client->pongTimeoutCount >= client->disconnectTimeoutCount) {
+                    DEBUG_WEBSOCKETS("[HBtimeout] count=%d, DISCONNECTING\n", client->pongTimeoutCount);
+                    clientDisconnect(client);
+                }
+            }
+        }
+    }
+}

+ 331 - 0
lib/arduinoWebSockets-2.1.4/src/WebSockets.h

@@ -0,0 +1,331 @@
+/**
+ * @file WebSockets.h
+ * @date 20.05.2015
+ * @author Markus Sattler
+ *
+ * Copyright (c) 2015 Markus Sattler. All rights reserved.
+ * This file is part of the WebSockets for Arduino.
+ *
+ * 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 WEBSOCKETS_H_
+#define WEBSOCKETS_H_
+
+#ifdef STM32_DEVICE
+#include <application.h>
+#define bit(b) (1UL << (b))    // Taken directly from Arduino.h
+#else
+#include <Arduino.h>
+#include <IPAddress.h>
+#endif
+
+#ifdef ARDUINO_ARCH_AVR
+#error Version 2.x.x currently does not support Arduino with AVR since there is no support for std namespace of c++.
+#error Use Version 1.x.x. (ATmega branch)
+#else
+#include <functional>
+#endif
+
+#ifndef NODEBUG_WEBSOCKETS
+#ifdef DEBUG_ESP_PORT
+#define DEBUG_WEBSOCKETS(...) DEBUG_ESP_PORT.printf(__VA_ARGS__)
+#else
+//#define DEBUG_WEBSOCKETS(...) os_printf( __VA_ARGS__ )
+#endif
+#endif
+
+#ifndef DEBUG_WEBSOCKETS
+#define DEBUG_WEBSOCKETS(...)
+#define NODEBUG_WEBSOCKETS
+#endif
+
+#if defined(ESP8266) || defined(ESP32)
+
+#define WEBSOCKETS_MAX_DATA_SIZE (15 * 1024)
+#define WEBSOCKETS_USE_BIG_MEM
+#define GET_FREE_HEAP ESP.getFreeHeap()
+// moves all Header strings to Flash (~300 Byte)
+//#define WEBSOCKETS_SAVE_RAM
+
+#elif defined(STM32_DEVICE)
+
+#define WEBSOCKETS_MAX_DATA_SIZE (15 * 1024)
+#define WEBSOCKETS_USE_BIG_MEM
+#define GET_FREE_HEAP System.freeMemory()
+
+#else
+
+//atmega328p has only 2KB ram!
+#define WEBSOCKETS_MAX_DATA_SIZE (1024)
+// moves all Header strings to Flash
+#define WEBSOCKETS_SAVE_RAM
+
+#endif
+
+#define WEBSOCKETS_TCP_TIMEOUT (2000)
+
+#define NETWORK_ESP8266_ASYNC (0)
+#define NETWORK_ESP8266 (1)
+#define NETWORK_W5100 (2)
+#define NETWORK_ENC28J60 (3)
+#define NETWORK_ESP32 (4)
+#define NETWORK_ESP32_ETH (5)
+
+// max size of the WS Message Header
+#define WEBSOCKETS_MAX_HEADER_SIZE (14)
+
+#if !defined(WEBSOCKETS_NETWORK_TYPE)
+// select Network type based
+#if defined(ESP8266) || defined(ESP31B)
+#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP8266
+//#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP8266_ASYNC
+//#define WEBSOCKETS_NETWORK_TYPE NETWORK_W5100
+
+#elif defined(ESP32)
+#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP32
+//#define WEBSOCKETS_NETWORK_TYPE NETWORK_ESP32_ETH
+#else
+#define WEBSOCKETS_NETWORK_TYPE NETWORK_W5100
+
+#endif
+#endif
+
+// Includes and defined based on Network Type
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+
+// Note:
+//   No SSL/WSS support for client in Async mode
+//   TLS lib need a sync interface!
+
+#if defined(ESP8266)
+#include <ESP8266WiFi.h>
+#elif defined(ESP32)
+#include <WiFi.h>
+#include <WiFiClientSecure.h>
+#elif defined(ESP31B)
+#include <ESP31BWiFi.h>
+#else
+#error "network type ESP8266 ASYNC only possible on the ESP mcu!"
+#endif
+
+#include <ESPAsyncTCP.h>
+#include <ESPAsyncTCPbuffer.h>
+#define WEBSOCKETS_NETWORK_CLASS AsyncTCPbuffer
+#define WEBSOCKETS_NETWORK_SERVER_CLASS AsyncServer
+
+#elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266)
+
+#if !defined(ESP8266) && !defined(ESP31B)
+#error "network type ESP8266 only possible on the ESP mcu!"
+#endif
+
+#ifdef ESP8266
+#include <ESP8266WiFi.h>
+#else
+#include <ESP31BWiFi.h>
+#endif
+#define WEBSOCKETS_NETWORK_CLASS WiFiClient
+#define WEBSOCKETS_NETWORK_SSL_CLASS WiFiClientSecure
+#define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer
+
+#elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_W5100)
+
+#ifdef STM32_DEVICE
+#define WEBSOCKETS_NETWORK_CLASS TCPClient
+#define WEBSOCKETS_NETWORK_SERVER_CLASS TCPServer
+#else
+#include <Ethernet.h>
+#include <SPI.h>
+#define WEBSOCKETS_NETWORK_CLASS EthernetClient
+#define WEBSOCKETS_NETWORK_SERVER_CLASS EthernetServer
+#endif
+
+#elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_ENC28J60)
+
+#include <UIPEthernet.h>
+#define WEBSOCKETS_NETWORK_CLASS UIPClient
+#define WEBSOCKETS_NETWORK_SERVER_CLASS UIPServer
+
+#elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+
+#include <WiFi.h>
+#include <WiFiClientSecure.h>
+#define WEBSOCKETS_NETWORK_CLASS WiFiClient
+#define WEBSOCKETS_NETWORK_SSL_CLASS WiFiClientSecure
+#define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer
+
+#elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32_ETH)
+
+#include <ETH.h>
+#define WEBSOCKETS_NETWORK_CLASS WiFiClient
+#define WEBSOCKETS_NETWORK_SERVER_CLASS WiFiServer
+
+#else
+#error "no network type selected!"
+#endif
+
+#ifdef WEBSOCKETS_NETWORK_SSL_CLASS
+#define HAS_SSL
+#endif
+
+// moves all Header strings to Flash (~300 Byte)
+#ifdef WEBSOCKETS_SAVE_RAM
+#define WEBSOCKETS_STRING(var) F(var)
+#else
+#define WEBSOCKETS_STRING(var) var
+#endif
+
+typedef enum {
+    WSC_NOT_CONNECTED,
+    WSC_HEADER,
+    WSC_CONNECTED
+} WSclientsStatus_t;
+
+typedef enum {
+    WStype_ERROR,
+    WStype_DISCONNECTED,
+    WStype_CONNECTED,
+    WStype_TEXT,
+    WStype_BIN,
+    WStype_FRAGMENT_TEXT_START,
+    WStype_FRAGMENT_BIN_START,
+    WStype_FRAGMENT,
+    WStype_FRAGMENT_FIN,
+    WStype_PING,
+    WStype_PONG,
+} WStype_t;
+
+typedef enum {
+    WSop_continuation = 0x00,    ///< %x0 denotes a continuation frame
+    WSop_text         = 0x01,    ///< %x1 denotes a text frame
+    WSop_binary       = 0x02,    ///< %x2 denotes a binary frame
+                                 ///< %x3-7 are reserved for further non-control frames
+    WSop_close = 0x08,           ///< %x8 denotes a connection close
+    WSop_ping  = 0x09,           ///< %x9 denotes a ping
+    WSop_pong  = 0x0A            ///< %xA denotes a pong
+                                 ///< %xB-F are reserved for further control frames
+} WSopcode_t;
+
+typedef struct {
+    bool fin;
+    bool rsv1;
+    bool rsv2;
+    bool rsv3;
+
+    WSopcode_t opCode;
+    bool mask;
+
+    size_t payloadLen;
+
+    uint8_t * maskKey;
+} WSMessageHeader_t;
+
+typedef struct {
+    uint8_t num;    ///< connection number
+
+    WSclientsStatus_t status;
+
+    WEBSOCKETS_NETWORK_CLASS * tcp;
+
+    bool isSocketIO;    ///< client for socket.io server
+
+#if defined(HAS_SSL)
+    bool isSSL;    ///< run in ssl mode
+    WEBSOCKETS_NETWORK_SSL_CLASS * ssl;
+#endif
+
+    String cUrl;       ///< http url
+    uint16_t cCode;    ///< http code
+
+    bool cIsClient = false;    ///< will be used for masking
+    bool cIsUpgrade;           ///< Connection == Upgrade
+    bool cIsWebsocket;         ///< Upgrade == websocket
+
+    String cSessionId;     ///< client Set-Cookie (session id)
+    String cKey;           ///< client Sec-WebSocket-Key
+    String cAccept;        ///< client Sec-WebSocket-Accept
+    String cProtocol;      ///< client Sec-WebSocket-Protocol
+    String cExtensions;    ///< client Sec-WebSocket-Extensions
+    uint16_t cVersion;     ///< client Sec-WebSocket-Version
+
+    uint8_t cWsRXsize;                                ///< State of the RX
+    uint8_t cWsHeader[WEBSOCKETS_MAX_HEADER_SIZE];    ///< RX WS Message buffer
+    WSMessageHeader_t cWsHeaderDecode;
+
+    String base64Authorization;    ///< Base64 encoded Auth request
+    String plainAuthorization;     ///< Base64 encoded Auth request
+
+    String extraHeaders;
+
+    bool cHttpHeadersValid;           ///< non-websocket http header validity indicator
+    size_t cMandatoryHeadersCount;    ///< non-websocket mandatory http headers present count
+
+    bool pongReceived;
+    uint32_t pingInterval;             // how often ping will be sent, 0 means "heartbeat is not active"
+    uint32_t lastPing;                 // millis when last pong has been received
+    uint32_t pongTimeout;              // interval in millis after which pong is considered to timeout
+    uint8_t disconnectTimeoutCount;    // after how many subsequent pong timeouts discconnect will happen, 0 means "do not disconnect"
+    uint8_t pongTimeoutCount;          // current pong timeout count
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+    String cHttpLine;    ///< HTTP header lines
+#endif
+
+} WSclient_t;
+
+class WebSockets {
+  protected:
+#ifdef __AVR__
+    typedef void (*WSreadWaitCb)(WSclient_t * client, bool ok);
+#else
+    typedef std::function<void(WSclient_t * client, bool ok)> WSreadWaitCb;
+#endif
+
+    virtual void clientDisconnect(WSclient_t * client)  = 0;
+    virtual bool clientIsConnected(WSclient_t * client) = 0;
+
+    void clientDisconnect(WSclient_t * client, uint16_t code, char * reason = NULL, size_t reasonLen = 0);
+
+    virtual void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) = 0;
+
+    uint8_t createHeader(uint8_t * buf, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin);
+    bool sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length = 0, bool fin = true);
+    bool sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload = NULL, size_t length = 0, bool fin = true, bool headerToPayload = false);
+
+    void headerDone(WSclient_t * client);
+
+    void handleWebsocket(WSclient_t * client);
+
+    bool handleWebsocketWaitFor(WSclient_t * client, size_t size);
+    void handleWebsocketCb(WSclient_t * client);
+    void handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload);
+
+    String acceptKey(String & clientKey);
+    String base64_encode(uint8_t * data, size_t length);
+
+    bool readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb);
+    virtual size_t write(WSclient_t * client, uint8_t * out, size_t n);
+    size_t write(WSclient_t * client, const char * out);
+
+    void enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount);
+    void handleHBTimeout(WSclient_t * client);
+};
+
+#ifndef UNUSED
+#define UNUSED(var) (void)(var)
+#endif
+#endif /* WEBSOCKETS_H_ */

+ 854 - 0
lib/arduinoWebSockets-2.1.4/src/WebSocketsClient.cpp

@@ -0,0 +1,854 @@
+/**
+ * @file WebSocketsClient.cpp
+ * @date 20.05.2015
+ * @author Markus Sattler
+ *
+ * Copyright (c) 2015 Markus Sattler. All rights reserved.
+ * This file is part of the WebSockets for Arduino.
+ *
+ * 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 "WebSockets.h"
+#include "WebSocketsClient.h"
+
+WebSocketsClient::WebSocketsClient() {
+    _cbEvent             = NULL;
+    _client.num          = 0;
+    _client.cIsClient    = true;
+    _client.extraHeaders = WEBSOCKETS_STRING("Origin: file://");
+}
+
+WebSocketsClient::~WebSocketsClient() {
+    disconnect();
+}
+
+/**
+ * calles to init the Websockets server
+ */
+void WebSocketsClient::begin(const char * host, uint16_t port, const char * url, const char * protocol) {
+    _host = host;
+    _port = port;
+#if defined(HAS_SSL)
+    _fingerprint = "";
+    _CA_cert     = NULL;
+#endif
+
+    _client.num    = 0;
+    _client.status = WSC_NOT_CONNECTED;
+    _client.tcp    = NULL;
+#if defined(HAS_SSL)
+    _client.isSSL = false;
+    _client.ssl   = NULL;
+#endif
+    _client.cUrl                = url;
+    _client.cCode               = 0;
+    _client.cIsUpgrade          = false;
+    _client.cIsWebsocket        = true;
+    _client.cKey                = "";
+    _client.cAccept             = "";
+    _client.cProtocol           = protocol;
+    _client.cExtensions         = "";
+    _client.cVersion            = 0;
+    _client.base64Authorization = "";
+    _client.plainAuthorization  = "";
+    _client.isSocketIO          = false;
+
+    _client.lastPing         = 0;
+    _client.pongReceived     = false;
+    _client.pongTimeoutCount = 0;
+
+#ifdef ESP8266
+    randomSeed(RANDOM_REG32);
+#else
+    // todo find better seed
+    randomSeed(millis());
+#endif
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+    asyncConnect();
+#endif
+
+    _lastConnectionFail = 0;
+    _reconnectInterval  = 500;
+}
+
+void WebSocketsClient::begin(String host, uint16_t port, String url, String protocol) {
+    begin(host.c_str(), port, url.c_str(), protocol.c_str());
+}
+
+void WebSocketsClient::begin(IPAddress host, uint16_t port, const char * url, const char * protocol) {
+    return begin(host.toString().c_str(), port, url, protocol);
+}
+
+#if defined(HAS_SSL)
+void WebSocketsClient::beginSSL(const char * host, uint16_t port, const char * url, const char * fingerprint, const char * protocol) {
+    begin(host, port, url, protocol);
+    _client.isSSL = true;
+    _fingerprint  = fingerprint;
+    _CA_cert      = NULL;
+}
+
+void WebSocketsClient::beginSSL(String host, uint16_t port, String url, String fingerprint, String protocol) {
+    beginSSL(host.c_str(), port, url.c_str(), fingerprint.c_str(), protocol.c_str());
+}
+
+void WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) {
+    begin(host, port, url, protocol);
+    _client.isSSL = true;
+    _fingerprint  = "";
+    _CA_cert      = CA_cert;
+}
+#endif
+
+void WebSocketsClient::beginSocketIO(const char * host, uint16_t port, const char * url, const char * protocol) {
+    begin(host, port, url, protocol);
+    _client.isSocketIO = true;
+}
+
+void WebSocketsClient::beginSocketIO(String host, uint16_t port, String url, String protocol) {
+    beginSocketIO(host.c_str(), port, url.c_str(), protocol.c_str());
+}
+
+#if defined(HAS_SSL)
+void WebSocketsClient::beginSocketIOSSL(const char * host, uint16_t port, const char * url, const char * protocol) {
+    begin(host, port, url, protocol);
+    _client.isSocketIO = true;
+    _client.isSSL      = true;
+    _fingerprint       = "";
+}
+
+void WebSocketsClient::beginSocketIOSSL(String host, uint16_t port, String url, String protocol) {
+    beginSocketIOSSL(host.c_str(), port, url.c_str(), protocol.c_str());
+}
+
+void WebSocketsClient::beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) {
+    begin(host, port, url, protocol);
+    _client.isSocketIO = true;
+    _client.isSSL      = true;
+    _fingerprint       = "";
+    _CA_cert           = CA_cert;
+}
+#endif
+
+#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC)
+/**
+ * called in arduino loop
+ */
+void WebSocketsClient::loop(void) {
+    if(!clientIsConnected(&_client)) {
+        // do not flood the server
+        if((millis() - _lastConnectionFail) < _reconnectInterval) {
+            return;
+        }
+
+#if defined(HAS_SSL)
+        if(_client.isSSL) {
+            DEBUG_WEBSOCKETS("[WS-Client] connect wss...\n");
+            if(_client.ssl) {
+                delete _client.ssl;
+                _client.ssl = NULL;
+                _client.tcp = NULL;
+            }
+            _client.ssl = new WEBSOCKETS_NETWORK_SSL_CLASS();
+            _client.tcp = _client.ssl;
+            if(_CA_cert) {
+                DEBUG_WEBSOCKETS("[WS-Client] setting CA certificate");
+#if defined(ESP32)
+                _client.ssl->setCACert(_CA_cert);
+#elif defined(ESP8266)
+                _client.ssl->setCACert((const uint8_t *)_CA_cert, strlen(_CA_cert) + 1);
+#else
+#error setCACert not implemented
+#endif
+            }
+        } else {
+            DEBUG_WEBSOCKETS("[WS-Client] connect ws...\n");
+            if(_client.tcp) {
+                delete _client.tcp;
+                _client.tcp = NULL;
+            }
+            _client.tcp = new WEBSOCKETS_NETWORK_CLASS();
+        }
+#else
+        _client.tcp = new WEBSOCKETS_NETWORK_CLASS();
+#endif
+
+        if(!_client.tcp) {
+            DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!");
+            return;
+        }
+#if defined(ESP32)
+        if(_client.tcp->connect(_host.c_str(), _port, WEBSOCKETS_TCP_TIMEOUT)) {
+#else
+        if(_client.tcp->connect(_host.c_str(), _port)) {
+#endif
+            connectedCb();
+            _lastConnectionFail = 0;
+        } else {
+            connectFailedCb();
+            _lastConnectionFail = millis();
+        }
+    } else {
+        handleClientData();
+
+        if(_client.status == WSC_CONNECTED) {
+            handleHBPing();
+            handleHBTimeout(&_client);
+        }
+    }
+}
+#endif
+
+/**
+ * set callback function
+ * @param cbEvent WebSocketServerEvent
+ */
+void WebSocketsClient::onEvent(WebSocketClientEvent cbEvent) {
+    _cbEvent = cbEvent;
+}
+
+/**
+ * send text data to client
+ * @param num uint8_t client id
+ * @param payload uint8_t *
+ * @param length size_t
+ * @param headerToPayload bool  (see sendFrame for more details)
+ * @return true if ok
+ */
+bool WebSocketsClient::sendTXT(uint8_t * payload, size_t length, bool headerToPayload) {
+    if(length == 0) {
+        length = strlen((const char *)payload);
+    }
+    if(clientIsConnected(&_client)) {
+        return sendFrame(&_client, WSop_text, payload, length, true, headerToPayload);
+    }
+    return false;
+}
+
+bool WebSocketsClient::sendTXT(const uint8_t * payload, size_t length) {
+    return sendTXT((uint8_t *)payload, length);
+}
+
+bool WebSocketsClient::sendTXT(char * payload, size_t length, bool headerToPayload) {
+    return sendTXT((uint8_t *)payload, length, headerToPayload);
+}
+
+bool WebSocketsClient::sendTXT(const char * payload, size_t length) {
+    return sendTXT((uint8_t *)payload, length);
+}
+
+bool WebSocketsClient::sendTXT(String & payload) {
+    return sendTXT((uint8_t *)payload.c_str(), payload.length());
+}
+
+bool WebSocketsClient::sendTXT(char payload) {
+    uint8_t buf[WEBSOCKETS_MAX_HEADER_SIZE + 2] = { 0x00 };
+    buf[WEBSOCKETS_MAX_HEADER_SIZE]             = payload;
+    return sendTXT(buf, 1, true);
+}
+
+/**
+ * send binary data to client
+ * @param num uint8_t client id
+ * @param payload uint8_t *
+ * @param length size_t
+ * @param headerToPayload bool  (see sendFrame for more details)
+ * @return true if ok
+ */
+bool WebSocketsClient::sendBIN(uint8_t * payload, size_t length, bool headerToPayload) {
+    if(clientIsConnected(&_client)) {
+        return sendFrame(&_client, WSop_binary, payload, length, true, headerToPayload);
+    }
+    return false;
+}
+
+bool WebSocketsClient::sendBIN(const uint8_t * payload, size_t length) {
+    return sendBIN((uint8_t *)payload, length);
+}
+
+/**
+ * sends a WS ping to Server
+ * @param payload uint8_t *
+ * @param length size_t
+ * @return true if ping is send out
+ */
+bool WebSocketsClient::sendPing(uint8_t * payload, size_t length) {
+    if(clientIsConnected(&_client)) {
+        bool sent = sendFrame(&_client, WSop_ping, payload, length);
+        if(sent)
+            _client.lastPing = millis();
+        return sent;
+    }
+    return false;
+}
+
+bool WebSocketsClient::sendPing(String & payload) {
+    return sendPing((uint8_t *)payload.c_str(), payload.length());
+}
+
+/**
+ * disconnect one client
+ * @param num uint8_t client id
+ */
+void WebSocketsClient::disconnect(void) {
+    if(clientIsConnected(&_client)) {
+        WebSockets::clientDisconnect(&_client, 1000);
+    }
+}
+
+/**
+ * set the Authorizatio for the http request
+ * @param user const char *
+ * @param password const char *
+ */
+void WebSocketsClient::setAuthorization(const char * user, const char * password) {
+    if(user && password) {
+        String auth = user;
+        auth += ":";
+        auth += password;
+        _client.base64Authorization = base64_encode((uint8_t *)auth.c_str(), auth.length());
+    }
+}
+
+/**
+ * set the Authorizatio for the http request
+ * @param auth const char * base64
+ */
+void WebSocketsClient::setAuthorization(const char * auth) {
+    if(auth) {
+        //_client.base64Authorization = auth;
+        _client.plainAuthorization = auth;
+    }
+}
+
+/**
+ * set extra headers for the http request;
+ * separate headers by "\r\n"
+ * @param extraHeaders const char * extraHeaders
+ */
+void WebSocketsClient::setExtraHeaders(const char * extraHeaders) {
+    _client.extraHeaders = extraHeaders;
+}
+
+/**
+ * set the reconnect Interval
+ * how long to wait after a connection initiate failed
+ * @param time in ms
+ */
+void WebSocketsClient::setReconnectInterval(unsigned long time) {
+    _reconnectInterval = time;
+}
+
+bool WebSocketsClient::isConnected(void) {
+    return (_client.status == WSC_CONNECTED);
+}
+
+//#################################################################################
+//#################################################################################
+//#################################################################################
+
+/**
+ *
+ * @param client WSclient_t *  ptr to the client struct
+ * @param opcode WSopcode_t
+ * @param payload  uint8_t *
+ * @param length size_t
+ */
+void WebSocketsClient::messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) {
+    WStype_t type = WStype_ERROR;
+
+    UNUSED(client);
+
+    switch(opcode) {
+        case WSop_text:
+            type = fin ? WStype_TEXT : WStype_FRAGMENT_TEXT_START;
+            break;
+        case WSop_binary:
+            type = fin ? WStype_BIN : WStype_FRAGMENT_BIN_START;
+            break;
+        case WSop_continuation:
+            type = fin ? WStype_FRAGMENT_FIN : WStype_FRAGMENT;
+            break;
+        case WSop_ping:
+            type = WStype_PING;
+            break;
+        case WSop_pong:
+            type = WStype_PONG;
+            break;
+        case WSop_close:
+        default:
+            break;
+    }
+
+    runCbEvent(type, payload, length);
+}
+
+/**
+ * Disconnect an client
+ * @param client WSclient_t *  ptr to the client struct
+ */
+void WebSocketsClient::clientDisconnect(WSclient_t * client) {
+    bool event = false;
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+    if(client->isSSL && client->ssl) {
+        if(client->ssl->connected()) {
+            client->ssl->flush();
+            client->ssl->stop();
+        }
+        event = true;
+        delete client->ssl;
+        client->ssl = NULL;
+        client->tcp = NULL;
+    }
+#endif
+
+    if(client->tcp) {
+        if(client->tcp->connected()) {
+#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC)
+            client->tcp->flush();
+#endif
+            client->tcp->stop();
+        }
+        event = true;
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+        client->status = WSC_NOT_CONNECTED;
+#else
+        delete client->tcp;
+#endif
+        client->tcp = NULL;
+    }
+
+    client->cCode        = 0;
+    client->cKey         = "";
+    client->cAccept      = "";
+    client->cVersion     = 0;
+    client->cIsUpgrade   = false;
+    client->cIsWebsocket = false;
+    client->cSessionId   = "";
+
+    client->status = WSC_NOT_CONNECTED;
+
+    DEBUG_WEBSOCKETS("[WS-Client] client disconnected.\n");
+    if(event) {
+        runCbEvent(WStype_DISCONNECTED, NULL, 0);
+    }
+}
+
+/**
+ * get client state
+ * @param client WSclient_t *  ptr to the client struct
+ * @return true = conneted
+ */
+bool WebSocketsClient::clientIsConnected(WSclient_t * client) {
+    if(!client->tcp) {
+        return false;
+    }
+
+    if(client->tcp->connected()) {
+        if(client->status != WSC_NOT_CONNECTED) {
+            return true;
+        }
+    } else {
+        // client lost
+        if(client->status != WSC_NOT_CONNECTED) {
+            DEBUG_WEBSOCKETS("[WS-Client] connection lost.\n");
+            // do cleanup
+            clientDisconnect(client);
+        }
+    }
+
+    if(client->tcp) {
+        // do cleanup
+        clientDisconnect(client);
+    }
+
+    return false;
+}
+#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC)
+/**
+ * Handel incomming data from Client
+ */
+void WebSocketsClient::handleClientData(void) {
+    int len = _client.tcp->available();
+    if(len > 0) {
+        switch(_client.status) {
+            case WSC_HEADER: {
+                String headerLine = _client.tcp->readStringUntil('\n');
+                handleHeader(&_client, &headerLine);
+            } break;
+            case WSC_CONNECTED:
+                WebSockets::handleWebsocket(&_client);
+                break;
+            default:
+                WebSockets::clientDisconnect(&_client, 1002);
+                break;
+        }
+    }
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+    delay(0);
+#endif
+}
+#endif
+
+/**
+ * send the WebSocket header to Server
+ * @param client WSclient_t *  ptr to the client struct
+ */
+void WebSocketsClient::sendHeader(WSclient_t * client) {
+    static const char * NEW_LINE = "\r\n";
+
+    DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header...\n");
+
+    uint8_t randomKey[16] = { 0 };
+
+    for(uint8_t i = 0; i < sizeof(randomKey); i++) {
+        randomKey[i] = random(0xFF);
+    }
+
+    client->cKey = base64_encode(&randomKey[0], 16);
+
+#ifndef NODEBUG_WEBSOCKETS
+    unsigned long start = micros();
+#endif
+
+    String handshake;
+    bool ws_header = true;
+    String url     = client->cUrl;
+
+    if(client->isSocketIO) {
+        if(client->cSessionId.length() == 0) {
+            url += WEBSOCKETS_STRING("&transport=polling");
+            ws_header = false;
+        } else {
+            url += WEBSOCKETS_STRING("&transport=websocket&sid=");
+            url += client->cSessionId;
+        }
+    }
+
+    handshake = WEBSOCKETS_STRING("GET ");
+    handshake += url + WEBSOCKETS_STRING(
+                           " HTTP/1.1\r\n"
+                           "Host: ");
+    handshake += _host + ":" + _port + NEW_LINE;
+
+    if(ws_header) {
+        handshake += WEBSOCKETS_STRING(
+            "Connection: Upgrade\r\n"
+            "Upgrade: websocket\r\n"
+            "Sec-WebSocket-Version: 13\r\n"
+            "Sec-WebSocket-Key: ");
+        handshake += client->cKey + NEW_LINE;
+
+        if(client->cProtocol.length() > 0) {
+            handshake += WEBSOCKETS_STRING("Sec-WebSocket-Protocol: ");
+            handshake += client->cProtocol + NEW_LINE;
+        }
+
+        if(client->cExtensions.length() > 0) {
+            handshake += WEBSOCKETS_STRING("Sec-WebSocket-Extensions: ");
+            handshake += client->cExtensions + NEW_LINE;
+        }
+    } else {
+        handshake += WEBSOCKETS_STRING("Connection: keep-alive\r\n");
+    }
+
+    // add extra headers; by default this includes "Origin: file://"
+    if(client->extraHeaders) {
+        handshake += client->extraHeaders + NEW_LINE;
+    }
+
+    handshake += WEBSOCKETS_STRING("User-Agent: arduino-WebSocket-Client\r\n");
+
+    if(client->base64Authorization.length() > 0) {
+        handshake += WEBSOCKETS_STRING("Authorization: Basic ");
+        handshake += client->base64Authorization + NEW_LINE;
+    }
+
+    if(client->plainAuthorization.length() > 0) {
+        handshake += WEBSOCKETS_STRING("Authorization: ");
+        handshake += client->plainAuthorization + NEW_LINE;
+    }
+
+    handshake += NEW_LINE;
+
+    DEBUG_WEBSOCKETS("[WS-Client][sendHeader] handshake %s", (uint8_t *)handshake.c_str());
+    write(client, (uint8_t *)handshake.c_str(), handshake.length());
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+    client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsClient::handleHeader, this, client, &(client->cHttpLine)));
+#endif
+
+    DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header... Done (%luus).\n", (micros() - start));
+}
+
+/**
+ * handle the WebSocket header reading
+ * @param client WSclient_t *  ptr to the client struct
+ */
+void WebSocketsClient::handleHeader(WSclient_t * client, String * headerLine) {
+    headerLine->trim();    // remove \r
+
+    if(headerLine->length() > 0) {
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader] RX: %s\n", headerLine->c_str());
+
+        if(headerLine->startsWith(WEBSOCKETS_STRING("HTTP/1."))) {
+            // "HTTP/1.1 101 Switching Protocols"
+            client->cCode = headerLine->substring(9, headerLine->indexOf(' ', 9)).toInt();
+        } else if(headerLine->indexOf(':') >= 0) {
+            String headerName  = headerLine->substring(0, headerLine->indexOf(':'));
+            String headerValue = headerLine->substring(headerLine->indexOf(':') + 1);
+
+            // remove space in the beginning  (RFC2616)
+            if(headerValue[0] == ' ') {
+                headerValue.remove(0, 1);
+            }
+
+            if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Connection"))) {
+                if(headerValue.equalsIgnoreCase(WEBSOCKETS_STRING("upgrade"))) {
+                    client->cIsUpgrade = true;
+                }
+            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Upgrade"))) {
+                if(headerValue.equalsIgnoreCase(WEBSOCKETS_STRING("websocket"))) {
+                    client->cIsWebsocket = true;
+                }
+            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Accept"))) {
+                client->cAccept = headerValue;
+                client->cAccept.trim();    // see rfc6455
+            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Protocol"))) {
+                client->cProtocol = headerValue;
+            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Extensions"))) {
+                client->cExtensions = headerValue;
+            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Version"))) {
+                client->cVersion = headerValue.toInt();
+            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Set-Cookie"))) {
+                if(headerValue.indexOf(WEBSOCKETS_STRING("HttpOnly")) > -1) {
+                    client->cSessionId = headerValue.substring(headerValue.indexOf('=') + 1, headerValue.indexOf(";"));
+                } else {
+                    client->cSessionId = headerValue.substring(headerValue.indexOf('=') + 1);
+                }
+            }
+        } else {
+            DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header error (%s)\n", headerLine->c_str());
+        }
+
+        (*headerLine) = "";
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+        client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsClient::handleHeader, this, client, &(client->cHttpLine)));
+#endif
+    } else {
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header read fin.\n");
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Client settings:\n");
+
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader]  - cURL: %s\n", client->cUrl.c_str());
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader]  - cKey: %s\n", client->cKey.c_str());
+
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Server header:\n");
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader]  - cCode: %d\n", client->cCode);
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader]  - cIsUpgrade: %d\n", client->cIsUpgrade);
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader]  - cIsWebsocket: %d\n", client->cIsWebsocket);
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader]  - cAccept: %s\n", client->cAccept.c_str());
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader]  - cProtocol: %s\n", client->cProtocol.c_str());
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader]  - cExtensions: %s\n", client->cExtensions.c_str());
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader]  - cVersion: %d\n", client->cVersion);
+        DEBUG_WEBSOCKETS("[WS-Client][handleHeader]  - cSessionId: %s\n", client->cSessionId.c_str());
+
+        bool ok = (client->cIsUpgrade && client->cIsWebsocket);
+
+        if(ok) {
+            switch(client->cCode) {
+                case 101:    ///< Switching Protocols
+
+                    break;
+                case 200:
+                    if(client->isSocketIO) {
+                        break;
+                    }
+                case 403:    ///< Forbidden
+                    // todo handle login
+                default:    ///< Server dont unterstand requrst
+                    ok = false;
+                    DEBUG_WEBSOCKETS("[WS-Client][handleHeader] serverCode is not 101 (%d)\n", client->cCode);
+                    clientDisconnect(client);
+                    _lastConnectionFail = millis();
+                    break;
+            }
+        }
+
+        if(ok) {
+            if(client->cAccept.length() == 0) {
+                ok = false;
+            } else {
+                // generate Sec-WebSocket-Accept key for check
+                String sKey = acceptKey(client->cKey);
+                if(sKey != client->cAccept) {
+                    DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Sec-WebSocket-Accept is wrong\n");
+                    ok = false;
+                }
+            }
+        }
+
+        if(ok) {
+            DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Websocket connection init done.\n");
+            headerDone(client);
+
+            runCbEvent(WStype_CONNECTED, (uint8_t *)client->cUrl.c_str(), client->cUrl.length());
+        } else if(clientIsConnected(client) && client->isSocketIO && client->cSessionId.length() > 0) {
+            if(_client.tcp->available()) {
+                // read not needed data
+                DEBUG_WEBSOCKETS("[WS-Client][handleHeader] still data in buffer (%d), clean up.\n", _client.tcp->available());
+                while(_client.tcp->available() > 0) {
+                    _client.tcp->read();
+                }
+            }
+            sendHeader(client);
+        } else {
+            DEBUG_WEBSOCKETS("[WS-Client][handleHeader] no Websocket connection close.\n");
+            _lastConnectionFail = millis();
+            if(clientIsConnected(client)) {
+                write(client, "This is a webSocket client!");
+            }
+            clientDisconnect(client);
+        }
+    }
+}
+
+void WebSocketsClient::connectedCb() {
+    DEBUG_WEBSOCKETS("[WS-Client] connected to %s:%u.\n", _host.c_str(), _port);
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+    _client.tcp->onDisconnect(std::bind([](WebSocketsClient * c, AsyncTCPbuffer * obj, WSclient_t * client) -> bool {
+        DEBUG_WEBSOCKETS("[WS-Server][%d] Disconnect client\n", client->num);
+        client->status = WSC_NOT_CONNECTED;
+        client->tcp    = NULL;
+
+        // reconnect
+        c->asyncConnect();
+
+        return true;
+    },
+        this, std::placeholders::_1, &_client));
+#endif
+
+    _client.status = WSC_HEADER;
+
+#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC)
+    // set Timeout for readBytesUntil and readStringUntil
+    _client.tcp->setTimeout(WEBSOCKETS_TCP_TIMEOUT);
+#endif
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+    _client.tcp->setNoDelay(true);
+#endif
+
+#if defined(HAS_SSL)
+    if(_client.isSSL && _fingerprint.length()) {
+        if(!_client.ssl->verify(_fingerprint.c_str(), _host.c_str())) {
+            DEBUG_WEBSOCKETS("[WS-Client] certificate mismatch\n");
+            WebSockets::clientDisconnect(&_client, 1000);
+            return;
+        }
+    } else if(_client.isSSL && !_CA_cert) {
+#if defined(wificlientbearssl_h) && !defined(USING_AXTLS) && !defined(wificlientsecure_h)
+        _client.ssl->setInsecure();
+#endif
+    }
+#endif
+
+    // send Header to Server
+    sendHeader(&_client);
+}
+
+void WebSocketsClient::connectFailedCb() {
+    DEBUG_WEBSOCKETS("[WS-Client] connection to %s:%u Failed\n", _host.c_str(), _port);
+}
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+
+void WebSocketsClient::asyncConnect() {
+    DEBUG_WEBSOCKETS("[WS-Client] asyncConnect...\n");
+
+    AsyncClient * tcpclient = new AsyncClient();
+
+    if(!tcpclient) {
+        DEBUG_WEBSOCKETS("[WS-Client] creating AsyncClient class failed!\n");
+        return;
+    }
+
+    tcpclient->onDisconnect([](void * obj, AsyncClient * c) {
+        c->free();
+        delete c;
+    });
+
+    tcpclient->onConnect(std::bind([](WebSocketsClient * ws, AsyncClient * tcp) {
+        ws->_client.tcp = new AsyncTCPbuffer(tcp);
+        if(!ws->_client.tcp) {
+            DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!\n");
+            ws->connectFailedCb();
+            return;
+        }
+        ws->connectedCb();
+    },
+        this, std::placeholders::_2));
+
+    tcpclient->onError(std::bind([](WebSocketsClient * ws, AsyncClient * tcp) {
+        ws->connectFailedCb();
+
+        // reconnect
+        ws->asyncConnect();
+    },
+        this, std::placeholders::_2));
+
+    if(!tcpclient->connect(_host.c_str(), _port)) {
+        connectFailedCb();
+        delete tcpclient;
+    }
+}
+
+#endif
+
+/**
+ * send heartbeat ping to server in set intervals
+ */
+void WebSocketsClient::handleHBPing() {
+    if(_client.pingInterval == 0)
+        return;
+    uint32_t pi = millis() - _client.lastPing;
+    if(pi > _client.pingInterval) {
+        DEBUG_WEBSOCKETS("[WS-Client] sending HB ping\n");
+        if(sendPing()) {
+            _client.lastPing     = millis();
+            _client.pongReceived = false;
+        }
+    }
+}
+
+/**
+ * enable ping/pong heartbeat process
+ * @param pingInterval uint32_t how often ping will be sent
+ * @param pongTimeout uint32_t millis after which pong should timout if not received
+ * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect
+ */
+void WebSocketsClient::enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) {
+    WebSockets::enableHeartbeat(&_client, pingInterval, pongTimeout, disconnectTimeoutCount);
+}
+
+/**
+ * disable ping/pong heartbeat process
+ */
+void WebSocketsClient::disableHeartbeat() {
+    _client.pingInterval = 0;
+}

+ 145 - 0
lib/arduinoWebSockets-2.1.4/src/WebSocketsClient.h

@@ -0,0 +1,145 @@
+/**
+ * @file WebSocketsClient.h
+ * @date 20.05.2015
+ * @author Markus Sattler
+ *
+ * Copyright (c) 2015 Markus Sattler. All rights reserved.
+ * This file is part of the WebSockets for Arduino.
+ *
+ * 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 WEBSOCKETSCLIENT_H_
+#define WEBSOCKETSCLIENT_H_
+
+#include "WebSockets.h"
+
+class WebSocketsClient : protected WebSockets {
+  public:
+#ifdef __AVR__
+    typedef void (*WebSocketClientEvent)(WStype_t type, uint8_t * payload, size_t length);
+#else
+    typedef std::function<void(WStype_t type, uint8_t * payload, size_t length)> WebSocketClientEvent;
+#endif
+
+    WebSocketsClient(void);
+    virtual ~WebSocketsClient(void);
+
+    void begin(const char * host, uint16_t port, const char * url = "/", const char * protocol = "arduino");
+    void begin(String host, uint16_t port, String url = "/", String protocol = "arduino");
+    void begin(IPAddress host, uint16_t port, const char * url = "/", const char * protocol = "arduino");
+
+#if defined(HAS_SSL)
+    void beginSSL(const char * host, uint16_t port, const char * url = "/", const char * = "", const char * protocol = "arduino");
+    void beginSSL(String host, uint16_t port, String url = "/", String fingerprint = "", String protocol = "arduino");
+    void beginSslWithCA(const char * host, uint16_t port, const char * url = "/", const char * CA_cert = NULL, const char * protocol = "arduino");
+#endif
+
+    void beginSocketIO(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino");
+    void beginSocketIO(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino");
+
+#if defined(HAS_SSL)
+    void beginSocketIOSSL(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino");
+    void beginSocketIOSSL(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino");
+    void beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * CA_cert = NULL, const char * protocol = "arduino");
+#endif
+
+#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC)
+    void loop(void);
+#else
+    // Async interface not need a loop call
+    void loop(void) __attribute__((deprecated)) {}
+#endif
+
+    void onEvent(WebSocketClientEvent cbEvent);
+
+    bool sendTXT(uint8_t * payload, size_t length = 0, bool headerToPayload = false);
+    bool sendTXT(const uint8_t * payload, size_t length = 0);
+    bool sendTXT(char * payload, size_t length = 0, bool headerToPayload = false);
+    bool sendTXT(const char * payload, size_t length = 0);
+    bool sendTXT(String & payload);
+    bool sendTXT(char payload);
+
+    bool sendBIN(uint8_t * payload, size_t length, bool headerToPayload = false);
+    bool sendBIN(const uint8_t * payload, size_t length);
+
+    bool sendPing(uint8_t * payload = NULL, size_t length = 0);
+    bool sendPing(String & payload);
+
+    void disconnect(void);
+
+    void setAuthorization(const char * user, const char * password);
+    void setAuthorization(const char * auth);
+
+    void setExtraHeaders(const char * extraHeaders = NULL);
+
+    void setReconnectInterval(unsigned long time);
+
+    void enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount);
+    void disableHeartbeat();
+
+  protected:
+    String _host;
+    uint16_t _port;
+
+    bool isConnected(void);
+
+#if defined(HAS_SSL)
+    String _fingerprint;
+    const char * _CA_cert;
+#endif
+    WSclient_t _client;
+
+    WebSocketClientEvent _cbEvent;
+
+    unsigned long _lastConnectionFail;
+    unsigned long _reconnectInterval;
+
+    void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin);
+
+    void clientDisconnect(WSclient_t * client);
+    bool clientIsConnected(WSclient_t * client);
+
+#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC)
+    void handleClientData(void);
+#endif
+
+    void sendHeader(WSclient_t * client);
+    void handleHeader(WSclient_t * client, String * headerLine);
+
+    void connectedCb();
+    void connectFailedCb();
+
+    void handleHBPing();    // send ping in specified intervals
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+    void asyncConnect();
+#endif
+
+    /**
+         * called for sending a Event to the app
+         * @param type WStype_t
+         * @param payload uint8_t *
+         * @param length size_t
+         */
+    virtual void runCbEvent(WStype_t type, uint8_t * payload, size_t length) {
+        if(_cbEvent) {
+            _cbEvent(type, payload, length);
+        }
+    }
+};
+
+#endif /* WEBSOCKETSCLIENT_H_ */

+ 852 - 0
lib/arduinoWebSockets-2.1.4/src/WebSocketsServer.cpp

@@ -0,0 +1,852 @@
+/**
+ * @file WebSocketsServer.cpp
+ * @date 20.05.2015
+ * @author Markus Sattler
+ *
+ * Copyright (c) 2015 Markus Sattler. All rights reserved.
+ * This file is part of the WebSockets for Arduino.
+ *
+ * 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 "WebSockets.h"
+#include "WebSocketsServer.h"
+
+WebSocketsServer::WebSocketsServer(uint16_t port, String origin, String protocol) {
+    _port     = port;
+    _origin   = origin;
+    _protocol = protocol;
+    _runnning = false;
+
+    _server = new WEBSOCKETS_NETWORK_SERVER_CLASS(port);
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+    _server->onClient([](void * s, AsyncClient * c) {
+        ((WebSocketsServer *)s)->newClient(new AsyncTCPbuffer(c));
+    },
+        this);
+#endif
+
+    _cbEvent = NULL;
+
+    _httpHeaderValidationFunc = NULL;
+    _mandatoryHttpHeaders     = NULL;
+    _mandatoryHttpHeaderCount = 0;
+
+    memset(&_clients[0], 0x00, (sizeof(WSclient_t) * WEBSOCKETS_SERVER_CLIENT_MAX));
+}
+
+WebSocketsServer::~WebSocketsServer() {
+    // disconnect all clients
+    close();
+
+    if(_mandatoryHttpHeaders)
+        delete[] _mandatoryHttpHeaders;
+
+    _mandatoryHttpHeaderCount = 0;
+}
+
+/**
+ * called to initialize the Websocket server
+ */
+void WebSocketsServer::begin(void) {
+    WSclient_t * client;
+
+    // init client storage
+    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {
+        client = &_clients[i];
+
+        client->num    = i;
+        client->status = WSC_NOT_CONNECTED;
+        client->tcp    = NULL;
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+        client->isSSL = false;
+        client->ssl   = NULL;
+#endif
+        client->cUrl         = "";
+        client->cCode        = 0;
+        client->cKey         = "";
+        client->cProtocol    = "";
+        client->cVersion     = 0;
+        client->cIsUpgrade   = false;
+        client->cIsWebsocket = false;
+
+        client->base64Authorization = "";
+
+        client->cWsRXsize = 0;
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+        client->cHttpLine = "";
+#endif
+    }
+
+#ifdef ESP8266
+    randomSeed(RANDOM_REG32);
+#elif defined(ESP32)
+#define DR_REG_RNG_BASE 0x3ff75144
+    randomSeed(READ_PERI_REG(DR_REG_RNG_BASE));
+#else
+    // TODO find better seed
+    randomSeed(millis());
+#endif
+
+    _runnning = true;
+    _server->begin();
+
+    DEBUG_WEBSOCKETS("[WS-Server] Server Started.\n");
+}
+
+void WebSocketsServer::close(void) {
+    _runnning = false;
+    disconnect();
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266)
+    _server->close();
+#elif(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+    _server->end();
+#else
+    // TODO how to close server?
+#endif
+}
+
+#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC)
+/**
+ * called in arduino loop
+ */
+void WebSocketsServer::loop(void) {
+    if(_runnning) {
+        handleNewClients();
+        handleClientData();
+    }
+}
+#endif
+
+/**
+ * set callback function
+ * @param cbEvent WebSocketServerEvent
+ */
+void WebSocketsServer::onEvent(WebSocketServerEvent cbEvent) {
+    _cbEvent = cbEvent;
+}
+
+/*
+ * Sets the custom http header validator function
+ * @param httpHeaderValidationFunc WebSocketServerHttpHeaderValFunc ///< pointer to the custom http header validation function
+ * @param mandatoryHttpHeaders[] const char* ///< the array of named http headers considered to be mandatory / must be present in order for websocket upgrade to succeed
+ * @param mandatoryHttpHeaderCount size_t ///< the number of items in the mandatoryHttpHeaders array
+ */
+void WebSocketsServer::onValidateHttpHeader(
+    WebSocketServerHttpHeaderValFunc validationFunc,
+    const char * mandatoryHttpHeaders[],
+    size_t mandatoryHttpHeaderCount) {
+    _httpHeaderValidationFunc = validationFunc;
+
+    if(_mandatoryHttpHeaders)
+        delete[] _mandatoryHttpHeaders;
+
+    _mandatoryHttpHeaderCount = mandatoryHttpHeaderCount;
+    _mandatoryHttpHeaders     = new String[_mandatoryHttpHeaderCount];
+
+    for(size_t i = 0; i < _mandatoryHttpHeaderCount; i++) {
+        _mandatoryHttpHeaders[i] = mandatoryHttpHeaders[i];
+    }
+}
+
+/*
+ * send text data to client
+ * @param num uint8_t client id
+ * @param payload uint8_t *
+ * @param length size_t
+ * @param headerToPayload bool  (see sendFrame for more details)
+ * @return true if ok
+ */
+bool WebSocketsServer::sendTXT(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload) {
+    if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) {
+        return false;
+    }
+    if(length == 0) {
+        length = strlen((const char *)payload);
+    }
+    WSclient_t * client = &_clients[num];
+    if(clientIsConnected(client)) {
+        return sendFrame(client, WSop_text, payload, length, true, headerToPayload);
+    }
+    return false;
+}
+
+bool WebSocketsServer::sendTXT(uint8_t num, const uint8_t * payload, size_t length) {
+    return sendTXT(num, (uint8_t *)payload, length);
+}
+
+bool WebSocketsServer::sendTXT(uint8_t num, char * payload, size_t length, bool headerToPayload) {
+    return sendTXT(num, (uint8_t *)payload, length, headerToPayload);
+}
+
+bool WebSocketsServer::sendTXT(uint8_t num, const char * payload, size_t length) {
+    return sendTXT(num, (uint8_t *)payload, length);
+}
+
+bool WebSocketsServer::sendTXT(uint8_t num, String & payload) {
+    return sendTXT(num, (uint8_t *)payload.c_str(), payload.length());
+}
+
+/**
+ * send text data to client all
+ * @param payload uint8_t *
+ * @param length size_t
+ * @param headerToPayload bool  (see sendFrame for more details)
+ * @return true if ok
+ */
+bool WebSocketsServer::broadcastTXT(uint8_t * payload, size_t length, bool headerToPayload) {
+    WSclient_t * client;
+    bool ret = true;
+    if(length == 0) {
+        length = strlen((const char *)payload);
+    }
+
+    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {
+        client = &_clients[i];
+        if(clientIsConnected(client)) {
+            if(!sendFrame(client, WSop_text, payload, length, true, headerToPayload)) {
+                ret = false;
+            }
+        }
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266)
+        delay(0);
+#endif
+    }
+    return ret;
+}
+
+bool WebSocketsServer::broadcastTXT(const uint8_t * payload, size_t length) {
+    return broadcastTXT((uint8_t *)payload, length);
+}
+
+bool WebSocketsServer::broadcastTXT(char * payload, size_t length, bool headerToPayload) {
+    return broadcastTXT((uint8_t *)payload, length, headerToPayload);
+}
+
+bool WebSocketsServer::broadcastTXT(const char * payload, size_t length) {
+    return broadcastTXT((uint8_t *)payload, length);
+}
+
+bool WebSocketsServer::broadcastTXT(String & payload) {
+    return broadcastTXT((uint8_t *)payload.c_str(), payload.length());
+}
+
+/**
+ * send binary data to client
+ * @param num uint8_t client id
+ * @param payload uint8_t *
+ * @param length size_t
+ * @param headerToPayload bool  (see sendFrame for more details)
+ * @return true if ok
+ */
+bool WebSocketsServer::sendBIN(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload) {
+    if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) {
+        return false;
+    }
+    WSclient_t * client = &_clients[num];
+    if(clientIsConnected(client)) {
+        return sendFrame(client, WSop_binary, payload, length, true, headerToPayload);
+    }
+    return false;
+}
+
+bool WebSocketsServer::sendBIN(uint8_t num, const uint8_t * payload, size_t length) {
+    return sendBIN(num, (uint8_t *)payload, length);
+}
+
+/**
+ * send binary data to client all
+ * @param payload uint8_t *
+ * @param length size_t
+ * @param headerToPayload bool  (see sendFrame for more details)
+ * @return true if ok
+ */
+bool WebSocketsServer::broadcastBIN(uint8_t * payload, size_t length, bool headerToPayload) {
+    WSclient_t * client;
+    bool ret = true;
+    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {
+        client = &_clients[i];
+        if(clientIsConnected(client)) {
+            if(!sendFrame(client, WSop_binary, payload, length, true, headerToPayload)) {
+                ret = false;
+            }
+        }
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266)
+        delay(0);
+#endif
+    }
+    return ret;
+}
+
+bool WebSocketsServer::broadcastBIN(const uint8_t * payload, size_t length) {
+    return broadcastBIN((uint8_t *)payload, length);
+}
+
+/**
+ * sends a WS ping to Client
+ * @param num uint8_t client id
+ * @param payload uint8_t *
+ * @param length size_t
+ * @return true if ping is send out
+ */
+bool WebSocketsServer::sendPing(uint8_t num, uint8_t * payload, size_t length) {
+    if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) {
+        return false;
+    }
+    WSclient_t * client = &_clients[num];
+    if(clientIsConnected(client)) {
+        return sendFrame(client, WSop_ping, payload, length);
+    }
+    return false;
+}
+
+bool WebSocketsServer::sendPing(uint8_t num, String & payload) {
+    return sendPing(num, (uint8_t *)payload.c_str(), payload.length());
+}
+
+/**
+ *  sends a WS ping to all Client
+ * @param payload uint8_t *
+ * @param length size_t
+ * @return true if ping is send out
+ */
+bool WebSocketsServer::broadcastPing(uint8_t * payload, size_t length) {
+    WSclient_t * client;
+    bool ret = true;
+    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {
+        client = &_clients[i];
+        if(clientIsConnected(client)) {
+            if(!sendFrame(client, WSop_ping, payload, length)) {
+                ret = false;
+            }
+        }
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266)
+        delay(0);
+#endif
+    }
+    return ret;
+}
+
+bool WebSocketsServer::broadcastPing(String & payload) {
+    return broadcastPing((uint8_t *)payload.c_str(), payload.length());
+}
+
+/**
+ * disconnect all clients
+ */
+void WebSocketsServer::disconnect(void) {
+    WSclient_t * client;
+    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {
+        client = &_clients[i];
+        if(clientIsConnected(client)) {
+            WebSockets::clientDisconnect(client, 1000);
+        }
+    }
+}
+
+/**
+ * disconnect one client
+ * @param num uint8_t client id
+ */
+void WebSocketsServer::disconnect(uint8_t num) {
+    if(num >= WEBSOCKETS_SERVER_CLIENT_MAX) {
+        return;
+    }
+    WSclient_t * client = &_clients[num];
+    if(clientIsConnected(client)) {
+        WebSockets::clientDisconnect(client, 1000);
+    }
+}
+
+/*
+ * set the Authorization for the http request
+ * @param user const char *
+ * @param password const char *
+ */
+void WebSocketsServer::setAuthorization(const char * user, const char * password) {
+    if(user && password) {
+        String auth = user;
+        auth += ":";
+        auth += password;
+        _base64Authorization = base64_encode((uint8_t *)auth.c_str(), auth.length());
+    }
+}
+
+/**
+ * set the Authorizatio for the http request
+ * @param auth const char * base64
+ */
+void WebSocketsServer::setAuthorization(const char * auth) {
+    if(auth) {
+        _base64Authorization = auth;
+    }
+}
+
+/**
+ * count the connected clients (optional ping them)
+ * @param ping bool ping the connected clients
+ */
+int WebSocketsServer::connectedClients(bool ping) {
+    WSclient_t * client;
+    int count = 0;
+    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {
+        client = &_clients[i];
+        if(client->status == WSC_CONNECTED) {
+            if(ping != true || sendPing(i)) {
+                count++;
+            }
+        }
+    }
+    return count;
+}
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+/**
+ * get an IP for a client
+ * @param num uint8_t client id
+ * @return IPAddress
+ */
+IPAddress WebSocketsServer::remoteIP(uint8_t num) {
+    if(num < WEBSOCKETS_SERVER_CLIENT_MAX) {
+        WSclient_t * client = &_clients[num];
+        if(clientIsConnected(client)) {
+            return client->tcp->remoteIP();
+        }
+    }
+
+    return IPAddress();
+}
+#endif
+
+//#################################################################################
+//#################################################################################
+//#################################################################################
+
+/**
+ * handle new client connection
+ * @param client
+ */
+bool WebSocketsServer::newClient(WEBSOCKETS_NETWORK_CLASS * TCPclient) {
+    WSclient_t * client;
+    // search free list entry for client
+    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {
+        client = &_clients[i];
+
+        // state is not connected or tcp connection is lost
+        if(!clientIsConnected(client)) {
+            client->tcp = TCPclient;
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+            client->isSSL = false;
+            client->tcp->setNoDelay(true);
+#endif
+#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC)
+            // set Timeout for readBytesUntil and readStringUntil
+            client->tcp->setTimeout(WEBSOCKETS_TCP_TIMEOUT);
+#endif
+            client->status = WSC_HEADER;
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+            IPAddress ip = client->tcp->remoteIP();
+            DEBUG_WEBSOCKETS("[WS-Server][%d] new client from %d.%d.%d.%d\n", client->num, ip[0], ip[1], ip[2], ip[3]);
+#else
+            DEBUG_WEBSOCKETS("[WS-Server][%d] new client\n", client->num);
+#endif
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+            client->tcp->onDisconnect(std::bind([](WebSocketsServer * server, AsyncTCPbuffer * obj, WSclient_t * client) -> bool {
+                DEBUG_WEBSOCKETS("[WS-Server][%d] Disconnect client\n", client->num);
+
+                AsyncTCPbuffer ** sl = &server->_clients[client->num].tcp;
+                if(*sl == obj) {
+                    client->status = WSC_NOT_CONNECTED;
+                    *sl            = NULL;
+                }
+                return true;
+            },
+                this, std::placeholders::_1, client));
+
+            client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsServer::handleHeader, this, client, &(client->cHttpLine)));
+#endif
+
+            return true;
+            break;
+        }
+    }
+    return false;
+}
+
+/**
+ *
+ * @param client WSclient_t *  ptr to the client struct
+ * @param opcode WSopcode_t
+ * @param payload  uint8_t *
+ * @param length size_t
+ */
+void WebSocketsServer::messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) {
+    WStype_t type = WStype_ERROR;
+
+    switch(opcode) {
+        case WSop_text:
+            type = fin ? WStype_TEXT : WStype_FRAGMENT_TEXT_START;
+            break;
+        case WSop_binary:
+            type = fin ? WStype_BIN : WStype_FRAGMENT_BIN_START;
+            break;
+        case WSop_continuation:
+            type = fin ? WStype_FRAGMENT_FIN : WStype_FRAGMENT;
+            break;
+        case WSop_ping:
+            type = WStype_PING;
+            break;
+        case WSop_pong:
+            type = WStype_PONG;
+            break;
+        case WSop_close:
+        default:
+            break;
+    }
+
+    runCbEvent(client->num, type, payload, length);
+}
+
+/**
+ * Disconnect an client
+ * @param client WSclient_t *  ptr to the client struct
+ */
+void WebSocketsServer::clientDisconnect(WSclient_t * client) {
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+    if(client->isSSL && client->ssl) {
+        if(client->ssl->connected()) {
+            client->ssl->flush();
+            client->ssl->stop();
+        }
+        delete client->ssl;
+        client->ssl = NULL;
+        client->tcp = NULL;
+    }
+#endif
+
+    if(client->tcp) {
+        if(client->tcp->connected()) {
+#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC)
+            client->tcp->flush();
+#endif
+            client->tcp->stop();
+        }
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+        client->status = WSC_NOT_CONNECTED;
+#else
+        delete client->tcp;
+#endif
+        client->tcp = NULL;
+    }
+
+    client->cUrl         = "";
+    client->cKey         = "";
+    client->cProtocol    = "";
+    client->cVersion     = 0;
+    client->cIsUpgrade   = false;
+    client->cIsWebsocket = false;
+
+    client->cWsRXsize = 0;
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+    client->cHttpLine = "";
+#endif
+
+    client->status = WSC_NOT_CONNECTED;
+
+    DEBUG_WEBSOCKETS("[WS-Server][%d] client disconnected.\n", client->num);
+
+    runCbEvent(client->num, WStype_DISCONNECTED, NULL, 0);
+}
+
+/**
+ * get client state
+ * @param client WSclient_t *  ptr to the client struct
+ * @return true = connected
+ */
+bool WebSocketsServer::clientIsConnected(WSclient_t * client) {
+    if(!client->tcp) {
+        return false;
+    }
+
+    if(client->tcp->connected()) {
+        if(client->status != WSC_NOT_CONNECTED) {
+            return true;
+        }
+    } else {
+        // client lost
+        if(client->status != WSC_NOT_CONNECTED) {
+            DEBUG_WEBSOCKETS("[WS-Server][%d] client connection lost.\n", client->num);
+            // do cleanup
+            clientDisconnect(client);
+        }
+    }
+
+    if(client->tcp) {
+        // do cleanup
+        DEBUG_WEBSOCKETS("[WS-Server][%d] client list cleanup.\n", client->num);
+        clientDisconnect(client);
+    }
+
+    return false;
+}
+#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC)
+/**
+ * Handle incoming Connection Request
+ */
+void WebSocketsServer::handleNewClients(void) {
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+    while(_server->hasClient()) {
+#endif
+        bool ok = false;
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+        // store new connection
+        WEBSOCKETS_NETWORK_CLASS * tcpClient = new WEBSOCKETS_NETWORK_CLASS(_server->available());
+#else
+    WEBSOCKETS_NETWORK_CLASS * tcpClient = new WEBSOCKETS_NETWORK_CLASS(_server->available());
+#endif
+
+        if(!tcpClient) {
+            DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!");
+            return;
+        }
+
+        ok = newClient(tcpClient);
+
+        if(!ok) {
+            // no free space to handle client
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+            IPAddress ip = tcpClient->remoteIP();
+            DEBUG_WEBSOCKETS("[WS-Server] no free space new client from %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
+#else
+        DEBUG_WEBSOCKETS("[WS-Server] no free space new client\n");
+#endif
+            tcpClient->stop();
+        }
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+        delay(0);
+    }
+#endif
+}
+
+/**
+ * Handel incomming data from Client
+ */
+void WebSocketsServer::handleClientData(void) {
+    WSclient_t * client;
+    for(uint8_t i = 0; i < WEBSOCKETS_SERVER_CLIENT_MAX; i++) {
+        client = &_clients[i];
+        if(clientIsConnected(client)) {
+            int len = client->tcp->available();
+            if(len > 0) {
+                //DEBUG_WEBSOCKETS("[WS-Server][%d][handleClientData] len: %d\n", client->num, len);
+                switch(client->status) {
+                    case WSC_HEADER: {
+                        String headerLine = client->tcp->readStringUntil('\n');
+                        handleHeader(client, &headerLine);
+                    } break;
+                    case WSC_CONNECTED:
+                        WebSockets::handleWebsocket(client);
+                        break;
+                    default:
+                        WebSockets::clientDisconnect(client, 1002);
+                        break;
+                }
+            }
+        }
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266)
+        delay(0);
+#endif
+    }
+}
+#endif
+
+/*
+ * returns an indicator whether the given named header exists in the configured _mandatoryHttpHeaders collection
+ * @param headerName String ///< the name of the header being checked
+ */
+bool WebSocketsServer::hasMandatoryHeader(String headerName) {
+    for(size_t i = 0; i < _mandatoryHttpHeaderCount; i++) {
+        if(_mandatoryHttpHeaders[i].equalsIgnoreCase(headerName))
+            return true;
+    }
+    return false;
+}
+
+/**
+ * handles http header reading for WebSocket upgrade
+ * @param client WSclient_t * ///< pointer to the client struct
+ * @param headerLine String ///< the header being read / processed
+ */
+void WebSocketsServer::handleHeader(WSclient_t * client, String * headerLine) {
+    static const char * NEW_LINE = "\r\n";
+
+    headerLine->trim();    // remove \r
+
+    if(headerLine->length() > 0) {
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] RX: %s\n", client->num, headerLine->c_str());
+
+        // websocket requests always start with GET see rfc6455
+        if(headerLine->startsWith("GET ")) {
+            // cut URL out
+            client->cUrl = headerLine->substring(4, headerLine->indexOf(' ', 4));
+
+            //reset non-websocket http header validation state for this client
+            client->cHttpHeadersValid      = true;
+            client->cMandatoryHeadersCount = 0;
+
+        } else if(headerLine->indexOf(':') >= 0) {
+            String headerName  = headerLine->substring(0, headerLine->indexOf(':'));
+            String headerValue = headerLine->substring(headerLine->indexOf(':') + 1);
+
+            // remove space in the beginning (RFC2616)
+            if(headerValue[0] == ' ') {
+                headerValue.remove(0, 1);
+            }
+
+            if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Connection"))) {
+                headerValue.toLowerCase();
+                if(headerValue.indexOf(WEBSOCKETS_STRING("upgrade")) >= 0) {
+                    client->cIsUpgrade = true;
+                }
+            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Upgrade"))) {
+                if(headerValue.equalsIgnoreCase(WEBSOCKETS_STRING("websocket"))) {
+                    client->cIsWebsocket = true;
+                }
+            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Version"))) {
+                client->cVersion = headerValue.toInt();
+            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Key"))) {
+                client->cKey = headerValue;
+                client->cKey.trim();    // see rfc6455
+            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Protocol"))) {
+                client->cProtocol = headerValue;
+            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Sec-WebSocket-Extensions"))) {
+                client->cExtensions = headerValue;
+            } else if(headerName.equalsIgnoreCase(WEBSOCKETS_STRING("Authorization"))) {
+                client->base64Authorization = headerValue;
+            } else {
+                client->cHttpHeadersValid &= execHttpHeaderValidation(headerName, headerValue);
+                if(_mandatoryHttpHeaderCount > 0 && hasMandatoryHeader(headerName)) {
+                    client->cMandatoryHeadersCount++;
+                }
+            }
+
+        } else {
+            DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header error (%s)\n", headerLine->c_str());
+        }
+
+        (*headerLine) = "";
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC)
+        client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsServer::handleHeader, this, client, &(client->cHttpLine)));
+#endif
+    } else {
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] Header read fin.\n", client->num);
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader]  - cURL: %s\n", client->num, client->cUrl.c_str());
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader]  - cIsUpgrade: %d\n", client->num, client->cIsUpgrade);
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader]  - cIsWebsocket: %d\n", client->num, client->cIsWebsocket);
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader]  - cKey: %s\n", client->num, client->cKey.c_str());
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader]  - cProtocol: %s\n", client->num, client->cProtocol.c_str());
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader]  - cExtensions: %s\n", client->num, client->cExtensions.c_str());
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader]  - cVersion: %d\n", client->num, client->cVersion);
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader]  - base64Authorization: %s\n", client->num, client->base64Authorization.c_str());
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader]  - cHttpHeadersValid: %d\n", client->num, client->cHttpHeadersValid);
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader]  - cMandatoryHeadersCount: %d\n", client->num, client->cMandatoryHeadersCount);
+
+        bool ok = (client->cIsUpgrade && client->cIsWebsocket);
+
+        if(ok) {
+            if(client->cUrl.length() == 0) {
+                ok = false;
+            }
+            if(client->cKey.length() == 0) {
+                ok = false;
+            }
+            if(client->cVersion != 13) {
+                ok = false;
+            }
+            if(!client->cHttpHeadersValid) {
+                ok = false;
+            }
+            if(client->cMandatoryHeadersCount != _mandatoryHttpHeaderCount) {
+                ok = false;
+            }
+        }
+
+        if(_base64Authorization.length() > 0) {
+            String auth = WEBSOCKETS_STRING("Basic ");
+            auth += _base64Authorization;
+            if(auth != client->base64Authorization) {
+                DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] HTTP Authorization failed!\n", client->num);
+                handleAuthorizationFailed(client);
+                return;
+            }
+        }
+
+        if(ok) {
+            DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] Websocket connection incoming.\n", client->num);
+
+            // generate Sec-WebSocket-Accept key
+            String sKey = acceptKey(client->cKey);
+
+            DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader]  - sKey: %s\n", client->num, sKey.c_str());
+
+            client->status = WSC_CONNECTED;
+
+            String handshake = WEBSOCKETS_STRING(
+                "HTTP/1.1 101 Switching Protocols\r\n"
+                "Server: arduino-WebSocketsServer\r\n"
+                "Upgrade: websocket\r\n"
+                "Connection: Upgrade\r\n"
+                "Sec-WebSocket-Version: 13\r\n"
+                "Sec-WebSocket-Accept: ");
+            handshake += sKey + NEW_LINE;
+
+            if(_origin.length() > 0) {
+                handshake += WEBSOCKETS_STRING("Access-Control-Allow-Origin: ");
+                handshake += _origin + NEW_LINE;
+            }
+
+            if(client->cProtocol.length() > 0) {
+                handshake += WEBSOCKETS_STRING("Sec-WebSocket-Protocol: ");
+                handshake += _protocol + NEW_LINE;
+            }
+
+            // header end
+            handshake += NEW_LINE;
+
+            DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] handshake %s", client->num, (uint8_t *)handshake.c_str());
+
+            write(client, (uint8_t *)handshake.c_str(), handshake.length());
+
+            headerDone(client);
+
+            // send ping
+            WebSockets::sendFrame(client, WSop_ping);
+
+            runCbEvent(client->num, WStype_CONNECTED, (uint8_t *)client->cUrl.c_str(), client->cUrl.length());
+
+        } else {
+            handleNonWebsocketConnection(client);
+        }
+    }
+}

+ 206 - 0
lib/arduinoWebSockets-2.1.4/src/WebSocketsServer.h

@@ -0,0 +1,206 @@
+/**
+ * @file WebSocketsServer.h
+ * @date 20.05.2015
+ * @author Markus Sattler
+ *
+ * Copyright (c) 2015 Markus Sattler. All rights reserved.
+ * This file is part of the WebSockets for Arduino.
+ *
+ * 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 WEBSOCKETSSERVER_H_
+#define WEBSOCKETSSERVER_H_
+
+#include "WebSockets.h"
+
+#ifndef WEBSOCKETS_SERVER_CLIENT_MAX
+#define WEBSOCKETS_SERVER_CLIENT_MAX (5)
+#endif
+
+class WebSocketsServer : protected WebSockets {
+  public:
+#ifdef __AVR__
+    typedef void (*WebSocketServerEvent)(uint8_t num, WStype_t type, uint8_t * payload, size_t length);
+    typedef bool (*WebSocketServerHttpHeaderValFunc)(String headerName, String headerValue);
+#else
+    typedef std::function<void(uint8_t num, WStype_t type, uint8_t * payload, size_t length)> WebSocketServerEvent;
+    typedef std::function<bool(String headerName, String headerValue)> WebSocketServerHttpHeaderValFunc;
+#endif
+
+    WebSocketsServer(uint16_t port, String origin = "", String protocol = "arduino");
+    virtual ~WebSocketsServer(void);
+
+    void begin(void);
+    void close(void);
+
+#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC)
+    void loop(void);
+#else
+    // Async interface not need a loop call
+    void loop(void) __attribute__((deprecated)) {}
+#endif
+
+    void onEvent(WebSocketServerEvent cbEvent);
+    void onValidateHttpHeader(
+        WebSocketServerHttpHeaderValFunc validationFunc,
+        const char * mandatoryHttpHeaders[],
+        size_t mandatoryHttpHeaderCount);
+
+    bool sendTXT(uint8_t num, uint8_t * payload, size_t length = 0, bool headerToPayload = false);
+    bool sendTXT(uint8_t num, const uint8_t * payload, size_t length = 0);
+    bool sendTXT(uint8_t num, char * payload, size_t length = 0, bool headerToPayload = false);
+    bool sendTXT(uint8_t num, const char * payload, size_t length = 0);
+    bool sendTXT(uint8_t num, String & payload);
+
+    bool broadcastTXT(uint8_t * payload, size_t length = 0, bool headerToPayload = false);
+    bool broadcastTXT(const uint8_t * payload, size_t length = 0);
+    bool broadcastTXT(char * payload, size_t length = 0, bool headerToPayload = false);
+    bool broadcastTXT(const char * payload, size_t length = 0);
+    bool broadcastTXT(String & payload);
+
+    bool sendBIN(uint8_t num, uint8_t * payload, size_t length, bool headerToPayload = false);
+    bool sendBIN(uint8_t num, const uint8_t * payload, size_t length);
+
+    bool broadcastBIN(uint8_t * payload, size_t length, bool headerToPayload = false);
+    bool broadcastBIN(const uint8_t * payload, size_t length);
+
+    bool sendPing(uint8_t num, uint8_t * payload = NULL, size_t length = 0);
+    bool sendPing(uint8_t num, String & payload);
+
+    bool broadcastPing(uint8_t * payload = NULL, size_t length = 0);
+    bool broadcastPing(String & payload);
+
+    void disconnect(void);
+    void disconnect(uint8_t num);
+
+    void setAuthorization(const char * user, const char * password);
+    void setAuthorization(const char * auth);
+
+    int connectedClients(bool ping = false);
+
+#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32)
+    IPAddress remoteIP(uint8_t num);
+#endif
+
+  protected:
+    uint16_t _port;
+    String _origin;
+    String _protocol;
+    String _base64Authorization;    ///< Base64 encoded Auth request
+    String * _mandatoryHttpHeaders;
+    size_t _mandatoryHttpHeaderCount;
+
+    WEBSOCKETS_NETWORK_SERVER_CLASS * _server;
+
+    WSclient_t _clients[WEBSOCKETS_SERVER_CLIENT_MAX];
+
+    WebSocketServerEvent _cbEvent;
+    WebSocketServerHttpHeaderValFunc _httpHeaderValidationFunc;
+
+    bool _runnning;
+
+    bool newClient(WEBSOCKETS_NETWORK_CLASS * TCPclient);
+
+    void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin);
+
+    void clientDisconnect(WSclient_t * client);
+    bool clientIsConnected(WSclient_t * client);
+
+#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC)
+    void handleNewClients(void);
+    void handleClientData(void);
+#endif
+
+    void handleHeader(WSclient_t * client, String * headerLine);
+
+    /**
+         * called if a non Websocket connection is coming in.
+         * Note: can be override
+         * @param client WSclient_t *  ptr to the client struct
+         */
+    virtual void handleNonWebsocketConnection(WSclient_t * client) {
+        DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] no Websocket connection close.\n", client->num);
+        client->tcp->write(
+            "HTTP/1.1 400 Bad Request\r\n"
+            "Server: arduino-WebSocket-Server\r\n"
+            "Content-Type: text/plain\r\n"
+            "Content-Length: 32\r\n"
+            "Connection: close\r\n"
+            "Sec-WebSocket-Version: 13\r\n"
+            "\r\n"
+            "This is a Websocket server only!");
+        clientDisconnect(client);
+    }
+
+    /**
+         * called if a non Authorization connection is coming in.
+         * Note: can be override
+         * @param client WSclient_t *  ptr to the client struct
+         */
+    virtual void handleAuthorizationFailed(WSclient_t * client) {
+        client->tcp->write(
+            "HTTP/1.1 401 Unauthorized\r\n"
+            "Server: arduino-WebSocket-Server\r\n"
+            "Content-Type: text/plain\r\n"
+            "Content-Length: 45\r\n"
+            "Connection: close\r\n"
+            "Sec-WebSocket-Version: 13\r\n"
+            "WWW-Authenticate: Basic realm=\"WebSocket Server\""
+            "\r\n"
+            "This Websocket server requires Authorization!");
+        clientDisconnect(client);
+    }
+
+    /**
+         * called for sending a Event to the app
+         * @param num uint8_t
+         * @param type WStype_t
+         * @param payload uint8_t *
+         * @param length size_t
+         */
+    virtual void runCbEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
+        if(_cbEvent) {
+            _cbEvent(num, type, payload, length);
+        }
+    }
+
+    /*
+         * Called at client socket connect handshake negotiation time for each http header that is not
+         * a websocket specific http header (not Connection, Upgrade, Sec-WebSocket-*)
+         * If the custom httpHeaderValidationFunc returns false for any headerName / headerValue passed, the
+         * socket negotiation is considered invalid and the upgrade to websockets request is denied / rejected
+         * This mechanism can be used to enable custom authentication schemes e.g. test the value
+         * of a session cookie to determine if a user is logged on / authenticated
+         */
+    virtual bool execHttpHeaderValidation(String headerName, String headerValue) {
+        if(_httpHeaderValidationFunc) {
+            //return the value of the custom http header validation function
+            return _httpHeaderValidationFunc(headerName, headerValue);
+        }
+        //no custom http header validation so just assume all is good
+        return true;
+    }
+
+  private:
+    /*
+         * returns an indicator whether the given named header exists in the configured _mandatoryHttpHeaders collection
+         * @param headerName String ///< the name of the header being checked
+         */
+    bool hasMandatoryHeader(String headerName);
+};
+
+#endif /* WEBSOCKETSSERVER_H_ */

+ 7 - 0
lib/arduinoWebSockets-2.1.4/src/libb64/AUTHORS

@@ -0,0 +1,7 @@
+libb64: Base64 Encoding/Decoding Routines
+======================================
+
+Authors:
+-------
+
+Chris Venter	chris.venter@gmail.com	http://rocketpod.blogspot.com

+ 29 - 0
lib/arduinoWebSockets-2.1.4/src/libb64/LICENSE

@@ -0,0 +1,29 @@
+Copyright-Only Dedication (based on United States law) 
+or Public Domain Certification
+
+The person or persons who have associated work with this document (the
+"Dedicator" or "Certifier") hereby either (a) certifies that, to the best of
+his knowledge, the work of authorship identified is in the public domain of the
+country from which the work is published, or (b) hereby dedicates whatever
+copyright the dedicators holds in the work of authorship identified below (the
+"Work") to the public domain. A certifier, moreover, dedicates any copyright
+interest he may have in the associated work, and for these purposes, is
+described as a "dedicator" below.
+
+A certifier has taken reasonable steps to verify the copyright status of this
+work. Certifier recognizes that his good faith efforts may not shield him from
+liability if in fact the work certified is not in the public domain.
+
+Dedicator makes this dedication for the benefit of the public at large and to
+the detriment of the Dedicator's heirs and successors. Dedicator intends this
+dedication to be an overt act of relinquishment in perpetuity of all present
+and future rights under copyright law, whether vested or contingent, in the
+Work. Dedicator understands that such relinquishment of all rights includes
+the relinquishment of all rights to enforce (by lawsuit or otherwise) those
+copyrights in the Work.
+
+Dedicator recognizes that, once placed in the public domain, the Work may be
+freely reproduced, distributed, transmitted, used, modified, built upon, or
+otherwise exploited by anyone for any purpose, commercial or non-commercial,
+and in any way, including by methods that have not yet been invented or
+conceived.

+ 98 - 0
lib/arduinoWebSockets-2.1.4/src/libb64/cdecode.c

@@ -0,0 +1,98 @@
+/*
+cdecoder.c - c source to a base64 decoding algorithm implementation
+
+This is part of the libb64 project, and has been placed in the public domain.
+For details, see http://sourceforge.net/projects/libb64
+*/
+
+#ifdef ESP8266
+#include <core_esp8266_features.h>
+#endif
+
+#if defined(ESP32)
+#define CORE_HAS_LIBB64
+#endif
+
+#ifndef CORE_HAS_LIBB64
+#include "cdecode_inc.h"
+
+int base64_decode_value(char value_in)
+{
+	static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51};
+	static const char decoding_size = sizeof(decoding);
+	value_in -= 43;
+	if (value_in < 0 || value_in > decoding_size) return -1;
+	return decoding[(int)value_in];
+}
+
+void base64_init_decodestate(base64_decodestate* state_in)
+{
+	state_in->step = step_a;
+	state_in->plainchar = 0;
+}
+
+int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in)
+{
+	const char* codechar = code_in;
+	char* plainchar = plaintext_out;
+	char fragment;
+
+	*plainchar = state_in->plainchar;
+
+	switch (state_in->step)
+	{
+		while (1)
+		{
+	case step_a:
+			do {
+				if (codechar == code_in+length_in)
+				{
+					state_in->step = step_a;
+					state_in->plainchar = *plainchar;
+					return plainchar - plaintext_out;
+				}
+				fragment = (char)base64_decode_value(*codechar++);
+			} while (fragment < 0);
+			*plainchar    = (fragment & 0x03f) << 2;
+	case step_b:
+			do {
+				if (codechar == code_in+length_in)
+				{
+					state_in->step = step_b;
+					state_in->plainchar = *plainchar;
+					return plainchar - plaintext_out;
+				}
+				fragment = (char)base64_decode_value(*codechar++);
+			} while (fragment < 0);
+			*plainchar++ |= (fragment & 0x030) >> 4;
+			*plainchar    = (fragment & 0x00f) << 4;
+	case step_c:
+			do {
+				if (codechar == code_in+length_in)
+				{
+					state_in->step = step_c;
+					state_in->plainchar = *plainchar;
+					return plainchar - plaintext_out;
+				}
+				fragment = (char)base64_decode_value(*codechar++);
+			} while (fragment < 0);
+			*plainchar++ |= (fragment & 0x03c) >> 2;
+			*plainchar    = (fragment & 0x003) << 6;
+	case step_d:
+			do {
+				if (codechar == code_in+length_in)
+				{
+					state_in->step = step_d;
+					state_in->plainchar = *plainchar;
+					return plainchar - plaintext_out;
+				}
+				fragment = (char)base64_decode_value(*codechar++);
+			} while (fragment < 0);
+			*plainchar++   |= (fragment & 0x03f);
+		}
+	}
+	/* control should not reach here */
+	return plainchar - plaintext_out;
+}
+
+#endif

+ 28 - 0
lib/arduinoWebSockets-2.1.4/src/libb64/cdecode_inc.h

@@ -0,0 +1,28 @@
+/*
+cdecode.h - c header for a base64 decoding algorithm
+
+This is part of the libb64 project, and has been placed in the public domain.
+For details, see http://sourceforge.net/projects/libb64
+*/
+
+#ifndef BASE64_CDECODE_H
+#define BASE64_CDECODE_H
+
+typedef enum
+{
+	step_a, step_b, step_c, step_d
+} base64_decodestep;
+
+typedef struct
+{
+	base64_decodestep step;
+	char plainchar;
+} base64_decodestate;
+
+void base64_init_decodestate(base64_decodestate* state_in);
+
+int base64_decode_value(char value_in);
+
+int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in);
+
+#endif /* BASE64_CDECODE_H */

+ 119 - 0
lib/arduinoWebSockets-2.1.4/src/libb64/cencode.c

@@ -0,0 +1,119 @@
+/*
+cencoder.c - c source to a base64 encoding algorithm implementation
+
+This is part of the libb64 project, and has been placed in the public domain.
+For details, see http://sourceforge.net/projects/libb64
+*/
+
+#ifdef ESP8266
+#include <core_esp8266_features.h>
+#endif
+
+#if defined(ESP32)
+#define CORE_HAS_LIBB64
+#endif
+
+#ifndef CORE_HAS_LIBB64
+#include "cencode_inc.h"
+
+const int CHARS_PER_LINE = 72;
+
+void base64_init_encodestate(base64_encodestate* state_in)
+{
+	state_in->step = step_A;
+	state_in->result = 0;
+	state_in->stepcount = 0;
+}
+
+char base64_encode_value(char value_in)
+{
+	static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+	if (value_in > 63) return '=';
+	return encoding[(int)value_in];
+}
+
+int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in)
+{
+	const char* plainchar = plaintext_in;
+	const char* const plaintextend = plaintext_in + length_in;
+	char* codechar = code_out;
+	char result;
+	char fragment;
+
+	result = state_in->result;
+
+	switch (state_in->step)
+	{
+		while (1)
+		{
+	case step_A:
+			if (plainchar == plaintextend)
+			{
+				state_in->result = result;
+				state_in->step = step_A;
+				return codechar - code_out;
+			}
+			fragment = *plainchar++;
+			result = (fragment & 0x0fc) >> 2;
+			*codechar++ = base64_encode_value(result);
+			result = (fragment & 0x003) << 4;
+	case step_B:
+			if (plainchar == plaintextend)
+			{
+				state_in->result = result;
+				state_in->step = step_B;
+				return codechar - code_out;
+			}
+			fragment = *plainchar++;
+			result |= (fragment & 0x0f0) >> 4;
+			*codechar++ = base64_encode_value(result);
+			result = (fragment & 0x00f) << 2;
+	case step_C:
+			if (plainchar == plaintextend)
+			{
+				state_in->result = result;
+				state_in->step = step_C;
+				return codechar - code_out;
+			}
+			fragment = *plainchar++;
+			result |= (fragment & 0x0c0) >> 6;
+			*codechar++ = base64_encode_value(result);
+			result  = (fragment & 0x03f) >> 0;
+			*codechar++ = base64_encode_value(result);
+
+			++(state_in->stepcount);
+			if (state_in->stepcount == CHARS_PER_LINE/4)
+			{
+				*codechar++ = '\n';
+				state_in->stepcount = 0;
+			}
+		}
+	}
+	/* control should not reach here */
+	return codechar - code_out;
+}
+
+int base64_encode_blockend(char* code_out, base64_encodestate* state_in)
+{
+	char* codechar = code_out;
+
+	switch (state_in->step)
+	{
+	case step_B:
+		*codechar++ = base64_encode_value(state_in->result);
+		*codechar++ = '=';
+		*codechar++ = '=';
+		break;
+	case step_C:
+		*codechar++ = base64_encode_value(state_in->result);
+		*codechar++ = '=';
+		break;
+	case step_A:
+		break;
+	}
+	*codechar++ = 0x00;
+
+	return codechar - code_out;
+}
+
+#endif

+ 31 - 0
lib/arduinoWebSockets-2.1.4/src/libb64/cencode_inc.h

@@ -0,0 +1,31 @@
+/*
+cencode.h - c header for a base64 encoding algorithm
+
+This is part of the libb64 project, and has been placed in the public domain.
+For details, see http://sourceforge.net/projects/libb64
+*/
+
+#ifndef BASE64_CENCODE_H
+#define BASE64_CENCODE_H
+
+typedef enum
+{
+	step_A, step_B, step_C
+} base64_encodestep;
+
+typedef struct
+{
+	base64_encodestep step;
+	char result;
+	int stepcount;
+} base64_encodestate;
+
+void base64_init_encodestate(base64_encodestate* state_in);
+
+char base64_encode_value(char value_in);
+
+int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in);
+
+int base64_encode_blockend(char* code_out, base64_encodestate* state_in);
+
+#endif /* BASE64_CENCODE_H */

+ 202 - 0
lib/arduinoWebSockets-2.1.4/src/libsha1/libsha1.c

@@ -0,0 +1,202 @@
+/* from valgrind tests */
+
+/* ================ sha1.c ================ */
+/*
+SHA-1 in C
+By Steve Reid <steve@edmweb.com>
+100% Public Domain
+
+Test Vectors (from FIPS PUB 180-1)
+"abc"
+  A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+  84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+A million repetitions of "a"
+  34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+*/
+
+/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */
+/* #define SHA1HANDSOFF * Copies data before messing with it. */
+
+#if !defined(ESP8266) && !defined(ESP32)
+
+#define SHA1HANDSOFF
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "libsha1.h"
+
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
+    |(rol(block->l[i],8)&0x00FF00FF))
+#elif BYTE_ORDER == BIG_ENDIAN
+#define blk0(i) block->l[i]
+#else
+#error "Endianness not defined!"
+#endif
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+    ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+
+void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
+{
+    uint32_t a, b, c, d, e;
+    typedef union {
+        unsigned char c[64];
+        uint32_t l[16];
+    } CHAR64LONG16;
+#ifdef SHA1HANDSOFF
+    CHAR64LONG16 block[1];  /* use array to appear as a pointer */
+    memcpy(block, buffer, 64);
+#else
+    /* The following had better never be used because it causes the
+     * pointer-to-const buffer to be cast into a pointer to non-const.
+     * And the result is written through.  I threw a "const" in, hoping
+     * this will cause a diagnostic.
+     */
+    CHAR64LONG16* block = (const CHAR64LONG16*)buffer;
+#endif
+    /* Copy context->state[] to working vars */
+    a = state[0];
+    b = state[1];
+    c = state[2];
+    d = state[3];
+    e = state[4];
+    /* 4 rounds of 20 operations each. Loop unrolled. */
+    R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+    R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+    R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+    R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+    R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+    R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+    R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+    R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+    R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+    R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+    R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+    R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+    R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+    R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+    R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+    R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+    R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+    R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+    R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+    R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+    /* Add the working vars back into context.state[] */
+    state[0] += a;
+    state[1] += b;
+    state[2] += c;
+    state[3] += d;
+    state[4] += e;
+    /* Wipe variables */
+    a = b = c = d = e = 0;
+#ifdef SHA1HANDSOFF
+    memset(block, '\0', sizeof(block));
+#endif
+}
+
+
+/* SHA1Init - Initialize new context */
+
+void SHA1Init(SHA1_CTX* context)
+{
+    /* SHA1 initialization constants */
+    context->state[0] = 0x67452301;
+    context->state[1] = 0xEFCDAB89;
+    context->state[2] = 0x98BADCFE;
+    context->state[3] = 0x10325476;
+    context->state[4] = 0xC3D2E1F0;
+    context->count[0] = context->count[1] = 0;
+}
+
+
+/* Run your data through this. */
+
+void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len)
+{
+    uint32_t i, j;
+
+    j = context->count[0];
+    if ((context->count[0] += len << 3) < j)
+        context->count[1]++;
+    context->count[1] += (len>>29);
+    j = (j >> 3) & 63;
+    if ((j + len) > 63) {
+        memcpy(&context->buffer[j], data, (i = 64-j));
+        SHA1Transform(context->state, context->buffer);
+        for ( ; i + 63 < len; i += 64) {
+            SHA1Transform(context->state, &data[i]);
+        }
+        j = 0;
+    }
+    else i = 0;
+    memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+
+/* Add padding and return the message digest. */
+
+void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
+{
+    unsigned i;
+    unsigned char finalcount[8];
+    unsigned char c;
+
+#if 0	/* untested "improvement" by DHR */
+    /* Convert context->count to a sequence of bytes
+     * in finalcount.  Second element first, but
+     * big-endian order within element.
+     * But we do it all backwards.
+     */
+    unsigned char *fcp = &finalcount[8];
+
+    for (i = 0; i < 2; i++)
+       {
+        uint32_t t = context->count[i];
+        int j;
+
+        for (j = 0; j < 4; t >>= 8, j++)
+	          *--fcp = (unsigned char) t;
+    }
+#else
+    for (i = 0; i < 8; i++) {
+        finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
+         >> ((3-(i & 3)) * 8) ) & 255);  /* Endian independent */
+    }
+#endif
+    c = 0200;
+    SHA1Update(context, &c, 1);
+    while ((context->count[0] & 504) != 448) {
+	c = 0000;
+        SHA1Update(context, &c, 1);
+    }
+    SHA1Update(context, finalcount, 8);  /* Should cause a SHA1Transform() */
+    for (i = 0; i < 20; i++) {
+        digest[i] = (unsigned char)
+         ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
+    }
+    /* Wipe variables */
+    memset(context, '\0', sizeof(*context));
+    memset(&finalcount, '\0', sizeof(finalcount));
+}
+/* ================ end of sha1.c ================ */
+
+
+#endif

+ 21 - 0
lib/arduinoWebSockets-2.1.4/src/libsha1/libsha1.h

@@ -0,0 +1,21 @@
+/* ================ sha1.h ================ */
+/*
+SHA-1 in C
+By Steve Reid <steve@edmweb.com>
+100% Public Domain
+*/
+
+#if !defined(ESP8266) && !defined(ESP32)
+
+typedef struct {
+    uint32_t state[5];
+    uint32_t count[2];
+    unsigned char buffer[64];
+} SHA1_CTX;
+
+void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
+void SHA1Init(SHA1_CTX* context);
+void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len);
+void SHA1Final(unsigned char digest[20], SHA1_CTX* context);
+
+#endif

+ 49 - 0
lib/arduinoWebSockets-2.1.4/tests/webSocket.html

@@ -0,0 +1,49 @@
+<html>
+<head>
+
+<script>
+var connection = new WebSocket('ws://10.11.2.1:81/', ['arduino']);
+
+connection.onopen = function () {
+	connection.send('Message from Browser to ESP8266 yay its Working!! ' + new Date()); 
+	connection.send('ping');
+	
+/*	setInterval(function() {
+		connection.send('Time: ' + new Date()); 
+	}, 20);
+*/
+connection.send('Time: ' + new Date()); 
+};
+
+connection.onerror = function (error) {
+	console.log('WebSocket Error ', error);
+};
+
+connection.onmessage = function (e) {
+	console.log('Server: ', e.data);
+	connection.send('Time: ' + new Date()); 
+};
+
+function sendRGB() {
+	var r = parseInt(document.getElementById('r').value).toString(16);
+	var g = parseInt(document.getElementById('g').value).toString(16);
+	var b = parseInt(document.getElementById('b').value).toString(16);
+	if(r.length < 2) { r = '0' + r; }
+	if(g.length < 2) { g = '0' + g; }
+	if(b.length < 2) { b = '0' + b; }
+	var rgb = '#'+r+g+b;
+	console.log('RGB: ' + rgb);
+	connection.send(rgb); 
+}
+
+</script>
+
+</head>
+<body>
+LED Control:<br/>
+<br/>
+R: <input id="r" type="range" min="0" max="255" step="1" onchange="sendRGB();" /><br/>
+G: <input id="g" type="range" min="0" max="255" step="1" onchange="sendRGB();" /><br/>
+B: <input id="b" type="range" min="0" max="255" step="1" onchange="sendRGB();" /><br/>
+</body>
+</html>

+ 57 - 0
lib/arduinoWebSockets-2.1.4/tests/webSocketServer/index.js

@@ -0,0 +1,57 @@
+#!/usr/bin/env node
+var WebSocketServer = require('websocket').server;
+var http = require('http');
+ 
+var server = http.createServer(function(request, response) {
+    console.log((new Date()) + ' Received request for ' + request.url);
+    response.writeHead(404);
+    response.end();
+});
+server.listen(8011, function() {
+    console.log((new Date()) + ' Server is listening on port 8011');
+});
+ 
+wsServer = new WebSocketServer({
+    httpServer: server,
+    // You should not use autoAcceptConnections for production 
+    // applications, as it defeats all standard cross-origin protection 
+    // facilities built into the protocol and the browser.  You should 
+    // *always* verify the connection's origin and decide whether or not 
+    // to accept it. 
+    autoAcceptConnections: false
+});
+ 
+function originIsAllowed(origin) {
+  // put logic here to detect whether the specified origin is allowed. 
+  return true;
+}
+ 
+wsServer.on('request', function(request) {
+	
+    if (!originIsAllowed(request.origin)) {
+      // Make sure we only accept requests from an allowed origin 
+      request.reject();
+      console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
+      return;
+    }
+    
+    var connection = request.accept('arduino', request.origin);
+    console.log((new Date()) + ' Connection accepted.');
+    
+	connection.on('message', function(message) {
+        if (message.type === 'utf8') {
+            console.log('Received Message: ' + message.utf8Data);
+           // connection.sendUTF(message.utf8Data);
+        }
+        else if (message.type === 'binary') {
+            console.log('Received Binary Message of ' + message.binaryData.length + ' bytes');
+           //connection.sendBytes(message.binaryData);
+        }
+    });
+    
+	connection.on('close', function(reasonCode, description) {
+        console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
+    });
+	
+	connection.sendUTF("Hallo Client!");
+});

+ 27 - 0
lib/arduinoWebSockets-2.1.4/tests/webSocketServer/package.json

@@ -0,0 +1,27 @@
+{
+  "name": "webSocketServer",
+  "version": "1.0.0",
+  "description": "WebSocketServer for testing",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/Links2004/arduinoWebSockets"
+  },
+  "keywords": [
+    "esp8266",
+    "websocket",
+    "arduino"
+  ],
+  "author": "Markus Sattler",
+  "license": "LGPLv2",
+  "bugs": {
+    "url": "https://github.com/Links2004/arduinoWebSockets/issues"
+  },
+  "homepage": "https://github.com/Links2004/arduinoWebSockets",
+  "dependencies": {
+    "websocket": "^1.0.18"
+  }
+}

+ 53 - 0
lib/arduinoWebSockets-2.1.4/travis/common.sh

@@ -0,0 +1,53 @@
+#!/bin/bash
+
+function build_sketches()
+{
+    local arduino=$1
+    local srcpath=$2
+    local platform=$3
+    local sketches=$(find $srcpath -name *.ino)
+    for sketch in $sketches; do
+        local sketchdir=$(dirname $sketch)
+        if [[ -f "$sketchdir/.$platform.skip" ]]; then
+            echo -e "\n\n ------------ Skipping $sketch ------------ \n\n";
+            continue
+        fi
+        echo -e "\n\n ------------ Building $sketch ------------ \n\n";
+        $arduino --verify $sketch;
+        local result=$?
+        if [ $result -ne 0 ]; then
+            echo "Build failed ($sketch) build verbose..."
+            $arduino --verify --verbose --preserve-temp-files $sketch
+            result=$?
+        fi
+        if [ $result -ne 0 ]; then
+            echo "Build failed ($1) $sketch"
+            return $result
+        fi
+    done
+}
+
+
+function get_core()
+{
+    echo Setup core for $1
+
+    cd $HOME/arduino_ide/hardware
+
+    if [ "$1" = "esp8266" ] ; then
+        mkdir esp8266com
+        cd esp8266com
+        git clone https://github.com/esp8266/Arduino.git esp8266
+        cd esp8266/tools
+        python get.py
+    fi
+
+    if [ "$1" = "esp32" ] ; then
+        mkdir espressif
+        cd espressif
+        git clone https://github.com/espressif/arduino-esp32.git esp32
+        cd esp32/tools
+        python get.py
+    fi
+
+}

BIN
releases/bin/WiFiThermostat.ino.d1_mini.20200113_v0.6.0.bin


BIN
releases/src/WiFiThermostat_0.6.0.zip


+ 12 - 6
src/WiFiThermostat/Buttonhandling.ino

@@ -100,7 +100,8 @@ void onButtonReleased(Button &btn, uint16_t duration)
 
 void plusButtonAction()
 {
-  Serial.println("Btn +");
+  //Serial.println("Btn +");
+  sendLog("DEV: Btn +", LOGLEVEL_INFO);
   if (!displayActive)
   {
     enableDisplay();
@@ -117,7 +118,8 @@ void plusButtonAction()
 
 void minusButtonAction()
 {
-  Serial.println("Btn -");
+  //Serial.println("Btn -");
+  sendLog("DEV: Btn -", LOGLEVEL_INFO);
   if (heatingMode == 1 && preset != 0)
     setPresetTo(0);
   if (!displayActive)
@@ -136,7 +138,8 @@ void minusButtonAction()
 
 void modeButtonAction()
 {
-  Serial.println("Btn mode");
+  //Serial.println("Btn mode");
+  sendLog("DEV: Btn MODE", LOGLEVEL_INFO);
   if (!displayActive)
   {
     enableDisplay();
@@ -170,7 +173,8 @@ void modeButtonAction()
 
 void modeButtonHoldAction()
 {
-  Serial.println("Btn mode held - toggle on/off");
+  //Serial.println("Btn mode held - toggle on/off");
+  sendLog("DEV: Btn MODE HOLD", LOGLEVEL_INFO);
   extendDisplayTimeout();
   if (pendingRestart)
   {
@@ -201,7 +205,8 @@ void modeButtonRebootAction()
 void pirSensorOnAction()
 {
   PIRSensorOn = true;
-  Serial.println("PIR sensor ON");
+  //Serial.println("PIR sensor ON");
+  //sendLog("SENS: PIR=ON", LOGLEVEL_INFO);
   publishCurrentPIRValue();
   if (confBas.PIR_enablesDisplay)
   {
@@ -215,6 +220,7 @@ void pirSensorOnAction()
 void pirSensorOffAction()
 {
   PIRSensorOn = false;
-  Serial.println("PIR sensor OFF");
+  //Serial.println("PIR sensor OFF");
+  //sendLog("SENS: PIR=OFF", LOGLEVEL_INFO);
   publishCurrentPIRValue();
 }

+ 163 - 24
src/WiFiThermostat/WiFiThermostat.ino

@@ -27,6 +27,10 @@
 #include <ButtonEventCallback.h>
 #include <PushButton.h>
 
+#include <time.h>
+
+#include <WebSocketsServer.h>
+
 // PRE-COMPILE CONFIGURATION
 
 //#define FORCE_SPIFFS_FORMAT
@@ -41,7 +45,7 @@
 #define SPIFFS_USE_MAGIC
 
 #define FIRMWARE_NAME "WiFiThermostat"
-#define FIRMWARE_VERSION "0.5.0"
+#define FIRMWARE_VERSION "0.6.0"
 #define FIRMWARE_URL "https://git.flokra.at/flo/WiFiThermostat"
 #define FIRMWARE_COPYRIGHT "FloKra"
 #define FIRMWARE_COPYRIGHT_URL "https://www.flokra.at/"
@@ -53,13 +57,21 @@
 #define MAX_MEASUREMENT_AGE 30000      // in ms, for measured inside Temp/Hum
 #define MAX_MEASUREMENT_AGE_OUT 300000 // in ms, for received outside Temp/Hum
 
+// NTP
+#define TIMEZONE "CET-1CEST,M3.5.0/02,M10.5.0/03"
+#define NTP_TIMEOUT 1500
+#define NTP_TIMEZONE 1
+#define NTP_MINUTES_TZ 0
+#define NTP_SYNC_INTERVAL 7200
+#define NTP_SERVER "pool.ntp.org"
+
 // conf DevWiFi
 //#define DEVICE_NAME "WTherm-1" - created from FIRMWARE_SHORTNAME + last 2
 // octets of MAC address
 #define HOST_NAME ""
 #define WIFI_APMODE_PASSWORD ""
-#define DEFAULT_WIFI_APMODE_TIMEOUT 5 //min
-#define DEFAULT_WIFI_RETRY_INTERVAL 5 //min
+#define DEFAULT_WIFI_APMODE_TIMEOUT 5      //min
+#define DEFAULT_WIFI_RETRY_INTERVAL 5      //min
 #define DEFAULT_WIFI_CONNCHECK_INTERVAL 20 //s
 #define DEFAULT_WIFI_REBOOT_ONNOCONNECT 60 //min
 
@@ -68,6 +80,7 @@
 #define DEFAULT_HTTP_USER ""
 #define DEFAULT_HTTP_PASS ""
 #define DEFAULT_HTTP_USER_AUTH false
+#define DEFAULT_ENABLE_WEBCONSOLE false
 
 // confMqtt
 #define MQTT_ENABLE true
@@ -79,7 +92,7 @@
 #define MQTT_TOPIC_OUT "Test/Thermostat/stat"
 #define MQTT_OUT_RETAIN false
 #define MQTT_OUT_RETAIN_SENSORS false
-#define MQTT_OUT_PUBLISH_INTERVAL 0 // min, 0=only on change
+#define MQTT_OUT_PUBLISH_INTERVAL 0         // min, 0=only on change
 #define MQTT_OUT_PUBLISH_INTERVAL_SENSORS 5 // min, 0=only on change
 #define MQTT_WILLTOPIC "Test/Thermostat/availability"
 #define MQTT_WILLQOS 2
@@ -174,8 +187,66 @@
 #define RELAISONSTATE HIGH
 #define BUTTONONSTATE LOW
 
+// LOG LEVELS
+#define LOGLEVEL_OFF 0
+#define LOGLEVEL_ERROR 1
+#define LOGLEVEL_WARN 2
+#define LOGLEVEL_INFO 3
+#define LOGLEVEL_DEBUG 4
+#define LOGLEVEL_VERBOSE 5
+
+#define DEFAULT_LOGLEVEL_SERIAL LOGLEVEL_DEBUG
+#define DEFAULT_LOGLEVEL_WEB LOGLEVEL_DEBUG
+#define DEFAULT_LOGLEVEL_MQTT LOGLEVEL_DEBUG
+#define DEFAULT_LOGLEVEL_SYSLOG LOGLEVEL_DEBUG
+
 // END PRE-COMPILE CONFIGURATION
 
+PROGMEM const char PGMStr_changedTo[] = "changed to";
+PROGMEM const char PGMStr_preset[] = "preset";
+PROGMEM const char PGMStr_heatingMode[] = "heatingMode";
+PROGMEM const char PGMStr_setTemp[] = "setTemp";
+PROGMEM const char PGMStr_currentSetTemp[] = "currSetTemp";
+PROGMEM const char PGMStr_setTempLow[] = "setTempLow";
+PROGMEM const char PGMStr_setTempLow2[] = "setTempLow2";
+PROGMEM const char PGMStr_switchHeating[] = "switch heating";
+PROGMEM const char PGMStr_mqttRetainedSave[] = "MQTT retained save";
+PROGMEM const char PGMStr_heating[] = "heating";
+PROGMEM const char PGMStr_thermostat[] = "TSTAT";
+PROGMEM const char PGMStr_WiFi[] = "WiFi";
+PROGMEM const char PGMStr_connectedTo[] = "connected to";
+PROGMEM const char PGMStr_withIP[] = "with IP";
+PROGMEM const char PGMStr_MQTT[] = "MQTT";
+PROGMEM const char PGMStr_connectedReconnects[] = "connected, reconnects";
+PROGMEM const char PGMStr_connectedFailed[] = "connect FAILED";
+
+const char PGMStr_MQTTStateM4[] PROGMEM = "CONNECTION_TIMEOUT";
+const char PGMStr_MQTTStateM3[] PROGMEM = "CONNECTION_LOST";
+const char PGMStr_MQTTStateM2[] PROGMEM = "CONNECT_FAILED";
+const char PGMStr_MQTTStateM1[] PROGMEM = "DISCONNECTED";
+const char PGMStr_MQTTState0[] PROGMEM = "CONNECTED";
+const char PGMStr_MQTTState1[] PROGMEM = "CONNECT_BAD_PROTOCOL";
+const char PGMStr_MQTTState2[] PROGMEM = "CONNECT_BAD_CLIENT_ID";
+const char PGMStr_MQTTState3[] PROGMEM = "CONNECT_UNAVAILABLE";
+const char PGMStr_MQTTState4[] PROGMEM = "CONNECT_BAD_CREDENTIALS";
+const char PGMStr_MQTTState5[] PROGMEM = "CONNECT_UNAUTHORIZED";
+
+char mqttCurrentStateName[25];
+
+const char *const PGMStr_MQTTStates[] PROGMEM =
+    {
+        PGMStr_MQTTStateM4,
+        PGMStr_MQTTStateM3,
+        PGMStr_MQTTStateM2,
+        PGMStr_MQTTStateM1,
+        PGMStr_MQTTState0,
+        PGMStr_MQTTState1,
+        PGMStr_MQTTState2,
+        PGMStr_MQTTState3,
+        PGMStr_MQTTState4,
+        PGMStr_MQTTState5,
+};
+
 //---------------------------------------------------------------------------------------------------------------------------------------------
 
 PushButton buttonPlus = PushButton(PIN_BUTTON_PLUS, ENABLE_INTERNAL_PULLUP);
@@ -189,6 +260,13 @@ bool mqttdebug = DEBUG_MQTT;
 bool WifiInApMode = false;
 unsigned long WifiApModeStartedAt;
 
+// time
+struct tm lt; // http://www.cplusplus.com/reference/ctime/tm/
+const char *const PROGMEM dayNames[] = {"Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"};
+const char *const PROGMEM dayShortNames[] = {"So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"};
+const char *const PROGMEM monthNames[] = {"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"};
+const char *const PROGMEM monthShortNames[] = {"Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"};
+
 // config variables - do not change here!
 
 // confDevWiFi
@@ -219,6 +297,7 @@ struct confDataWeb
   char http_pass1[31];
   char http_user2[31];
   char http_pass2[31];
+  bool enableConsole;
 } confWeb;
 
 // confMqtt
@@ -236,7 +315,7 @@ struct confDataMqtt
   bool mqtt_willRetain;    // MQTT Last Will retain
   char mqtt_willMsg[31];   // MQTT Last Will payload
   char mqtt_connMsg[31];
-  bool mqtt_outRetain; // send MQTT out with retain flag
+  bool mqtt_outRetain;         // send MQTT out with retain flag
   bool mqtt_outRetain_sensors; // send MQTT out with retain flag
   uint8_t mqtt_outPubInterval;
   uint8_t mqtt_outPubInterval_sensors;
@@ -294,6 +373,25 @@ struct confDataAdd
   char outHum_topic_in[51];
 } confAdd;
 
+// confTime
+struct confDataTime
+{
+  bool ntpEnable;
+  char timeZoneStr[50];
+  char ntpServer1[31];
+  char ntpServer2[31];
+  unsigned long ntpSyncInterval;
+} confTime;
+
+// confLog
+struct confDataLog
+{
+  uint8_t logLevelSerial;
+  uint8_t logLevelWeb;
+  uint8_t logLevelMqtt;
+  uint8_t logLevelSyslog;
+} confLog;
+
 bool mqtt_tempDisabled_credentialError = false;
 
 char mqtt_topic_in_cmd[61];
@@ -427,11 +525,22 @@ void mqttCallback(char *topic, byte *payload, uint16_t length);
 PubSubClient mqttclient(espClient);
 
 ESP8266WebServer httpServer(80);
+WebSocketsServer webSocket(81);
 DNSServer dnsServer;
 PersWiFiManager persWM(httpServer, dnsServer);
 
 ESP8266HTTPUpdateServer httpUpdater;
 
+// WebSocketsServer
+unsigned long int wsValidSessionId = 0;
+
+void updateTime();
+bool setupTime();
+void updateTimeFromNTP();
+
+void sendLog(const char *msg, uint8_t loglevel = 1);
+void sendLog(const __FlashStringHelper *msg, uint8_t loglevel = 1);
+
 void setup()
 {
   Serial.begin(115200);
@@ -454,15 +563,17 @@ void setup()
 
   loadConf_defaults();
 
-  if (serialdebug)
-    Serial.println("default config values loaded..");
+  //if (serialdebug)
+  //  Serial.println("default config values loaded..");
+
+  sendLog(F("DEV: default config values loaded."), LOGLEVEL_INFO);
 
   initDisplay();
 
-  Serial.println(F("Mounting SPIFFS..."));
+  sendLog(F("Mounting SPIFFS..."), LOGLEVEL_INFO);
   if (!SPIFFS.begin())
   {
-    Serial.println(F("Failed to mount SPIFFS"));
+    sendLog(F("Failed to mount SPIFFS"), LOGLEVEL_ERROR);
     return;
   }
 
@@ -481,7 +592,7 @@ void setup()
   // is necessary if (strlen(deviceName) < 4) strlcpy(deviceName, DEVICE_NAME,
   // 31);
   loadConf_restoreDefaultWhenMissing();
-  
+
   updateCurrentHeatingModeName();
   updateCurrentPresetName();
 
@@ -496,30 +607,41 @@ void setup()
 
   // optional code handlers to run everytime wifi is connected...
   persWM.onConnect([]() {
-    Serial.print(F("WiFi: connected to '"));
-    Serial.print(WiFi.SSID());
-    Serial.print(F("' with IP: "));
-    Serial.println(WiFi.localIP());
+    //Serial.print(F("WiFi: connected to '"));
+    //Serial.print(WiFi.SSID());
+    //Serial.print(F("' with IP: "));
+    //Serial.println(WiFi.localIP());
+    char buf[60];
+    sprintf(buf, "%s: %s '%s' %s: '%s'", PGMStr_WiFi, PGMStr_connectedTo, WiFi.SSID().c_str(), PGMStr_withIP, WiFi.localIP().toString().c_str());
+    sendLog(buf, LOGLEVEL_INFO);
+
     WifiInApMode = false;
+    if (confTime.ntpEnable)
+      setupTime();
     displayShowWifiConnected();
   });
   //...or AP mode is started
   persWM.onAp([]() {
-    Serial.print(F("WiFi: AP mode, SSID: '"));
-    Serial.print(persWM.getApSsid());
-    if (strlen(confDevWiFi.WiFiAPModePassword) >= 4)
-    {
-      Serial.print("', PW: '");
-      Serial.print(confDevWiFi.WiFiAPModePassword);
-    }
-    Serial.println("'");
+    //Serial.print(F("WiFi: AP mode, SSID: '"));
+    //Serial.print(persWM.getApSsid());
+    //if (strlen(confDevWiFi.WiFiAPModePassword) >= 4)
+    //{
+    //  Serial.print("', PW: '");
+    //  Serial.print(confDevWiFi.WiFiAPModePassword);
+    //}
+    //Serial.println("'");
+    char buf[60];
+    sprintf(buf, "%s: AP-MODE started, SSID '%s', PW: '%s'", PGMStr_WiFi, persWM.getApSsid().c_str(), confDevWiFi.WiFiAPModePassword);
+    sendLog(buf, LOGLEVEL_INFO);
+
     WifiInApMode = true;
     WifiApModeStartedAt = millis();
     displayShowWifiConnectionError();
   });
 
   persWM.onApOff([]() {
-    Serial.println(F("WiFi: AP mode stopped"));
+    //Serial.println(F("WiFi: AP mode stopped"));
+    sendLog(F("WiFi: AP-MODE stopped"), LOGLEVEL_INFO);
   });
 
   // sets network name and password for AP mode
@@ -561,13 +683,18 @@ void setup()
 
   httpServerInit();
 
+  if (confWeb.enableConsole)
+    startWebSocketServer();
+
   if (confMqtt.mqtt_enable)
   {
     mqttPrepareSubscribeTopics();
     mqttClientInit();
   }
 
-  Serial.println("setup complete.");
+  buildUptimeString();
+
+  sendLog(F("DEV: setup complete."), LOGLEVEL_INFO);
 } // void setup
 
 void loop()
@@ -610,4 +737,16 @@ void loop()
     updateDisplay();
   }
 
+  if (confWeb.enableConsole)
+  {
+    if (WiFi.status() == WL_CONNECTED)
+    {
+      webSocket.loop();
+    }
+    else
+    {
+      webSocket.disconnect();
+    }
+  }
+
 } // void loop

+ 2 - 2
src/WiFiThermostat/commands.ino

@@ -14,7 +14,7 @@ void serialEvent() {
     Serial.print(serBuffer);
     Serial.println("'");
 #endif
-    strlcpy(cmdPayload, serBuffer, 101);
+    strlcpy(cmdPayload, serBuffer, sizeof(cmdPayload));
     cmdInQueue = true;
     evalCmd();
     serBufferCount = 0;
@@ -176,7 +176,7 @@ void evalCmd() {
       yield();
             
       //Serial.println("saved config to SPIFFS");
-      mqttSendLog("saved config to SPIFFS");
+      sendLog("saved config to SPIFFS");
       //Serial.println("reloading config to check...");
       //loadConfig();
       //yield();

+ 8 - 0
src/WiFiThermostat/commonFunctions.ino

@@ -10,3 +10,11 @@ char *strlwr(char *str)
   }
   return str;
 }
+
+double round2(double value) {
+   return round(value * 100) / 100.0;
+}
+
+double round1(double value) {
+   return round(value * 10) / 10.0;
+}

+ 292 - 1
src/WiFiThermostat/config.ino

@@ -42,11 +42,19 @@ void setConfig(char *param, char *value)
   else if (strcmp(param, "mode") == 0)
   {
     int val = atoi(value);
-    if (val >= 0 && val <= 3)
+    if (val >= 0 && val <= 1)
     {
       setHeatingmodeTo(val);
     }
   }
+  else if (strcmp(param, "preset") == 0)
+  {
+    int val = atoi(value);
+    if (val >= 0 && val <= 2)
+    {
+      setPresetTo(val);
+    }
+  }
 
   // confDevWiFi
   else if (strcmp(param, "devname") == 0)
@@ -140,6 +148,13 @@ void setConfig(char *param, char *value)
   {
     if(!strcmp(value,"****") == 0) strlcpy(confWeb.http_pass2, value, sizeof(confWeb.http_pass2));
   }
+  else if (strcmp(param, "enableconsole") == 0)
+  {
+    if (atoi(value) == 1)
+      confWeb.enableConsole = true;
+    else
+      confWeb.enableConsole = false;
+  }
 
   // confMqtt
   else if (strcmp(param, "mqttenable") == 0)
@@ -423,6 +438,52 @@ void setConfig(char *param, char *value)
   {
     strlcpy(confAdd.mqtt_payload_pir_off, value, sizeof(confAdd.mqtt_payload_pir_off));
   }
+
+  // confTime
+  else if (strcmp(param, "tzstr") == 0)
+  {
+    strlcpy(confTime.timeZoneStr, value, sizeof(confTime.timeZoneStr));
+  }
+  else if (strcmp(param, "ntpenable") == 0)
+  {
+    int valueInt = atoi(value);
+    if (valueInt == 1)
+      confTime.ntpEnable = true;
+    else
+      confTime.ntpEnable = false;
+  }
+  else if (strcmp(param, "ntpserver1") == 0)
+  {
+    strlcpy(confTime.ntpServer1, value, sizeof(confTime.ntpServer1));
+  }
+  else if (strcmp(param, "ntpserver2") == 0)
+  {
+    strlcpy(confTime.ntpServer2, value, sizeof(confTime.ntpServer2));
+  }
+  else if (strcmp(param, "ntpsyncint") == 0)
+  {
+    confTime.ntpSyncInterval = atoi(value);
+  }
+
+  // confLog
+  else if (strcmp(param, "loglevser") == 0)
+  {
+    int valueInt = atoi(value);
+    if (valueInt >= 0 && valueInt <= 5)
+      confLog.logLevelSerial = valueInt;
+  }
+  else if (strcmp(param, "loglevweb") == 0)
+  {
+    int valueInt = atoi(value);
+    if (valueInt >= 0 && valueInt <= 5)
+      confLog.logLevelWeb = valueInt;
+  }
+  else if (strcmp(param, "loglevmqtt") == 0)
+  {
+    int valueInt = atoi(value);
+    if (valueInt >= 0 && valueInt <= 5)
+      confLog.logLevelMqtt = valueInt;
+  }
 }
 
 /*
@@ -968,6 +1029,11 @@ boolean loadConfigWeb()
       strlcpy(confWeb.http_user2, json["httpU2"] | "", sizeof(confWeb.http_user2));
       strlcpy(confWeb.http_pass2, json["httpP2"] | "", sizeof(confWeb.http_pass2));
 
+      if ((json["enableConsole"] | 0) == 1)
+        confWeb.enableConsole = true;
+      else
+        confWeb.enableConsole = false;
+
       Serial.println(F("Loaded config values:"));
       printConfigWeb();
       return true;
@@ -1349,6 +1415,154 @@ boolean loadConfigAdd()
   }
 } // loadConfigAdd
 
+
+boolean loadConfigTime()
+{
+  char configFileName[] = "/confTime.json";
+  if (SPIFFS.exists(configFileName))
+  {
+    File configFile = SPIFFS.open(configFileName, "r");
+    if (!configFile)
+    {
+      Serial.print(F("ERR: Failed to open file '"));
+      Serial.print(configFileName);
+      Serial.println("'");
+      return false;
+    }
+    else
+    {
+      Serial.print("file '");
+      Serial.print(configFileName);
+      Serial.print("' opened. ");
+      size_t size = configFile.size();
+      Serial.print("size=");
+      Serial.println(size);
+
+      if (size > 1000)
+      {
+        Serial.println(F("Config file size is too large"));
+        return false;
+      }
+
+      std::unique_ptr<char[]> buf(new char[size]);
+#ifdef DEBUGMODE
+      Serial.println(F("file content:"));
+      while (configFile.available())
+      {
+        Serial.write(configFile.read());
+      }
+      Serial.println();
+      configFile.seek(0, SeekSet); // reset so that we can read again
+#endif
+      configFile.readBytes(buf.get(), size);
+
+      DynamicJsonDocument json(size + 50);
+      DeserializationError error = deserializeJson(json, buf.get());
+      if (error)
+      {
+        Serial.println(F("Failed to parse config file"));
+        return false;
+      }
+
+      if ((json["NTPEnable"] | 0) == 1)
+        confTime.ntpEnable = true;
+      else
+        confTime.ntpEnable = false;
+
+      strlcpy(confTime.ntpServer1, json["NTPServer1"] | "", sizeof(confTime.ntpServer1));
+      strlcpy(confTime.ntpServer2, json["NTPServer2"] | "", sizeof(confTime.ntpServer2));
+      confTime.ntpSyncInterval = json["NTPSyncInt"];
+      strlcpy(confTime.timeZoneStr, json["TZstr"] | "", sizeof(confTime.timeZoneStr));
+
+      // Serial.println("Loaded config values:");
+      // printConfigWeb();
+      return true;
+    }
+    configFile.close();
+  }
+  else
+  {
+    Serial.print(F("NOTE: could not load confTime - file '"));
+    Serial.print(configFileName);
+    Serial.println(F("' does not exist."));
+    return false;
+  }
+} // loadConfigTime
+
+
+boolean loadConfigLog()
+{
+  char configFileName[] = "/confLog.json";
+  if (SPIFFS.exists(configFileName))
+  {
+    File configFile = SPIFFS.open(configFileName, "r");
+    if (!configFile)
+    {
+      Serial.print(F("ERR: Failed to open file '"));
+      Serial.print(configFileName);
+      Serial.println("'");
+      return false;
+    }
+    else
+    {
+      Serial.print("file '");
+      Serial.print(configFileName);
+      Serial.print("' opened. ");
+      size_t size = configFile.size();
+      Serial.print("size=");
+      Serial.println(size);
+
+      if (size > 1000)
+      {
+        Serial.println(F("Config file size is too large"));
+        return false;
+      }
+
+      std::unique_ptr<char[]> buf(new char[size]);
+#ifdef DEBUGMODE
+      Serial.println(F("file content:"));
+      while (configFile.available())
+      {
+        Serial.write(configFile.read());
+      }
+      Serial.println();
+      configFile.seek(0, SeekSet); // reset so that we can read again
+#endif
+      configFile.readBytes(buf.get(), size);
+
+      DynamicJsonDocument json(size + 50);
+      DeserializationError error = deserializeJson(json, buf.get());
+      if (error)
+      {
+        Serial.println(F("Failed to parse config file"));
+        return false;
+      }
+
+      //if ((json["NTPEnable"] | 0) == 1)
+      //  confTime.ntpEnable = true;
+      //else
+      //  confTime.ntpEnable = false;
+
+      confLog.logLevelSerial = json["logLevSer"];
+      confLog.logLevelWeb = json["logLevWeb"];
+      confLog.logLevelMqtt = json["logLevMqtt"];
+
+      // Serial.println("Loaded config values:");
+      // printConfigWeb();
+      return true;
+    }
+    configFile.close();
+  }
+  else
+  {
+    Serial.print(F("NOTE: could not load confTime - file '"));
+    Serial.print(configFileName);
+    Serial.println(F("' does not exist."));
+    return false;
+  }
+} // loadConfigLog
+
+
 boolean loadSetTemp()
 { // loadSetTemp
   File configFile = SPIFFS.open("/setTemp", "r");
@@ -1473,6 +1687,11 @@ boolean saveConfigWeb()
   json["httpU2"] = confWeb.http_user2;
   json["httpP2"] = confWeb.http_pass2;
 
+  if (confWeb.enableConsole)
+    json["enableConsole"] = 1;
+  else
+    json["enableConsole"] = 0;
+
   yield();
 
   File configFile = SPIFFS.open(configFileName, "w");
@@ -1694,6 +1913,61 @@ boolean saveConfigAdd()
   return true;
 } // saveConfigAdd
 
+boolean saveConfigTime()
+{
+  char configFileName[] = "/confTime.json";
+  DynamicJsonDocument json(1050);
+
+
+  if(confTime.ntpEnable) json["NTPEnable"] = 1;
+  else json["NTPEnable"] = 0;
+
+  json["NTPServer1"] = confTime.ntpServer1;
+  json["NTPServer2"] = confTime.ntpServer2;
+  json["NTPSyncInt"] = confTime.ntpSyncInterval;
+  json["TZstr"] = confTime.timeZoneStr;
+
+  yield();
+
+  File configFile = SPIFFS.open(configFileName, "w");
+  if (!configFile)
+  {
+    Serial.print("Failed to open file '");
+    Serial.print(configFileName);
+    Serial.println("' for writing");
+    return false;
+  }
+  serializeJson(json, configFile);
+  return true;
+} // saveConfigTime
+
+boolean saveConfigLog()
+{
+  char configFileName[] = "/confLog.json";
+  DynamicJsonDocument json(1050);
+
+
+  //if(confTime.ntpEnable) json["NTPEnable"] = 1;
+  //else json["NTPEnable"] = 0;
+
+  json["logLevSer"] = confLog.logLevelSerial;
+  json["logLevWeb"] = confLog.logLevelWeb;
+  json["logLevMqtt"] = confLog.logLevelMqtt;
+
+  yield();
+
+  File configFile = SPIFFS.open(configFileName, "w");
+  if (!configFile)
+  {
+    Serial.print("Failed to open file '");
+    Serial.print(configFileName);
+    Serial.println("' for writing");
+    return false;
+  }
+  serializeJson(json, configFile);
+  return true;
+} // saveConfigLog
+
 boolean saveSetTemp()
 { // saveSetTemp
   File configFile = SPIFFS.open("/setTemp", "w");
@@ -1780,6 +2054,8 @@ void loadConf_all()
   loadConfigBas();
   loadConfigAdv();
   loadConfigAdd();
+  loadConfigTime();
+  loadConfigLog();
 }
 
 void loadSavedValues()
@@ -1903,6 +2179,18 @@ void loadConf_defaults()
   strlcpy(confAdd.mqtt_payload_pir_off, MQTT_TOPIC_PIR_OFF, sizeof(confAdd.mqtt_payload_pir_off));
   strlcpy(confAdd.outTemp_topic_in, OUTTEMP_TOPIC_IN, sizeof(confAdd.outTemp_topic_in));
   strlcpy(confAdd.outHum_topic_in, OUTHUM_TOPIC_IN, sizeof(confAdd.outHum_topic_in));
+
+  // confTime
+  strlcpy(confTime.timeZoneStr, TIMEZONE, sizeof(confTime.timeZoneStr));
+  strlcpy(confTime.ntpServer1, NTP_SERVER, sizeof(confTime.ntpServer1));
+  confTime.ntpSyncInterval = NTP_SYNC_INTERVAL;
+
+  // confLog
+  confLog.logLevelSerial = DEFAULT_LOGLEVEL_SERIAL;
+  confLog.logLevelWeb = DEFAULT_LOGLEVEL_WEB;
+  confLog.logLevelMqtt = DEFAULT_LOGLEVEL_MQTT;
+  confLog.logLevelSyslog = DEFAULT_LOGLEVEL_SYSLOG;
+  
 }
 
 void loadConf_restoreDefaultWhenMissing()
@@ -1926,4 +2214,7 @@ void loadConf_restoreDefaultWhenMissing()
     strlcpy(confAdv.oTempLabel, OUTSIDE_TEMP_LABEL, sizeof(confAdv.oTempLabel));
 
   if(confDevWiFi.WiFiConnCheckInterval == 0) confDevWiFi.WiFiConnCheckInterval = DEFAULT_WIFI_CONNCHECK_INTERVAL;
+
+  //NTP
+  if(strlen(confTime.ntpServer1) == 0) strlcpy(confTime.ntpServer1, NTP_SERVER, sizeof(confTime.ntpServer1));
 }

+ 0 - 0
src/WiFiThermostat/html.ino → src/WiFiThermostat/html.h


+ 11 - 1
src/WiFiThermostat/html_conf.ino → src/WiFiThermostat/html_conf.h

@@ -4,16 +4,26 @@ static const char html_conf_body[] PROGMEM = R"=====(
 <p><b>Configuration</b></p>
 <table style='width:100%'>
 <tr><td><form action='confdevwifi' method='get'><button>Device &amp; WiFi</button></form></td></tr>
+<tr><td><form action='conftime' method='get'><button>Date &amp; Time</button></form></td></tr>
+<tr><td><form action='conflog' method='get'><button>Logging</button></form></td></tr>
 <tr><td><form action='confweb' method='get'><button>Web Interface</button></form></td></tr>
 <tr><td><form action='confmqtt' method='get'><button>MQTT</button></form></td></tr>
 <tr><td><form action='confbas' method='get'><button>Thermostat Basic</button></form></td></tr>
 <tr><td><form action='confadv' method='get'><button>Thermostat Advanced</button></form></td></tr>
 <tr><td><form action='confadd' method='get'><button>Additional Functions</button></form></td></tr>
 <tr><td><form action='update' method='get'><button>OTA Firmware Update</button></form></td></tr>
+)====="; // html_conf_body
+
+static const char html_conf_body_console[] PROGMEM = R"=====(
+<tr><td>&nbsp;</td></tr>
+<tr><td><form action='console' method='get'><button>Console</button></form></td></tr>
+)====="; // html_conf_body
+
+static const char html_conf_body_end[] PROGMEM = R"=====(
 </table>
 <div></div>
 <table style='width:100%'><tr>
 <td style='width:50%'><form action='.' method='get'><button class='bgrey'>Main Menu</button></form></td>
 <td><form action='/' method='get' onsubmit='return confirm("Confirm Restart");'><button name='restart' class='bred'>Restart</button></form></td>
 </tr></table>
-)====="; // html_conf_body
+)====="; // html_conf_body_end

+ 0 - 0
src/WiFiThermostat/html_confadd.ino → src/WiFiThermostat/html_confAdd.h


+ 0 - 0
src/WiFiThermostat/html_confadv.ino → src/WiFiThermostat/html_confAdv.h


+ 0 - 0
src/WiFiThermostat/html_confbas.ino → src/WiFiThermostat/html_confBas.h


+ 11 - 9
src/WiFiThermostat/html_confDevWiFi.ino → src/WiFiThermostat/html_confDevWiFi.h

@@ -86,29 +86,31 @@ static const char html_confDevWiFi_body[] PROGMEM = R"=====(
 <p class='n'>if blank, random hostname will be generated</p>
 </fieldset>
 <br>
-<p class='n'>After boot, firmware will first try to connect to Wifi-1, then WiFi-2. <br>
-If both fails it switches to AP-Mode. <br>
-AP-Mode is disabled again after configured timeout and a reconnect is tried <br>
-every some minutes as configured below. If no connection can be established <br>
-the module will reboot after another timeout (if configured). <br>
-Reconnect always tries Wifi-1 first, then WiFi-2 (also if connection is lost).</p>
+<p class='n'>After boot, firmware will first try to connect to Main Wifi-AP, <br>
+then if unsuccessful Fallback-AP. <br>
+If both fails it switches to Configuration AP-Mode. <br>
+AP-Mode is disabled again after configured timeout and a reconnect to the set <br>
+APs is tried every some minutes as configured below. <br>
+If no connection can be established the module will reboot after another <br>
+timeout if configured. <br>
+Reconnect always tries Main-AP first, then Fallback-AP.</p>
 <div></div>
 <fieldset>
-<legend>WiFi-1</legend>
+<legend>WiFi - Default-AP</legend>
 <p><b>Set</b>&nbsp;<input type='checkbox' id='WPW1Set' name='WPW1Set'></p>
 <p><b>SSID</b><br><input type='text' length=32 name='SSID1' id='SSID1'></p>
 <p><b>Password</b>&nbsp;<input type='checkbox' onclick='sp("WPW1")'>&nbsp;show<br><input type='password' length=64 name='WPW1' id='WPW1'></p>
 </fieldset>
 <br>
 <fieldset>
-<legend>WiFi-2</legend>
+<legend>WiFi - Fallback-AP</legend>
 <p><b>Set</b>&nbsp;<input type='checkbox' id='WPW2Set' name='WPW2Set'></p>
 <p><b>SSID</b><br><input type='text' length=32 name='SSID2' id='SSID2'></p>
 <p><b>Password</b>&nbsp;&nbsp;<input type='checkbox' onclick='sp("WPW2")'>&nbsp;show<br><input type='password' length=64 name='WPW2' id='WPW2'></p>
 </fieldset>
 <div></div><br>
 <fieldset>
-<legend>AP Mode</legend>
+<legend>Configuration AP-Mode</legend>
 <p><b>SSID</b>: <i><span id='SSIDAP'></span></i>
 <p><b>Password *</b><input type='checkbox' id='WPWAPSet' name='WPWAPSet' onclick='sp("WPWAP")'><br><input type='password' name='WPWAP' id='WPWAP'></p>
 <p class='n'>* min. 8 chars, empty password for open WiFi</p>

+ 93 - 0
src/WiFiThermostat/html_confLog.h

@@ -0,0 +1,93 @@
+/* clang-format off */
+
+static const char html_conflog_script[] PROGMEM = R"=====(
+<script>
+  function g(i) { return document.getElementById(i) };
+  function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
+  var xhttp, reqTime, reqFin;
+  function selectElement(el, val) {    
+    el.value = val;
+}
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 1000;
+    xhttp.overrideMimeType('application/json');
+    xhttp.open('POST', 'confDataLog');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+		selectElement(g('logLevSer'), data.logLevSer);
+		selectElement(g('logLevWeb'), data.logLevWeb);
+		selectElement(g('logLevMqtt'), data.logLevMqtt);
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  function saveConf() {
+    g('frmConf').submit();
+  }
+  function init() {
+    transmit();
+  }
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+)====="; // html_confweb_script
+
+static const char html_conflog_body[] PROGMEM = R"=====(
+<p><b>Configuration - Logging</b></p>
+<div class='config'>
+<form id='frmConf' action='setConfLog' method='POST'>
+<fieldset>
+<legend>Log Levels</legend>
+<p><b>Serial</b><br>
+<select name='logLevSer' id='logLevSer'>
+  <option value="0">Off</option>
+  <option value="1">Error</option>
+  <option value="2">Warn</option>
+  <option value="3">Info</option>
+  <option value="4">Debug</option>
+  <option value="5">Verbose</option>
+</select></p>
+<p><b>Web-Console</b><br>
+<select name='logLevWeb' id='logLevWeb'>
+  <option value="0">Off</option>
+  <option value="1">Error</option>
+  <option value="2">Warn</option>
+  <option value="3">Info</option>
+  <option value="4">Debug</option>
+  <option value="5">Verbose</option>
+</select></p>
+<p><b>MQTT</b><br>
+<select name='logLevMqtt' id='logLevMqtt'>
+  <option value="0">Off</option>
+  <option value="1">Error</option>
+  <option value="2">Warn</option>
+  <option value="3">Info</option>
+  <option value="4">Debug</option>
+  <option value="5">Verbose</option>
+</select></p>
+<br>
+</fieldset>
+</form>
+<div></div>
+<table style='width:100%'>
+<td style='width:50%'><button onclick='location="conf";' class='bgrey'>Cancel</button></td>
+<td style='width:50%'><button onclick='return saveConf()' class='bred'>Save</button></td>
+</tr></table>
+<div></div>
+</div>
+)====="; // html_confweb_body

+ 0 - 0
src/WiFiThermostat/html_confmqtt.ino → src/WiFiThermostat/html_confMqtt.h


+ 93 - 0
src/WiFiThermostat/html_confTime.h

@@ -0,0 +1,93 @@
+/* clang-format off */
+
+static const char html_conftime_script[] PROGMEM = R"=====(
+<script>
+  function g(i) { return document.getElementById(i) };
+  function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
+  var xhttp, reqTime, reqFin;
+  function setCbx(el, da) {
+    if(da == '1') {
+      el.checked = true;
+      el.style.visibility = 'visible';
+    }
+    else {
+      el.checked = false;
+      el.style.visibility = 'visible';
+    }
+  }
+  function updCbxVal(el) {
+    if (el.checked) el.value = '1';
+    else {
+      el.checked = true;
+    el.value = '0';
+      el.style.visibility = 'hidden';
+    }
+  }
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+    updCbxVal(g('NTPEnable'));
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 1000;
+    xhttp.overrideMimeType('application/json');
+    xhttp.open('POST', 'confDataTime');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+        setCbx(g('NTPEnable'), data.NTPEnable);
+        g('NTPServer1').value = data.NTPServer1;
+        g('NTPServer2').value = data.NTPServer2;
+        g('TZStr').value = data.TZStr;
+        g('NTPSyncInt').value = data.NTPSyncInt;
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  //transmit();
+  function saveConf() {
+    updCbxVal(g('NTPEnable'));
+    g('frmConf').submit();
+  }
+  function init() {
+    transmit();
+  }
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+)====="; // html_conftime_script
+
+static const char html_conftime_body[] PROGMEM = R"=====(
+<b>Configuration - Date &amp; Time</b>
+<div class='config'>
+<form id='frmConf' action='setConfTime' method='POST'>
+<br>
+<fieldset>
+<legend>NTP-Server</legend>
+<p><b>Enable NTP Time Sync</b>&nbsp;<input type='checkbox' name='NTPEnable' id='NTPEnable'></p>
+<p><b>NTP Server 1</b><br><input type='text' name='NTPServer1' id='NTPServer1'></p>
+<p><b>NTP Server 2</b><br><input type='text' name='NTPServer2' id='NTPServer2'></p>
+<p><b>Timezone String</b><br><input type='text' name='TZStr' id='TZStr'></p>
+<p class='n'>a valid TZ string (right column) from here: <br><a href='https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv'>zones.csv</a></p>
+<p><b>NTP Sync Interval [s]</b><br><input type='text' name='NTPSyncInt' id='NTPSyncInt'></p>
+</fieldset>
+<div></div><br>
+</form>
+<div></div>
+<table style='width:100%'>
+<td style='width:50%'><button onclick='location="conf";' class='bgrey'>Cancel</button></td>
+<td style='width:50%'><button onclick='return saveConf()' class='bred'>Save</button></td>
+</tr></table>
+</div>
+)====="; // html_conftime_body
+

+ 9 - 1
src/WiFiThermostat/html_confweb.ino → src/WiFiThermostat/html_confWeb.h

@@ -29,6 +29,7 @@ static const char html_confweb_script[] PROGMEM = R"=====(
     reqTime = 0;
     reqFin = false;
     updCbxVal(g('httpAuth'));
+    updCbxVal(g('enableConsole'));
     xhttp = new XMLHttpRequest();
     xhttp.timeout = 1000;
     xhttp.overrideMimeType('application/json');
@@ -45,6 +46,7 @@ static const char html_confweb_script[] PROGMEM = R"=====(
         g('httpP1').value = data.httpP1;
         g('httpU2').value = data.httpU2;
         g('httpP2').value = data.httpP2;
+        setCbx(g('enableConsole'), data.enableConsole);
         xhttp = null;
         reqFin = true;
        }
@@ -61,7 +63,8 @@ static const char html_confweb_script[] PROGMEM = R"=====(
   //transmit();
   function saveConf() {
     updCbxVal(g('httpAuth'));
-    if(g('httpPA').value != g('httpPAC').value) {
+    updCbxVal(g('enableConsole'));
+    if(g('httpPASet').checked && g('httpPA').value != g('httpPAC').value) {
       alert("Admin password verification failed!");
     }
     else g('frmConf').submit();
@@ -110,6 +113,11 @@ make usaccessible without Auth.</p>
 <p><b>User 2 *</b><br><input type='text' name='httpU2' id='httpU2'></p>
 <p><b>User 2 Password *</b>&nbsp;<input type='checkbox' onclick='sp("httpP2")'>&nbsp;show<br><input type='password' name='httpP2' id='httpP2'></p>
 </fieldset>
+<br>
+<fieldset>
+<legend>Console</legend>
+<p><b>Enable Web-Console (Experimental)</b>&nbsp;<input type='checkbox' name='enableConsole' id='enableConsole'></p>
+</fieldset>
 </form>
 <div></div>
 <table style='width:100%'>

+ 63 - 0
src/WiFiThermostat/html_console.h

@@ -0,0 +1,63 @@
+/* clang-format off */
+
+static const char js_wsapp[] PROGMEM = R"=====(var cmd = document.getElementById('cmd');
+var listMsgs = document.getElementById('msgs');
+var socketStatus = document.getElementById('status');
+var btnClose = document.getElementById('btnClose');
+
+var wsUrl = 'ws://' + window.location.hostname + ':81';
+var socket = new WebSocket(wsUrl);
+socket.onopen = function(event) {
+  //socketStatus.innerHTML = 'Connected to: ' + event.currentTarget.URL;
+  socketStatus.innerHTML = 'Connected';
+  socketStatus.className = 'open';
+};
+
+socket.onerror = function(error) {
+  console.log('WebSocket error: ' + error);
+};
+
+socket.onmessage = function(event) {
+  var msg = event.data;
+  listMsgs.innerHTML += msg;
+  listMsgs.scrollTop = listMsgs.scrollHeight;
+};
+
+socket.onclose = function(event) {
+  socketStatus.innerHTML = 'Disconnected';
+  socketStatus.className = 'closed';
+};
+
+function sendCmd() {
+    socket.send(cmd.value);
+    listMsgs.innerHTML += 'Sent: ' + cmd.value + '\n';
+    cmd.value = '';
+    return false;
+}
+
+function togConn() {
+	if(socketStatus.className=='open') { socket.close(); btnClose.innerHTML='Reload'; }
+	else location.reload();
+}
+
+document.getElementById("cmd")
+    .addEventListener("keyup", function(event) {
+    event.preventDefault();
+    if (event.keyCode === 13) {
+        document.getElementById("btnSend").click();
+    }
+});
+)====="; // js_wsapp
+
+static const char html_console_body[] PROGMEM = R"=====(
+<p><b>Console</b></p>
+<div id="status">Connecting...</div>
+<table style='width:100%'>
+<tr><td style='width:800px'><textarea id='msgs'></textarea></td></tr>
+<tr><td style='width:100%'><input id='cmd' type='text'></td></tr>
+<tr><td style='width:100%'><button type='submit' id='btnSend' onclick='sendCmd();'>Send</button></td></tr>
+<tr><td style='width:100%'><button type='button' id='btnClose' onclick='togConn();'>Disconnect</button></td></tr>
+<tr><td style='width:100%'><form action='.' method='get'><button class='bgrey'>Main Menu</button></form></td></tr>
+</table>
+<script src="wsapp.js"></script>
+)====="; // html_console_body

+ 43 - 25
src/WiFiThermostat/html_main.ino → src/WiFiThermostat/html_main.h

@@ -32,15 +32,17 @@ static const char html_main_script[] PROGMEM = R"=====(
           if(data.psetName1 !== undefined) g('btnPs1').innerHTML = data.psetName1;
           if(data.psetName2 !== undefined) g('btnPs2').innerHTML = data.psetName2;
           
-      //if(data.temp !== undefined) g('temp').innerHTML = data.temp.toFixed(1);
-          //if(data.hum !== undefined) g('hum').innerHTML = data.hum;
-      
-      if(data.temp !== undefined && data.hum !== undefined) g('currTempHum').innerHTML = data.temp.toFixed(1) + '&deg;&nbsp;&nbsp;&nbsp;' + data.hum.toFixed(0) + '%';
-      else if(data.temp !== undefined) g('currTempHum').innerHTML = data.temp.toFixed(1) + '&deg;';
-      
+          if(data.temp !== undefined && data.hum !== undefined) g('currTempHum').innerHTML = data.temp.toFixed(1) + '&deg;&nbsp;&nbsp;&nbsp;' + data.hum.toFixed(0) + '%';
+          else if(data.temp !== undefined) g('currTempHum').innerHTML = data.temp.toFixed(1) + '&deg;';
+          
+          if(data.outTemp !== undefined && data.outHum !== undefined) g('outTempHum').innerHTML = data.outTemp.toFixed(1) + '&deg;&nbsp;&nbsp;&nbsp;' + data.outHum.toFixed(0) + '%';
+          else if(data.outTemp !== undefined) g('outTempHum').innerHTML = data.outTemp.toFixed(1) + '&deg;';
+
           if(data.modeName !== undefined) g('mode').innerHTML = data.modeName.toUpperCase();
+          
           if(data.mode == '1') { g('btnModeOn').setAttribute('class', ''); g('btnModeOff').setAttribute('class', 'bgrey');}
           else { g('btnModeOn').setAttribute('class', 'bgrey'); g('btnModeOff').setAttribute('class', '');}
+          
           if(data.pset == '0') { g('btnPs0').setAttribute('class', ''); g('btnPs1').setAttribute('class', 'bgrey'); g('btnPs2').setAttribute('class', 'bgrey');}
           else if(data.pset == '1') {g('btnPs0').setAttribute('class', 'bgrey'); g('btnPs1').setAttribute('class', ''); g('btnPs2').setAttribute('class', 'bgrey');}
           else if(data.pset == '2') {g('btnPs0').setAttribute('class', 'bgrey'); g('btnPs1').setAttribute('class', 'bgrey'); g('btnPs2').setAttribute('class', ''); }
@@ -49,8 +51,11 @@ static const char html_main_script[] PROGMEM = R"=====(
           else if(data.heating == '0' && data.mode == '1') { g('heating').innerHTML = 'IDLE'; g('heating').style = 'color:#1fa3ec;'; }
           else if(data.mode == '0') { g('heating').innerHTML = 'OFF'; g('heating').style = 'color:black;'; }
           
+          var APname;
+          if(data.WiFiNum == 1) APname='Default';
+          else if(data.WiFiNum == 1) APname='Fallback';
           
-          if(data.ssid !== undefined && data.WiFiNum !== undefined && data.WiFiNum > 0) g('ssid').innerHTML = data.ssid + ' (WiFi-' + data.WiFiNum + ')';
+          if(data.ssid !== undefined && data.WiFiNum !== undefined && data.WiFiNum > 0) g('ssid').innerHTML = data.ssid + ' (' + APname + '-AP)';
           else if(data.ssid !== undefined) g('ssid').innerHTML = data.ssid;
 
           if(data.mqttstate !== undefined) { 
@@ -58,7 +63,15 @@ static const char html_main_script[] PROGMEM = R"=====(
             else g('mqttstate').innerHTML = data.mqttstate;
           }
           if(data.uptime !== undefined) g('uptime').innerHTML = data.uptime;
+          
+          if(data.date !== undefined) g('date').innerHTML = data.date;
+          else g('date').innerHTML = 'NTP disabled';
+          
+          if(data.time !== undefined) g('time').innerHTML = data.time;
+          else g('time').innerHTML = 'NTP disabled';
+          
           if(data.mqttreconn !== undefined) g('mqttreconn').innerHTML = data.mqttreconn;
+          
           xhttp = null;
           updateTime = 0;
           reqFin = true;
@@ -88,13 +101,12 @@ static const char html_main_body[] PROGMEM = R"=====(
 <td style='width:50%'><form id='BtnFrmMinus'><input type='hidden' name='BtnMinus' value='1'><button onclick='return sendBtn("Minus")'>-</button></form></td>
 <td style='width:50%'><form id='BtnFrmPlus'><input type='hidden' name='BtnPlus' value='1'><button onclick='return sendBtn("Plus")'>+</button></form></td>
 </tr></table>
-<table style='width:100%'><tr>
-<td style='width:50%'>Current Temp/Hum </td><td><b><span id='currTempHum'></span></td>
-</tr></tr>
-<td>Actual set Temp </td><td><b><span id='currSetTemp'></span></b></td>
-</tr></tr>
-<td>Heating State </td><td><b><span id='heating'></span></b></td>
-</tr></table>
+<table style='width:100%'>
+<tr><td style='width:50%'>Current Temp/Hum </td><td><b><span id='currTempHum'></span></td></tr>
+<tr><td>Outside Temp/Hum </td><td><b><span id='outTempHum'></span></td></tr>
+<tr><td>Actual set Temp </td><td><b><span id='currSetTemp'></span></b></td></tr>
+<tr><td>Heating State </td><td><b><span id='heating'></span></b></td></tr>
+</table>
 <div></div>
 <div></div>
 <p><b>Preset</b></p>
@@ -118,18 +130,24 @@ static const char html_main_body[] PROGMEM = R"=====(
 <hr>
 <div></div>
 <p><b>System</b></p>
-<p>WiFi connected to <i><span id='ssid'></span></i></p>
-<p>MQTT <span id='mqttstate'></span></p>
-<p>MQTT reconnects: <span id='mqttreconn'></span></p>
-<p>Uptime: <span id='uptime'></span></p>
-<table style='width:100%'><tr>
-<td style='width:50%'>)====="; // html_main_body2
+<table style='width:100%'>
+<tr><td colspan=3>WiFi connected to <i><span id='ssid'></span></i></tr></td>
+<tr><td colspan=3>MQTT <span id='mqttstate'></span></tr></td>
+<tr><td colspan=3>MQTT reconnects: <span id='mqttreconn'></span></tr></td>
+<tr><td>Uptime</td><td><span id='uptime'></span></td></tr>
+<tr><td>Date</td><td><span id='date'></span></td></tr>
+<tr><td>Time</td><td><span id='time'></span></td></tr>
+</table>
+<br>
+<table style='width:100%'>)====="; // html_main_body2
 
 static const char html_main_body_adminonly[] PROGMEM = R"=====(
-<form action='conf' method='get'><button class='bgrey'>Config</button></form>
-)====="; // html_main_body_adminonly
+<tr><td style='width:100%'><form action='conf' method='get'><button>Configuration</button></form></td></tr>)====="; // html_main_body_adminonly
 
-static const char html_main_body2[] PROGMEM = R"=====(</td>
-<td style='width:50%'><form action='/' method='get' onsubmit='return confirm("Confirm Restart");'><button name='restart' class='bred'>Restart</button></form></td>
-</tr></table>
+static const char html_main_body_adminonly_console[] PROGMEM = R"=====(
+<tr><td style='width:100%'><form action='console' method='get'><button>Console</button></form></td></tr>)====="; // html_main_body_adminonly_console
+
+static const char html_main_body2[] PROGMEM = R"=====(
+<tr><td style='width:100%'><form action='/' method='get' onsubmit='return confirm("Confirm Restart");'><button name='restart' class='bred'>Restart</button></form></td></tr>
+</table>
 )====="; // html_main_body2

+ 0 - 0
src/WiFiThermostat/html_redTemps.ino → src/WiFiThermostat/html_redTemps.h


+ 527 - 82
src/WiFiThermostat/httpServer.ino

@@ -1,3 +1,18 @@
+
+#include "html.h"
+#include "html_conf.h"
+#include "html_confAdd.h"
+#include "html_confAdv.h"
+#include "html_confBas.h"
+#include "html_confDevWiFi.h"
+#include "html_confMqtt.h"
+#include "html_confWeb.h"
+#include "html_confTime.h"
+#include "html_confLog.h"
+#include "html_main.h"
+#include "html_redTemps.h"
+#include "html_console.h"
+
 void httpServerHandleMainPage() {
   httpServerSendHtmlHeadChunked();
   httpServer.sendContent_P(html_main_script);
@@ -5,7 +20,10 @@ void httpServerHandleMainPage() {
   httpServer.sendContent_P(html_bodytag_jsinit);
   httpServerSendHtmlBodyPageheadChunked();
   httpServer.sendContent_P(html_main_body);
-  if (httpIsAuthenticatedAdmin()) httpServer.sendContent_P(html_main_body_adminonly);
+  if (httpIsAuthenticatedAdmin()) {
+    httpServer.sendContent_P(html_main_body_adminonly);
+    if(confWeb.enableConsole) httpServer.sendContent_P(html_main_body_adminonly_console);
+  }
   httpServer.sendContent_P(html_main_body2);
   httpServerSendHtmlFooterChunked();
 }
@@ -26,6 +44,8 @@ void httpServerHandleConfPage() {
   httpServer.sendContent_P(html_bodytag);
   httpServerSendHtmlBodyPageheadChunked();
   httpServer.sendContent_P(html_conf_body);
+  if(confWeb.enableConsole) httpServer.sendContent_P(html_conf_body_console);
+  httpServer.sendContent_P(html_conf_body_end);
   httpServerSendHtmlFooterChunked();
 }
 
@@ -89,6 +109,35 @@ void httpServerHandleConfAddPage() {
   httpServerSendHtmlFooterChunked();
 }
 
+void httpServerHandleConfTimePage() {
+  httpServerSendHtmlHeadChunked();
+  httpServer.sendContent_P(html_conftime_script);
+  httpServer.sendContent_P(html_head_end);
+  httpServer.sendContent_P(html_bodytag_jsinit);
+  httpServerSendHtmlBodyPageheadChunked();
+  httpServer.sendContent_P(html_conftime_body);
+  httpServerSendHtmlFooterChunked();
+}
+
+void httpServerHandleConfLogPage() {
+  httpServerSendHtmlHeadChunked();
+  httpServer.sendContent_P(html_conflog_script);
+  httpServer.sendContent_P(html_head_end);
+  httpServer.sendContent_P(html_bodytag_jsinit);
+  httpServerSendHtmlBodyPageheadChunked();
+  httpServer.sendContent_P(html_conflog_body);
+  httpServerSendHtmlFooterChunked();
+}
+
+void httpServerHandleConsolePage() {
+  httpServerSendHtmlHeadChunked();
+  httpServer.sendContent_P(html_head_end);
+  httpServer.sendContent_P(html_bodytag);
+  httpServerSendHtmlBodyPageheadChunked();
+  httpServer.sendContent_P(html_console_body);
+  httpServerSendHtmlFooterChunked();
+}
+
 void httpServerHandleConfSavedPage() {
   httpServerSendHtmlHeadChunked();
   httpServer.sendContent_P(html_head_end);
@@ -242,56 +291,66 @@ void httpServerInit() {
     httpServer.send_P(200, "text/css", html_stylesheet);
   });
 
+  httpServer.on("/wsapp.js", []() {
+    httpServer.sendHeader("cache-control", "max-age=86400", false);
+    httpServer.send_P(200, "text/javascript", js_wsapp);
+  });
+
   httpServer.on("/api", []() {
     if (!httpIsAuthenticated()) return httpServer.requestAuthentication();
     else {
-      DEBUG_PRINT("httpServer.on /api");
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /api (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+      //sendLog("WEB: /api", LOGLEVEL_INFO);
       if (httpServer.hasArg("BtnPlus")) {
         setTempStepUp();
-        DEBUG_PRINT(P("web BtnPlus"));
+        sendLog(F("WEB: Btn +"), LOGLEVEL_INFO);
       } //if
       if (httpServer.hasArg("BtnMinus")) {
         setTempStepDown();
-        DEBUG_PRINT(P("web BtnMinus"));
+        sendLog(F("WEB: Btn -"), LOGLEVEL_INFO);
       } //if
       if (httpServer.hasArg("BtnPset0")) {
         setPresetTo(0);
-        DEBUG_PRINT(P("web BtnPset0"));
+        sendLog(F("WEB: Btn PSet0"), LOGLEVEL_INFO);
       } //if
       if (httpServer.hasArg("BtnPset1")) {
         setPresetTo(1);
-        DEBUG_PRINT(P("web BtnPset1"));
+        sendLog(F("WEB: Btn PSet1"), LOGLEVEL_INFO);
       } //if
       if (httpServer.hasArg("BtnPset2")) {
         setPresetTo(2);
-        DEBUG_PRINT(P("web BtnPset2"));
+        sendLog(F("WEB: Btn PSet2"), LOGLEVEL_INFO);
       } //if
       if (httpServer.hasArg("BtnOn")) {
         setHeatingmodeTo(1);
-        DEBUG_PRINT(P("web BtnOn"));
+        sendLog(F("WEB: Btn ON"), LOGLEVEL_INFO);
       } //if
       if (httpServer.hasArg("BtnOff")) {
         setHeatingmodeTo(0);
-        DEBUG_PRINT(P("web BtnOff"));
+        sendLog(F("WEB: Btn OFF"), LOGLEVEL_INFO);
       } //if
       if (httpServer.hasArg("BtnL1Plus")) {
         setTempLowStepUp();
-        DEBUG_PRINT(P("web BtnL1Plus"));
+        sendLog(F("WEB: Btn L1+"), LOGLEVEL_INFO);
       } //if
       if (httpServer.hasArg("BtnL1Minus")) {
         setTempLowStepDown();
-        DEBUG_PRINT(P("web BtnL1Minus"));
+        sendLog(F("WEB: Btn L1-"), LOGLEVEL_INFO);
       } //if
       if (httpServer.hasArg("BtnL2Plus")) {
         setTempLow2StepUp();
-        DEBUG_PRINT(P("web BtnL2Plus"));
+        sendLog(F("WEB: Btn L2+"), LOGLEVEL_INFO);
       } //if
       if (httpServer.hasArg("BtnL2Minus")) {
         setTempLow2StepDown();
-        DEBUG_PRINT(P("web BtnL2Minus"));
+        sendLog(F("WEB: Btn L2-"), LOGLEVEL_INFO);
       } //if
 
       if (httpServer.hasArg("setTemp")) {
+        sendLog(F("WEB: api?setTemp"), LOGLEVEL_INFO);
         char bufVal[20];
         httpServer.arg("setTemp").toCharArray(bufVal, 20);
         float valueFloat = round(atof(bufVal) * 2.0) / 2.0;
@@ -299,6 +358,7 @@ void httpServerInit() {
       }
 
       if (httpServer.hasArg("setMode")) {
+        sendLog(F("WEB: api?setMode"), LOGLEVEL_INFO);
         char bufVal[20];
         httpServer.arg("setMode").toCharArray(bufVal, 20);
         int valueInt = atoi(bufVal);
@@ -306,6 +366,7 @@ void httpServerInit() {
       }
 
       if (httpServer.hasArg("setPreset")) {
+        sendLog(F("WEB: api?setPreset"), LOGLEVEL_INFO);
         char bufVal[20];
         httpServer.arg("setPreset").toCharArray(bufVal, 20);
         int valueInt = atoi(bufVal);
@@ -318,24 +379,46 @@ void httpServerInit() {
       //dtostrf(setTemp, 1, 1, ch_currSetTemp );
 
       //build json object of program data
-      StaticJsonDocument<500> json;
+      StaticJsonDocument<700> json;
 
       json["devname"] = confDevWiFi.deviceName;
       json["ssid"] = WiFi.SSID();
       json["WiFiNum"] = persWM.getActiveWiFiNum();
       json["uptime"] = uptimeStr;
+      //json["uptime"] = NTP.getUptimeString();
+
+      if(confTime.ntpEnable) {
+        char buf[13];
+        updateTime();
+        strftime(buf, sizeof(buf), "%H:%M", &lt);
+        json["time"] = buf;
+        strftime(buf, sizeof(buf), "%d.%m.%Y", &lt);
+        json["date"] = buf;
+      }
       json["freeheap"] = ESP.getFreeHeap();
 
-      if (mqttclient.state() == -4) json["mqttstate"] = "CONNECTION_TIMEOUT";
-      else if (mqttclient.state() == -3) json["mqttstate"] = "CONNECTION_LOST";
-      else if (mqttclient.state() == -2) json["mqttstate"] = "CONNECT_FAILED";
-      else if (mqttclient.state() == -1) json["mqttstate"] = "DISCONNECTED";
-      else if (mqttclient.state() == 0) json["mqttstate"] = "CONNECTED";
-      else if (mqttclient.state() == 1) json["mqttstate"] = "CONNECT_BAD_PROTOCOL";
-      else if (mqttclient.state() == 2) json["mqttstate"] = "CONNECT_BAD_CLIENT_ID";
-      else if (mqttclient.state() == 3) json["mqttstate"] = "CONNECT_UNAVAILABLE";
-      else if (mqttclient.state() == 4) json["mqttstate"] = "CONNECT_BAD_CREDENTIALS";
-      else if (mqttclient.state() == 5) json["mqttstate"] = "CONNECT_UNAUTHORIZED";
+      mqtt_updateCurrentStateName();
+      json["mqttstate"] = mqttCurrentStateName;
+      //if (mqttclient.state() == -4) json["mqttstate"] = "CONNECTION_TIMEOUT";
+      //else if (mqttclient.state() == -3) json["mqttstate"] = "CONNECTION_LOST";
+      //else if (mqttclient.state() == -2) json["mqttstate"] = "CONNECT_FAILED";
+      //else if (mqttclient.state() == -1) json["mqttstate"] = "DISCONNECTED";
+      //else if (mqttclient.state() == 0) json["mqttstate"] = "CONNECTED";
+      //else if (mqttclient.state() == 1) json["mqttstate"] = "CONNECT_BAD_PROTOCOL";
+      //else if (mqttclient.state() == 2) json["mqttstate"] = "CONNECT_BAD_CLIENT_ID";
+      //else if (mqttclient.state() == 3) json["mqttstate"] = "CONNECT_UNAVAILABLE";
+      //else if (mqttclient.state() == 4) json["mqttstate"] = "CONNECT_BAD_CREDENTIALS";
+      //else if (mqttclient.state() == 5) json["mqttstate"] = "CONNECT_UNAUTHORIZED";
+      //if (mqttclient.state() == -4) json["mqttstate"] = PGMStr_MQTTStateM4;
+      //else if (mqttclient.state() == -3) json["mqttstate"] = PGMStr_MQTTStateM3;
+      //else if (mqttclient.state() == -2) json["mqttstate"] = PGMStr_MQTTStateM2;
+      //else if (mqttclient.state() == -1) json["mqttstate"] = PGMStr_MQTTStateM1;
+      //else if (mqttclient.state() == 0) json["mqttstate"] = PGMStr_MQTTState0;
+      //else if (mqttclient.state() == 1) json["mqttstate"] = PGMStr_MQTTState1;
+      //else if (mqttclient.state() == 2) json["mqttstate"] = PGMStr_MQTTState2;
+      //else if (mqttclient.state() == 3) json["mqttstate"] = PGMStr_MQTTState3;
+      //else if (mqttclient.state() == 4) json["mqttstate"] = PGMStr_MQTTState4;
+      //else if (mqttclient.state() == 5) json["mqttstate"] = PGMStr_MQTTState5;
 
       json["mqtthost"] = confMqtt.mqtt_server;
 
@@ -344,7 +427,7 @@ void httpServerInit() {
 
       json["setTemp"] = setTemp;
       json["currSetTemp"] = currSetTemp;
-      json["temp"] = currTemp;
+      json["temp"] = round1(currTemp);
       json["hum"] = int(currHum);
       json["heating"] = turnHeatingOn;
       json["mode"] = heatingMode;
@@ -356,6 +439,8 @@ void httpServerInit() {
       json["psetName2"] = confAdv.psetName2;
       json["tempLow"] = setTempLow;
       json["tempLow2"] = setTempLow2;
+      json["outTemp"] = round1(outTemp);
+      json["outHum"] = outHum;
 
       char jsonchar[500];
       serializeJson(json, jsonchar);
@@ -363,52 +448,61 @@ void httpServerInit() {
     }
   }); //httpServer.on /api
 
-  httpServer.on("/setTemp", []() {
-    if ( httpIsAuthenticated() || (!httpIsAuthenticated() && httpCheckToken()) ) {
-      //Serial.println("web triggered setTemp");
-      if (httpServer.hasArg("value")) {
-        char bufVal[20];
-        httpServer.arg("value").toCharArray(bufVal, 20);
-        float valueFloat = round(atof(bufVal) * 2.0) / 2.0;
-        setTempTo(valueFloat);
-        httpSend200OK();
-      }
-    } //if
-    else httpSendUnauthorized();
-  });
-
-  httpServer.on("/setMode", []() {
-    if ( httpIsAuthenticated() || (!httpIsAuthenticated() && httpCheckToken()) ) {
-      //Serial.println("web triggered setMode");
-      if (httpServer.hasArg("value")) {
-        char bufVal[20];
-        httpServer.arg("value").toCharArray(bufVal, 20);
-        int valueInt = atoi(bufVal);
-        if (valueInt >= 0 && valueInt <= 1) setHeatingmodeTo(valueInt);
-        httpSend200OK();
-      }
-    } //if
-    else httpSendUnauthorized();
-  });
-
-  httpServer.on("/setPreset", []() {
-    if ( httpIsAuthenticated() || (!httpIsAuthenticated() && httpCheckToken()) ) {
-      //Serial.println("web triggered setPreset");
-      if (httpServer.hasArg("value")) {
-        char bufVal[20];
-        httpServer.arg("value").toCharArray(bufVal, 20);
-        int valueInt = atoi(bufVal);
-        if (valueInt >= 0 && valueInt <= 2) setPresetTo(valueInt);
-        httpSend200OK();
-      }
-    } //if
-    else httpSendUnauthorized();
-  });
+  //httpServer.on("/setTemp", []() {
+  //  if ( httpIsAuthenticated() || (!httpIsAuthenticated() && httpCheckToken()) ) {
+  //    //Serial.println("web triggered setTemp");
+  //    sendLog(F("WEB: /setTemp"), LOGLEVEL_INFO);
+  //    if (httpServer.hasArg("value")) {
+  //      char bufVal[20];
+  //      httpServer.arg("value").toCharArray(bufVal, 20);
+  //      float valueFloat = round(atof(bufVal) * 2.0) / 2.0;
+  //      setTempTo(valueFloat);
+  //      httpSend200OK();
+  //    }
+  //  } //if
+  //  else httpSendUnauthorized();
+  //});
+
+  //httpServer.on("/setMode", []() {
+  //  if ( httpIsAuthenticated() || (!httpIsAuthenticated() && httpCheckToken()) ) {
+  //    //Serial.println("web triggered setMode");
+  //    sendLog(F("WEB: /setMode"), LOGLEVEL_INFO);
+  //    if (httpServer.hasArg("value")) {
+  //      char bufVal[20];
+  //      httpServer.arg("value").toCharArray(bufVal, 20);
+  //      int valueInt = atoi(bufVal);
+  //      if (valueInt >= 0 && valueInt <= 1) setHeatingmodeTo(valueInt);
+  //      httpSend200OK();
+  //    }
+  //  } //if
+  //  else httpSendUnauthorized();
+  //});
+
+  //httpServer.on("/setPreset", []() {
+  //  if ( httpIsAuthenticated() || (!httpIsAuthenticated() && httpCheckToken()) ) {
+  //    //Serial.println("web triggered setPreset");
+  //    sendLog(F("WEB: /setPreset"), LOGLEVEL_INFO);
+  //    if (httpServer.hasArg("value")) {
+  //      char bufVal[20];
+  //      httpServer.arg("value").toCharArray(bufVal, 20);
+  //      int valueInt = atoi(bufVal);
+  //      if (valueInt >= 0 && valueInt <= 2) setPresetTo(valueInt);
+  //      httpSend200OK();
+  //    }
+  //  } //if
+  //  else httpSendUnauthorized();
+  //});
 
   httpServer.on("/confDataWeb", []() {
     if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
     else {
-      Serial.println("httpServer.on /confdata");
+      //Serial.println("httpServer.on /confdata");
+      //sendLog("WEB: /confDataWeb", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confDataWeb (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       StaticJsonDocument<1000> json;
 
       json["apiToken"] = confWeb.http_token;
@@ -428,6 +522,10 @@ void httpServerInit() {
       //json["httpP2"] =  confWeb.http_pass2;
       if(strlen(confWeb.http_pass2) > 0) json["httpP2"] = "****";
       else json["httpP2"] = "";
+
+      if (confWeb.enableConsole) json["enableConsole"] = 1;
+      else json["enableConsole"] = 0;
+
       yield();
 
       char jsonchar[1000];
@@ -441,7 +539,12 @@ void httpServerInit() {
   httpServer.on("/confDataMqtt", []() {
     if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
     else {
-      Serial.println("httpServer.on /confdata");
+      //sendLog("WEB: /confDataMqtt", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confDataMqtt (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       StaticJsonDocument<1000> json;
 
       if (confMqtt.mqtt_enable) json["mqttEnable"] = 1;
@@ -492,7 +595,12 @@ void httpServerInit() {
   httpServer.on("/setConfWeb", []() {
     if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
     else {
-      Serial.println("httpServer.on /setConfWeb");
+      //sendLog("WEB: /setConfWeb", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /setConfWeb (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       bool httpPASet, httpP1Set, httpP2Set;
       httpPASet = false;
       httpP1Set = false;
@@ -541,7 +649,13 @@ void httpServerInit() {
   httpServer.on("/setConfMqtt", []() {
     if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
     else {
-      Serial.println("httpServer.on /setConfMqtt");
+      //Serial.println("httpServer.on /setConfMqtt");
+      //sendLog("WEB: /setConfMqtt", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /setConfMqtt (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       bool mqttPassSet;
       mqttPassSet = false;
       if (httpServer.hasArg("mqttPassSet")) mqttPassSet = true;
@@ -578,6 +692,12 @@ void httpServerInit() {
   httpServer.on("/confDataDevWiFi", []() {
     if (!httpIsAuthenticated()) return httpServer.requestAuthentication();
     else {
+      //sendLog("WEB: /confDataDevWiFi", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confDataDevWiFi (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       for (int i = 0; i < httpServer.args(); i++) {
         char bufName[20];
         char bufValue[101];
@@ -627,7 +747,11 @@ void httpServerInit() {
   httpServer.on("/setConfDevWiFi", []() {
     if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
     else {
-      Serial.println("httpServer.on /setConfDevWiFi");
+      //sendLog("WEB: /setConfDevWiFi", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /setConfDevWiFi (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
       
       bool WPW1Set, WPW2Set, WPWAPSet;
       WPW1Set = false;
@@ -674,7 +798,11 @@ void httpServerInit() {
   httpServer.on("/setConfBas", []() {
     if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
     else {
-      Serial.println("httpServer.on /setConfBas");
+      //sendLog("WEB: /setConfBas", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /setConfBas (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
 
       for (int i = 0; i < httpServer.args(); i++) {
         char bufName[20];
@@ -704,7 +832,11 @@ void httpServerInit() {
   httpServer.on("/setConfAdv", []() {
     if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
     else {
-      Serial.println("httpServer.on /setConfAdv");
+      //sendLog("WEB: /setConfAdv", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /setConfAdv (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
 
       for (int i = 0; i < httpServer.args(); i++) {
         char bufName[20];
@@ -734,7 +866,11 @@ void httpServerInit() {
   httpServer.on("/setConfAdd", []() {
     if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
     else {
-      Serial.println("httpServer.on /setConfAdd");
+      //sendLog("WEB: /setConfAdd", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /setConfAdd (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
 
       for (int i = 0; i < httpServer.args(); i++) {
         char bufName[20];
@@ -761,10 +897,83 @@ void httpServerInit() {
     }
   }); //httpServer.on /setConfAdd
 
+  httpServer.on("/setConfTime", []() {
+    if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
+    else {
+      //sendLog("WEB: /setConfTime", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /setConfTime (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
+      for (int i = 0; i < httpServer.args(); i++) {
+        char bufName[20];
+        char bufValue[101];
+        httpServer.argName(i).toCharArray(bufName, 20);
+        httpServer.arg(i).toCharArray(bufValue, 101);
+
+        if (strlen(bufName) > 0) {
+          Serial.print("web update ");
+          Serial.print(bufName);
+          Serial.print(" = ");
+          Serial.println(bufValue);
+          setConfig(bufName, bufValue);
+        }
+      }
+      yield();
+      saveConfigTime();
+
+      httpServerHandleConfSavedPage();
+      config_was_changed = true;
+      // delay(10);
+      // yield();
+      // ESP.restart();
+    }
+  }); //httpServer.on /setConfTime
+
+  httpServer.on("/setConfLog", []() {
+    if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
+    else {
+      //sendLog("WEB: /setConfLog", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /setConfLog (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
+      for (int i = 0; i < httpServer.args(); i++) {
+        char bufName[20];
+        char bufValue[101];
+        httpServer.argName(i).toCharArray(bufName, 20);
+        httpServer.arg(i).toCharArray(bufValue, 101);
+
+        if (strlen(bufName) > 0) {
+          Serial.print("web update ");
+          Serial.print(bufName);
+          Serial.print(" = ");
+          Serial.println(bufValue);
+          setConfig(bufName, bufValue);
+        }
+      }
+      yield();
+      saveConfigLog();
+
+      httpServerHandleConfSavedPage();
+      config_was_changed = true;
+      // delay(10);
+      // yield();
+      // ESP.restart();
+    }
+  }); //httpServer.on /setConfLog
+
   httpServer.on("/confDataBas", []() {
     if (!httpIsAuthenticated()) return httpServer.requestAuthentication();
     else {
+      //sendLog("WEB: /confDataBas", LOGLEVEL_INFO);
       //Serial.println("httpServer.on /confdata2");
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confDataBas (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
 
       for (int i = 0; i < httpServer.args(); i++) {
         char bufName[20];
@@ -823,7 +1032,12 @@ void httpServerInit() {
   httpServer.on("/confDataAdv", []() {
     if (!httpIsAuthenticated()) return httpServer.requestAuthentication();
     else {
+      //sendLog("WEB: /confDataAdv", LOGLEVEL_INFO);
       //Serial.println("httpServer.on /confdata2");
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confDataAdv (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
 
       for (int i = 0; i < httpServer.args(); i++) {
         char bufName[20];
@@ -870,7 +1084,12 @@ void httpServerInit() {
   httpServer.on("/confDataAdd", []() {
     if (!httpIsAuthenticated()) return httpServer.requestAuthentication();
     else {
+      //sendLog("WEB: /confDataAdd", LOGLEVEL_INFO);
       //Serial.println("httpServer.on /confdata2");
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confDataAdd (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
 
       for (int i = 0; i < httpServer.args(); i++) {
         char bufName[20];
@@ -907,18 +1126,138 @@ void httpServerInit() {
     }
   }); //httpServer.on /confdadd
 
+  httpServer.on("/confDataTime", []() {
+    if (!httpIsAuthenticated()) return httpServer.requestAuthentication();
+    else {
+      //sendLog("WEB: /confDataTime", LOGLEVEL_INFO);
+      //Serial.println("httpServer.on /confdata2");
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confDataTime (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
+      for (int i = 0; i < httpServer.args(); i++) {
+        char bufName[20];
+        char bufValue[101];
+        httpServer.argName(i).toCharArray(bufName, 20);
+        httpServer.arg(i).toCharArray(bufValue, 101);
+
+        if (strlen(bufName) > 0) {
+          //Serial.print("web update ");
+          //Serial.print(bufName);
+          //Serial.print(" = ");
+          //Serial.println(bufValue);
+          setConfig(bufName, bufValue);
+        }
+        saveConfig2ToFlash = true;
+        //Serial.println("web triggered saveConfig2ToFlash");
+      }
+      yield();
+
+      //build json object of program data
+      StaticJsonDocument<1000> json;
+
+      if(confTime.ntpEnable) json["NTPEnable"] = 1;
+      else json["NTPEnable"] = 0;
+
+      json["NTPServer1"] = confTime.ntpServer1;
+      json["NTPServer2"] = confTime.ntpServer2;
+      json["TZStr"] = confTime.timeZoneStr;
+      json["NTPSyncInt"] = confTime.ntpSyncInterval;
+
+      yield();
+
+      char jsonchar[1000];
+      serializeJson(json, jsonchar);
+      httpServer.send(200, "application/json", jsonchar);
+    }
+  }); //httpServer.on /confDataTime
+
+  httpServer.on("/confDataLog", []() {
+    if (!httpIsAuthenticated()) return httpServer.requestAuthentication();
+    else {
+      //sendLog("WEB: /confDataLog", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confDataLog (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
+      for (int i = 0; i < httpServer.args(); i++) {
+        char bufName[20];
+        char bufValue[101];
+        httpServer.argName(i).toCharArray(bufName, 20);
+        httpServer.arg(i).toCharArray(bufValue, 101);
+
+        if (strlen(bufName) > 0) {
+          //Serial.print("web update ");
+          //Serial.print(bufName);
+          //Serial.print(" = ");
+          //Serial.println(bufValue);
+          setConfig(bufName, bufValue);
+        }
+        saveConfig2ToFlash = true;
+        //Serial.println("web triggered saveConfig2ToFlash");
+      }
+      yield();
+
+      //build json object of program data
+      StaticJsonDocument<1000> json;
+
+      //if(confTime.ntpEnable) json["NTPEnable"] = 1;
+      //else json["NTPEnable"] = 0;
+
+      json["logLevSer"] = confLog.logLevelSerial;
+      json["logLevWeb"] = confLog.logLevelWeb;
+      json["logLevMqtt"] = confLog.logLevelMqtt;
+
+      yield();
+
+      char jsonchar[1000];
+      serializeJson(json, jsonchar);
+      httpServer.send(200, "application/json", jsonchar);
+    }
+  }); //httpServer.on /confDataLog
+
   //get heap status, analog input value and all GPIO statuses in one json call
-  httpServer.on("/info", HTTP_GET, []() {
+  httpServer.on("/sysinfo", HTTP_GET, []() {
     if (!httpIsAuthenticated()) return httpServer.requestAuthentication();
     else {
-      String json = " {";
-      json += "\"wifissid\":\"" + WiFi.SSID() + "\"";
-      json += "\"heap\":" + String(ESP.getFreeHeap());
-      json += "}";
-      httpServer.send(200, "text/json", json);
-      json = String();
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /sysinfo (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
+      StaticJsonDocument<600> json;
+      json["Devname"] = confDevWiFi.deviceName;
+      json["Ssid"] = WiFi.SSID();
+      json["WiFiNum"] = persWM.getActiveWiFiNum();
+      json["Uptime"] = uptimeStr;
+      json["HeapFree"] = ESP.getFreeHeap();
+      json["HeapFragment"] = ESP.getHeapFragmentation();
+      json["HeapMaxBlock"] = ESP.getMaxFreeBlockSize();
+      json["ResetReason"] = ESP.getResetReason();
+      json["CoreVersion"] = ESP.getCoreVersion();
+      json["SDKVersion"] = ESP.getSdkVersion();
+      json["CPUfreq"] = ESP.getCpuFreqMHz();
+      json["SketchSize"] = ESP.getSketchSize();
+      json["FlashSize"] = ESP.getFlashChipRealSize();
+
+      if(confTime.ntpEnable) {
+        char buf[13];
+        updateTime();
+        strftime(buf, sizeof(buf), "%H:%M", &lt);
+        json["time"] = buf;
+        strftime(buf, sizeof(buf), "%d.%m.%Y", &lt);
+        json["date"] = buf;
+      }
+      
+      yield();
+
+      char jsonchar[1000];
+      serializeJson(json, jsonchar);
+      httpServer.send(200, "application/json", jsonchar);
     }
-  }); //httpServer.on /info
+  }); //httpServer.on /sysinfo
 
   /*httpServer.on("/", []() {
     if ( WifiInApMode ) {
@@ -932,8 +1271,14 @@ void httpServerInit() {
     });*/
 
   httpServer.on("/", []() {
+    String addr = httpServer.client().remoteIP().toString();
+    char buf[40];
+    sprintf(buf, "WEB: / (from %s)", addr.c_str());
+    sendLog(buf, LOGLEVEL_INFO);
+
     if (httpServer.hasArg("restart")) {
       if (httpIsAuthenticated() || (!httpIsAuthenticated() && httpCheckToken()) ) {
+        sendLog("WEB: /?restart", LOGLEVEL_INFO);
         httpServerHandleRestartPage();
         restart();
       }
@@ -942,6 +1287,7 @@ void httpServerInit() {
     else if (httpServer.hasArg("mqttreconnect")) {
       if ( httpIsAuthenticated() || (!httpIsAuthenticated() && httpCheckToken()) ) {
         //Serial.println("web triggered mqttreconnect");
+        sendLog("WEB: /?mqttreconnect", LOGLEVEL_INFO);
         mqttReconnect();
         httpServer.sendHeader("Location", "/", true);
         httpServer.send(303);
@@ -950,6 +1296,7 @@ void httpServerInit() {
     }
     else if (httpServer.hasArg("clearconf")) {
       if (httpIsAuthenticatedAdmin() || (!httpIsAuthenticatedAdmin() && httpCheckToken()) ) {
+        sendLog("WEB: /?clearconf", LOGLEVEL_INFO);
         httpServerHandleClearconfPage();
 
         yield();
@@ -960,6 +1307,7 @@ void httpServerInit() {
     }
     else if (!httpIsAuthenticated()) return httpServer.requestAuthentication();
     else {
+      sendLog("WEB: /", LOGLEVEL_INFO);
       httpServerHandleMainPage();
     }
   });
@@ -967,6 +1315,12 @@ void httpServerInit() {
   httpServer.on("/conf", []() {
     if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
     else {
+      //sendLog("WEB: /conf", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /conf (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       httpServerHandleConfPage();
     }
   });
@@ -974,6 +1328,12 @@ void httpServerInit() {
   httpServer.on("/confweb", []() {
     if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
     else {
+      //sendLog("WEB: /confweb", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confweb (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       httpServerHandleConfWebPage();
     }
   });
@@ -981,6 +1341,12 @@ void httpServerInit() {
   httpServer.on("/confmqtt", []() {
     if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
     else {
+      //sendLog("WEB: /confmqtt", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confmqtt (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       httpServerHandleConfMqttPage();
     }
   });
@@ -988,6 +1354,12 @@ void httpServerInit() {
   httpServer.on("/confdevwifi", []() {
     if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
     else {
+      //sendLog("WEB: /confdevwifi", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confdevwifi (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       httpServerHandleConfDevWiFiPage();
     }
   });
@@ -995,6 +1367,12 @@ void httpServerInit() {
   httpServer.on("/confbas", []() {
     if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
     else {
+      //sendLog("WEB: /confbas", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confbas (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       httpServerHandleConfBasPage();
     }
   });
@@ -1002,6 +1380,12 @@ void httpServerInit() {
   httpServer.on("/confadv", []() {
     if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
     else {
+      //sendLog("WEB: /confadv", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confadv (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       httpServerHandleConfAdvPage();
     }
   });
@@ -1009,18 +1393,79 @@ void httpServerInit() {
   httpServer.on("/confadd", []() {
     if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
     else {
+      //sendLog("WEB: /confadd", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /confadd (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       httpServerHandleConfAddPage();
     }
   });
 
+  httpServer.on("/conftime", []() {
+    if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
+    else {
+      //sendLog("WEB: /conftime", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /conftime (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
+      httpServerHandleConfTimePage();
+    }
+  });
+
+  httpServer.on("/conflog", []() {
+    if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
+    else {
+      //sendLog("WEB: /conflog", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /conflog (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
+      httpServerHandleConfLogPage();
+    }
+  });
+
   httpServer.on("/redTemps", []() {
     if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
     else {
+      //sendLog("WEB: /redTemps", LOGLEVEL_INFO);
+      String addr = httpServer.client().remoteIP().toString();
+      char buf[40];
+      sprintf(buf, "WEB: /redTemps (from %s)", addr.c_str());
+      sendLog(buf, LOGLEVEL_INFO);
+
       httpServerHandleRedTempsPage();
     }
   });
 
+  if(confWeb.enableConsole) {
+    httpServer.on("/console", []() {
+      if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
+      else {
+        //sendLog("WEB: /console", LOGLEVEL_INFO);
+        String addr = httpServer.client().remoteIP().toString();
+        char buf[40];
+        sprintf(buf, "WEB: /console (from %s)", addr.c_str());
+        sendLog(buf, LOGLEVEL_INFO);
+
+        wsValidSessionId = millis();
+        char sidbuf[20];
+        sprintf(sidbuf, "sessionId=%lu|", wsValidSessionId);
+        httpServer.sendHeader("Set-Cookie",sidbuf);
+        httpServerHandleConsolePage();
+      }
+    });
+  }
+
   httpServer.onNotFound([]() {
+    String addr = httpServer.client().remoteIP().toString();
+    char buf[40];
+    sprintf(buf, "WEB: 404 (from %s)", addr.c_str());
+    sendLog(buf, LOGLEVEL_INFO);
     httpServerHandleNotFound();
   }); //httpServer.onNotFound
 

+ 69 - 0
src/WiFiThermostat/logging.ino

@@ -0,0 +1,69 @@
+void sendLog(const char *msg, uint8_t loglevel)
+{
+    char buf[101];
+    char buf2[121];
+    static char tbuf[20];
+    bool timeIsValid=false;
+    strlcpy(buf, msg, sizeof(buf));
+
+    if (confTime.ntpEnable)
+    {
+        updateTime();
+        if(lt.tm_year > 70) { // lt.tm_year = years since 1900, before NTP is synced = 70
+            //strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &lt);
+            strftime(tbuf, sizeof(tbuf), "%H:%M:%S", &lt);
+            sprintf(buf2, "[%s] %s\r\n", tbuf, buf);
+            timeIsValid = true;
+        }
+    }
+
+    if(!timeIsValid)
+    {
+        //unsigned long tmpMillis;
+        unsigned long tmpSecs;
+        //unsigned int restMillis;
+        //tmpMillis = millis();
+        //tmpSecs = tmpMillis / 1000;
+        //restMillis = tmpMillis - (tmpSecs * 1000);
+        tmpSecs = millis() / 1000;
+        
+        //sprintf(buf2, "[%u.%u] %s\r\n", tmpSecs, restMillis, buf);
+        sprintf(buf2, "[%07lu] %s\r\n", tmpSecs, buf);
+    }
+
+    if(loglevel <= confLog.logLevelSerial) Serial.print(buf2);
+
+    if (confMqtt.mqtt_enable && mqttclient.state() == 0)
+    {
+        if(loglevel <= confLog.logLevelMqtt) mqttclient.publish(confMqtt.mqtt_topic_out, buf, confMqtt.mqtt_outRetain);
+    }
+    yield();
+
+    if (confWeb.enableConsole && webSocket.connectedClients() > 0)
+    {
+        if(loglevel <= confLog.logLevelWeb) webSocket.broadcastTXT(buf2);
+    }
+}
+
+void sendLog(const __FlashStringHelper *msg, uint8_t loglevel)
+{
+    char buf[501];
+    PGM_P p = reinterpret_cast<PGM_P>(msg);
+    size_t n = 0;
+    while (1)
+    {
+        unsigned char c = pgm_read_byte(p++);
+        if (c == 0) {
+            buf[n] = c;
+            break;
+        }
+        else if (n >= sizeof(buf)-1) {
+            break;
+        }
+        else {
+            buf[n] = c;
+            n++;
+        }
+    }
+    sendLog(buf, loglevel);
+}

+ 12 - 0
src/WiFiThermostat/miscFunctions.ino

@@ -26,4 +26,16 @@ void restart() {
   ESP.wdtDisable();
 	ESP.reset();
   delay(2000);
+}
+
+void logSysdata() {
+    // Serial.print (NTP.getDateStr_YMD()); Serial.print (" "); Serial.print (NTP.getTimeStr ());
+    // Serial.print (" "); Serial.print (NTP.isSummerTime () ? "Summer Time. " : "Winter Time. "); Serial.println();
+    // Serial.print ("Uptime: ");
+    // Serial.print (NTP.getUptimeString ()); Serial.print (" since ");
+    // Serial.println (NTP.getDateTimeString (NTP.getFirstSync ()).c_str ());
+    // Serial.printf ("Free heap: %u", ESP.getFreeHeap ()); Serial.println();
+    char logBuf[101];
+    sprintf_P(logBuf, "SYS: uptime=%s, freeHeap=%u, heapFragm=%u%%", uptimeStr, ESP.getFreeHeap(), ESP.getHeapFragmentation());
+    sendLog(logBuf, LOGLEVEL_INFO);
 }

+ 197 - 168
src/WiFiThermostat/mqtt.ino

@@ -11,8 +11,8 @@ void mqttCallback(char *topic, unsigned char *payload, uint16_t length)
   //  }
   //  Serial.println();
 
-  char tmp_topic_pub[51];
-  char tmp_payload_pub[51];
+  //char tmp_topic_pub[51];
+  //char tmp_payload_pub[51];
 
   if (strcmp(topic, mqtt_topic_in_cmd) == 0)
   { //if topic = mqtt_topic_in_cmd
@@ -30,25 +30,24 @@ void mqttCallback(char *topic, unsigned char *payload, uint16_t length)
     cmdPayload[len] = '\0';
     //    Serial.print("cmdPayload:");
     //    Serial.println(cmdPayload);
-
-    if (serialdebug)
-    {
-      Serial.print("MQTT: received '");
-      Serial.print(mqtt_topic_in_cmd);
-      Serial.print("' - '");
-      Serial.print(cmdPayload);
-      Serial.println("'");
-    }
-    if (mqttdebug)
-    {
-      sprintf(tmp_topic_pub, "%s/%s", confMqtt.mqtt_topic_out, "mqtt_received");
-      sprintf(tmp_payload_pub, "%s: %s", mqtt_topic_in_cmd, cmdPayload);
-      mqttclient.publish(tmp_topic_pub, tmp_payload_pub);
-    }
+    //if (serialdebug)
+    //{
+    //  Serial.print("MQTT: received '");
+    //  Serial.print(mqtt_topic_in_cmd);
+    //  Serial.print("' - '");
+    //  Serial.print(cmdPayload);
+    //  Serial.println("'");
+    //}
+    //if (mqttdebug)
+    //{
+    //  sprintf(tmp_topic_pub, "%s/%s", confMqtt.mqtt_topic_out, "mqtt_received");
+    //  sprintf(tmp_payload_pub, "%s: %s", mqtt_topic_in_cmd, cmdPayload);
+    //  mqttclient.publish(tmp_topic_pub, tmp_payload_pub);
+    //}
 
     if (strncmp(cmdPayload, "HEARTBEAT", 9) == 0)
     {
-      Serial.println(F("MQTT: received HEARTBEAT - resetting timer..."));
+      sendLog(F("MQTT: received HEARTBEAT - resetting timer..."), LOGLEVEL_INFO);
       mqttLastHeartbeat = millis();
       mqttConnected = true;
     }
@@ -82,20 +81,24 @@ void mqttCallback(char *topic, unsigned char *payload, uint16_t length)
     valueFloat = round(atof(tmpPayload) * 2.0) / 2.0;
     setTempTo(valueFloat);
 
-    if (serialdebug)
-    {
-      Serial.print("MQTT: received '");
-      Serial.print(mqtt_topic_in_setTemp);
-      Serial.print("' - '");
-      Serial.print(cmdPayload);
-      Serial.println("'");
-    }
-    if (mqttdebug)
-    {
-      sprintf(tmp_topic_pub, "%s/%s", confMqtt.mqtt_topic_out, "mqtt_received");
-      sprintf(tmp_payload_pub, "%s: %s", mqtt_topic_in_setTemp, cmdPayload);
-      mqttclient.publish(tmp_topic_pub, tmp_payload_pub);
-    }
+    //if (serialdebug)
+    //{
+    //  Serial.print("MQTT: received '");
+    //  Serial.print(mqtt_topic_in_setTemp);
+    //  Serial.print("' - '");
+    //  Serial.print(cmdPayload);
+    //  Serial.println("'");
+    //}
+    //if (mqttdebug)
+    //{
+    //  sprintf(tmp_topic_pub, "%s/%s", confMqtt.mqtt_topic_out, "mqtt_received");
+    //  sprintf(tmp_payload_pub, "%s: %s", mqtt_topic_in_setTemp, cmdPayload);
+    //  mqttclient.publish(tmp_topic_pub, tmp_payload_pub);
+    //}
+
+    char buf[50];
+    sprintf(buf, "MQTT IN: setTemp to %2.1f", valueFloat);
+    sendLog(buf, LOGLEVEL_INFO);
 
     cmdInQueue = false; // payload is processed in "commands"
   }                     //if topic = mqtt_topic_in_setTemp
@@ -150,28 +153,29 @@ void mqttCallback(char *topic, unsigned char *payload, uint16_t length)
 
     if (tmpHeatMode == 0 || tmpHeatMode == 1)
     {
-      if (serialdebug)
-      {
-        Serial.print(F("set heatmode to: "));
-        Serial.println(tmpHeatMode);
-      }
-
       setHeatingmodeTo(tmpHeatMode);
-
-      if (serialdebug)
-      {
-        Serial.print("MQTT: received '");
-        Serial.print(mqtt_topic_in_setMode);
-        Serial.print("' - '");
-        Serial.print(cmdPayload);
-        Serial.println("'");
-      }
-      if (mqttdebug)
-      {
-        sprintf(tmp_topic_pub, "%s/%s", confMqtt.mqtt_topic_out, "mqtt_received");
-        sprintf(tmp_payload_pub, "%s: %s", mqtt_topic_in_setMode, cmdPayload);
-        mqttclient.publish(tmp_topic_pub, tmp_payload_pub);
-      }
+      //if (serialdebug)
+      //{
+      //  Serial.print(F("set heatmode to: "));
+      //  Serial.println(tmpHeatMode);
+      //}
+      //if (serialdebug)
+      //{
+      //  Serial.print("MQTT: received '");
+      //  Serial.print(mqtt_topic_in_setMode);
+      //  Serial.print("' - '");
+      //  Serial.print(cmdPayload);
+      //  Serial.println("'");
+      //}
+      //if (mqttdebug)
+      //{
+      //  sprintf(tmp_topic_pub, "%s/%s", confMqtt.mqtt_topic_out, "mqtt_received");
+      //  sprintf(tmp_payload_pub, "%s: %s", mqtt_topic_in_setMode, cmdPayload);
+      //  mqttclient.publish(tmp_topic_pub, tmp_payload_pub);
+      //}
+      char buf[50];
+      sprintf(buf, "MQTT IN: setMode to %u", tmpHeatMode);
+      sendLog(buf, LOGLEVEL_INFO);
     }
 
     cmdInQueue = false; // payload is processed in "commands"
@@ -231,28 +235,29 @@ void mqttCallback(char *topic, unsigned char *payload, uint16_t length)
 
     if (tmpPreset <= 2)
     {
-      if (serialdebug)
-      {
-        Serial.print(F("set preset to: "));
-        Serial.println(tmpPreset);
-      }
-
       setPresetTo(tmpPreset);
-
-      if (serialdebug)
-      {
-        Serial.print(F("MQTT: received '"));
-        Serial.print(mqtt_topic_in_setPreset);
-        Serial.print("' - '");
-        Serial.print(cmdPayload);
-        Serial.println("'");
-      }
-      if (mqttdebug)
-      {
-        sprintf(tmp_topic_pub, "%s/%s", confMqtt.mqtt_topic_out, "mqtt_received");
-        sprintf(tmp_payload_pub, "%s: %s", mqtt_topic_in_setPreset, cmdPayload);
-        mqttclient.publish(tmp_topic_pub, tmp_payload_pub);
-      }
+      //if (serialdebug)
+      //{
+      //  Serial.print(F("set preset to: "));
+      //  Serial.println(tmpPreset);
+      //}
+      //if (serialdebug)
+      //{
+      //  Serial.print(F("MQTT: received '"));
+      //  Serial.print(mqtt_topic_in_setPreset);
+      //  Serial.print("' - '");
+      //  Serial.print(cmdPayload);
+      //  Serial.println("'");
+      //}
+      //if (mqttdebug)
+      //{
+      //  sprintf(tmp_topic_pub, "%s/%s", confMqtt.mqtt_topic_out, "mqtt_received");
+      //  sprintf(tmp_payload_pub, "%s: %s", mqtt_topic_in_setPreset, cmdPayload);
+      //  mqttclient.publish(tmp_topic_pub, tmp_payload_pub);
+      //}
+      char buf[50];
+      sprintf(buf, "MQTT IN: setPreset to %s", cmdPayload);
+      sendLog(buf, LOGLEVEL_INFO);
     }
     cmdInQueue = false; // payload is processed in "commands"
   }                     //if topic = mqtt_topic_in_setPreset
@@ -311,74 +316,92 @@ bool mqttReconnect(void)
   //mqttClientId += String(random(0xffff), HEX);   //or random
   mqttClientId += String(confDevWiFi.deviceName);
 
-  if (serialdebug)
-    Serial.print(F("MQTT: connecting to broker '"));
-    Serial.print(confMqtt.mqtt_server);
-    Serial.print(F("' with "));
+  //if (serialdebug)
+  //  Serial.print(F("MQTT: connecting to broker '"));
+  //  Serial.print(confMqtt.mqtt_server);
+  //  Serial.print(F("' with "));
 
   boolean connRes;
   mqttReconnectAttempts++;
+  char connectMode[20];
 
   if (strlen(confMqtt.mqtt_user) > 0 && strlen(confMqtt.mqtt_willTopic) == 0)
   {
     // user and password, no Last Will
-    if (serialdebug)
-      Serial.print(F("Auth, no LWT"));
+    //if (serialdebug)
+    //  Serial.print(F("Auth, no LWT"));
+    strlcpy(connectMode, "Auth, no LWT", sizeof(connectMode));
     connRes = mqttclient.connect(mqttClientId.c_str(), confMqtt.mqtt_user, confMqtt.mqtt_pass);
   }
   else if (strlen(confMqtt.mqtt_user) > 0 && strlen(confMqtt.mqtt_willTopic) > 0)
   {
     // user, password and Last Will
-    if (serialdebug)
-      Serial.print(F("Auth and LWT"));
+    //if (serialdebug)
+    //  Serial.print(F("Auth and LWT"));
+    strlcpy(connectMode, "Auth and LWT", sizeof(connectMode));
     connRes = mqttclient.connect(mqttClientId.c_str(), confMqtt.mqtt_user, confMqtt.mqtt_pass, confMqtt.mqtt_willTopic, confMqtt.mqtt_willQos, confMqtt.mqtt_willRetain, confMqtt.mqtt_willMsg);
   }
   else if (strlen(confMqtt.mqtt_user) == 0 && strlen(confMqtt.mqtt_willTopic) > 0)
   {
     // Last Will but no user and password
-    if (serialdebug)
-      Serial.print(F("LWT, no Auth"));
+    //if (serialdebug)
+    //  Serial.print(F("LWT, no Auth"));
+    strlcpy(connectMode, "LWT, no Auth", sizeof(connectMode));
     connRes = mqttclient.connect(mqttClientId.c_str(), confMqtt.mqtt_willTopic, confMqtt.mqtt_willQos, confMqtt.mqtt_willRetain, confMqtt.mqtt_willMsg);
   }
   else
   {
     // no user, password and no Last Will
-    if (serialdebug)
-      Serial.print(F("no Auth, no LWT"));
+    //if (serialdebug)
+    //  Serial.print(F("no Auth, no LWT"));
+    strlcpy(connectMode, "no Auth, no LWT", sizeof(connectMode));
     connRes = mqttclient.connect(mqttClientId.c_str());
   }
 
-  if (serialdebug)
-  {
-    Serial.print(F(", attempt: "));
-    Serial.print(mqttReconnectAttempts);
-    Serial.println();
-  }
+  //if (serialdebug)
+  //{
+  //  Serial.print(F(", attempt: "));
+  //  Serial.print(mqttReconnectAttempts);
+  //  Serial.println();
+  //}
+
+  char logBuf[70];
+  sprintf(logBuf, "MQTT: connecting to broker with %s, attempt: %u", connectMode, mqttReconnectAttempts);
+  sendLog(logBuf, LOGLEVEL_INFO);
 
   if (connRes)
   { // if connection was successful
-    if (serialdebug)
-    {
-      Serial.print(F("MQTT: connected. Reconnects: "));
-      Serial.println(mqttReconnects);
-    }
+    // if (serialdebug)
+    // {
+    //   Serial.print(F("MQTT: connected. Reconnects: "));
+    //   Serial.println(mqttReconnects);
+    // }
+    char logBuf2[50];
+    sprintf(logBuf2, "MQTT: connected. Reconnects: %u", mqttReconnects);
+    sendLog(logBuf2, LOGLEVEL_INFO);
+    //sendLog(F("MQTT: connected. Reconnects: "), LOGLEVEL_INFO);
 
     mqttConnected = true;
     mqttReconnects++;
     mqttOnConnect();
 
-    if (serialdebug)
-      Serial.println("MQTT: subscribed topics:");
+    //if (serialdebug)
+    //Serial.println("MQTT: subscribed topics:");
+    sendLog("MQTT: subscribed topics:", LOGLEVEL_INFO);
+
     if (strlen(mqtt_topic_in_cmd) > 0)
     {
       char mqtt_topic_in_subscribe[52];
       sprintf(mqtt_topic_in_subscribe, "%s/%s", mqtt_topic_in_cmd, "#");
       if (mqttclient.subscribe(mqtt_topic_in_subscribe))
       {
-        if (serialdebug) {
-          Serial.print("    ");
-          Serial.println(mqtt_topic_in_subscribe);
-        }
+        //if (serialdebug) {
+        //  Serial.print("    ");
+        //  Serial.println(mqtt_topic_in_subscribe);
+        //}
+        char buf[60];
+        sprintf(buf, "      %s", mqtt_topic_in_subscribe);
+        sendLog(buf, LOGLEVEL_INFO);
         mqttInTopicSubscribed = true;
       }
     }
@@ -386,20 +409,26 @@ bool mqttReconnect(void)
     {
       if (mqttclient.subscribe(confAdd.outTemp_topic_in))
       {
-        if (serialdebug) {
-          Serial.print("    ");
-          Serial.println(confAdd.outTemp_topic_in);
-        }
+        //if (serialdebug) {
+        //  Serial.print("    ");
+        //  Serial.println(confAdd.outTemp_topic_in);
+        //}
+        char buf[60];
+        sprintf(buf, "      %s", confAdd.outTemp_topic_in);
+        sendLog(buf, LOGLEVEL_INFO);
       }
     }
     if (strlen(confAdd.outHum_topic_in) > 0)
     {
       if (mqttclient.subscribe(confAdd.outHum_topic_in))
       {
-        if (serialdebug) {
-          Serial.print("    ");
-          Serial.println(confAdd.outHum_topic_in);
-        }
+        //if (serialdebug) {
+        //  Serial.print("    ");
+        //  Serial.println(confAdd.outHum_topic_in);
+        //}
+        char buf[60];
+        sprintf(buf, "      %s", confAdd.outHum_topic_in);
+        sendLog(buf, LOGLEVEL_INFO);
       }
     }
     mqttReconnectAttempts = 0;
@@ -408,46 +437,49 @@ bool mqttReconnect(void)
   else
   {
     int mqttConnRes = mqttclient.state();
-    if (serialdebug)
-    {
-      Serial.print("MQTT connect FAILED, rc=");
-      Serial.print(mqttConnRes);
-      Serial.print(" (");
-      switch (mqttConnRes)
-      {
-      case -4:
-        Serial.print(F("MQTT_CONNECTION_TIMEOUT"));
-        break;
-      case -3:
-        Serial.print(F("MQTT_CONNECTION_LOST"));
-        break;
-      case -2:
-        Serial.print(F("MQTT_CONNECT_FAILED"));
-        break;
-      case -1:
-        Serial.print(F("MQTT_DISCONNECTED"));
-        break;
-      case 0:
-        Serial.print(F("MQTT_CONNECTED"));
-        break;
-      case 1:
-        Serial.print(F("MQTT_CONNECT_BAD_PROTOCOL"));
-        break;
-      case 2:
-        Serial.print(F("MQTT_CONNECT_BAD_CLIENT_ID"));
-        break;
-      case 3:
-        Serial.print(F("MQTT_CONNECT_UNAVAILABLE"));
-        break;
-      case 4:
-        Serial.print(F("MQTT_CONNECT_BAD_CREDENTIALS"));
-        break;
-      case 5:
-        Serial.print(F("MQTT_CONNECT_UNAUTHORIZED"));
-        break;
-      }
-      Serial.println(")");
-    }
+    //if (serialdebug) {
+    char logBuf[70];
+    mqtt_updateCurrentStateName();
+    //sprintf_P(logBuf, "%s: %s, rc=%d (%s)", PGMStr_MQTT, PGMStr_connectedFailed, mqttConnRes, PGMStr_MQTTStates[mqttConnRes + 4]);
+    sprintf_P(logBuf, "%s: %s, rc=%d (%s)", PGMStr_MQTT, PGMStr_connectedFailed, mqttConnRes, mqttCurrentStateName);
+    sendLog(logBuf, LOGLEVEL_INFO);
+    //Serial.print("MQTT connect FAILED, rc=");
+    //Serial.print(mqttConnRes);
+    //Serial.print(" (");
+    //switch (mqttConnRes)
+    //{
+    //case -4:
+    //  sendLog(PGMStr_MQTTStateM4);
+    //  break;
+    //case -3:
+    //  sendLog(PGMStr_MQTTStateM3);
+    //  break;
+    //case -2:
+    //  sendLog(PGMStr_MQTTStateM2);
+    //  break;
+    //case -1:
+    //  sendLog(PGMStr_MQTTStateM1);
+    //  break;
+    //case 0:
+    //  sendLog(PGMStr_MQTTState0);
+    //  break;
+    //case 1:
+    //  sendLog(PGMStr_MQTTState1);
+    //  break;
+    //case 2:
+    //  sendLog(PGMStr_MQTTState2);
+    //  break;
+    //case 3:
+    //  sendLog(PGMStr_MQTTState3);
+    //  break;
+    //case 4:
+    //  sendLog(PGMStr_MQTTState4);
+    //  break;
+    //case 5:
+    //  sendLog(PGMStr_MQTTState5);
+    //  break;
+    //}
+    //}
 
     if (mqttReconnectAttempts >= 3 && (mqttConnRes == 4 || mqttConnRes == 5))
     {
@@ -455,7 +487,8 @@ bool mqttReconnect(void)
       mqtt_tempDisabled_credentialError = true;
       if (serialdebug)
       {
-        Serial.println(F("MQTT disabled until reboot due to credential error"));
+        //Serial.println(F("MQTT disabled until reboot due to credential error"));
+        sendLog(F("MQTT disabled until reboot due to credential error"), LOGLEVEL_ERROR);
       }
     }
     mqttConnected = false;
@@ -502,7 +535,8 @@ void mqttHandleConnection()
         doReconnect = true;
         mqttConnected = false;
         mqttLastHeartbeat = millis();
-        Serial.println(F("MQTT: HEARTBEAT overdue. Force-Reconnecting MQTT..."));
+        //Serial.println(F("MQTT: HEARTBEAT overdue. Force-Reconnecting MQTT..."));
+        sendLog(F("MQTT: HEARTBEAT overdue. Force-Reconnecting MQTT..."), LOGLEVEL_WARN);
       }
     }
 
@@ -519,7 +553,8 @@ void mqttHandleConnection()
       if (confMqtt.mqtt_enable_heartbeat && confMqtt.mqtt_heartbeat_maxage_reboot > 0 && ((millis() - mqttLastHeartbeat) > confMqtt.mqtt_heartbeat_maxage_reboot))
       {
         // if no heartbeat was received for set time, reboot the ESP
-        Serial.println(F("MQTT: HEARTBEAT_MAXAGE_REBOOT overdue. Restarting..."));
+        //Serial.println(F("MQTT: HEARTBEAT_MAXAGE_REBOOT overdue. Restarting..."));
+        sendLog(F("MQTT: HEARTBEAT_MAXAGE_REBOOT overdue. Restarting..."), LOGLEVEL_WARN);
         restart();
       }
 
@@ -542,20 +577,9 @@ void mqttPublishStatus()
   if (confMqtt.mqtt_enable && mqttclient.state() == 0)
   {
     char outMsg[60];
-    sprintf(outMsg, "MQTT connected, reconnects: %d, uptime: %s, free_heap: %d", mqttReconnects - 1, uptimeStr, ESP.getFreeHeap());
-    mqttclient.publish(confMqtt.mqtt_topic_out, outMsg, confMqtt.mqtt_outRetain);
-  }
-}
-
-void mqttSendLog(const char *payload)
-{
-  if (confMqtt.mqtt_enable && mqttclient.state() == 0)
-  {
-    char buf[101];
-    strlcpy(buf, payload, 101);
-    Serial.println(buf);
-    mqttclient.publish(confMqtt.mqtt_topic_out, buf, confMqtt.mqtt_outRetain);
-    yield();
+    sprintf_P(outMsg, "%s: %s: %d", PGMStr_MQTT, PGMStr_connectedReconnects, mqttReconnects - 1);
+    //mqttclient.publish(confMqtt.mqtt_topic_out, outMsg, confMqtt.mqtt_outRetain);
+    sendLog(outMsg, LOGLEVEL_INFO);
   }
 }
 
@@ -578,6 +602,7 @@ void mqttPublishConnectMsg()
     if (confMqtt.mqtt_enable && mqttclient.state() == 0)
     {
       mqttclient.publish(confMqtt.mqtt_willTopic, confMqtt.mqtt_connMsg, confMqtt.mqtt_willRetain);
+      //sendLog(F("MQTT: connected"), LOGLEVEL_INFO);
     }
   }
 }
@@ -589,3 +614,7 @@ void mqttPublishHeartbeat()
     mqttclient.publish(mqtt_topic_in_cmd, "HEARTBEAT", false); // publishes to IN-topic. MQTT will force-reconnect if it does not get a HEARTBEAT for 2 min
   }
 }
+
+void mqtt_updateCurrentStateName() {
+  sprintf_P(mqttCurrentStateName, "%s", PGMStr_MQTTStates[mqttclient.state()+4]);
+}

+ 102 - 48
src/WiFiThermostat/mqtt_out.ino

@@ -19,19 +19,35 @@ void publishCurrentThermostatValues(bool force = false)
   updateCurrentHeatingModeName();
   updateCurrentPresetName();
 
-  if (serialdebug)
-  {
-    Serial.print(F("thermostat: {"));
-    Serial.print(F("'heatingMode':"));
-    Serial.print(heatingMode);
-    Serial.print(F(",'preset':"));
-    Serial.print(preset);
-    Serial.print(F(",'setTemp':"));
-    Serial.print(ch_setTemp);
-    Serial.print(F(",'currSetTemp':"));
-    Serial.print(ch_currSetTemp);
-    Serial.println("}");
-  }
+  // if (serialdebug)
+  // {
+  //   Serial.print(F("thermostat: {"));
+  //   Serial.print(F("'heatingMode':"));
+  //   Serial.print(heatingMode);
+  //   Serial.print(F(",'preset':"));
+  //   Serial.print(preset);
+  //   Serial.print(F(",'setTemp':"));
+  //   Serial.print(ch_setTemp);
+  //   Serial.print(F(",'currSetTemp':"));
+  //   Serial.print(ch_currSetTemp);
+  //   Serial.println("}");
+  // }
+
+  char logBuf[101];
+  sprintf_P(logBuf, "%s: %s=%u, %s=%u, %s=%2.1f, %s=%2.1f", PGMStr_thermostat, PGMStr_heatingMode, heatingMode, PGMStr_preset, preset, PGMStr_setTemp, setTemp, PGMStr_currentSetTemp, currSetTemp);
+  sendLog(logBuf, LOGLEVEL_INFO);
+
+  //   Serial.print(F("thermostat: {"));
+  //   Serial.print(F("'heatingMode':"));
+  //   Serial.print(heatingMode);
+  //   Serial.print(F(",'preset':"));
+  //   Serial.print(preset);
+  //   Serial.print(F(",'setTemp':"));
+  //   Serial.print(ch_setTemp);
+  //   Serial.print(F(",'currSetTemp':"));
+  //   Serial.print(ch_currSetTemp);
+  //   Serial.println("}");
+  // }
 
   if (force || !confBas.saveToMqttRetained || setTemp != setTemp_lastPublished)
   {
@@ -40,8 +56,11 @@ void publishCurrentThermostatValues(bool force = false)
 
     if (confBas.saveToMqttRetained && setTemp != setTemp_lastPublished)
     {
-      Serial.print("MQTT retained save setTemp: ");
-      Serial.println(ch_setTemp);
+      //Serial.print("MQTT retained save setTemp: ");
+      //Serial.println(ch_setTemp);
+      char buf1[30];
+      sprintf_P(buf1, "%s %s %2.1f", PGMStr_mqttRetainedSave, PGMStr_setTemp, setTemp);
+      sendLog(buf1);
       mqttclient.publish(mqtt_topic_in_setTemp, ch_setTemp, true);
     }
 
@@ -70,8 +89,11 @@ void publishCurrentThermostatValues(bool force = false)
 
     if (confBas.saveToMqttRetained && heatingMode != heatingMode_lastPublished)
     {
-      Serial.print("MQTT retained save setMode: ");
-      Serial.println(ch_heatingMode);
+      //Serial.print("MQTT retained save setMode: ");
+      //Serial.println(ch_heatingMode);
+      char buf1[30];
+      sprintf_P(buf1, "%s %s %u", PGMStr_mqttRetainedSave, PGMStr_heatingMode, heatingMode);
+      sendLog(buf1);
       mqttclient.publish(mqtt_topic_in_setMode, ch_heatingMode, true);
     }
 
@@ -97,8 +119,11 @@ void publishCurrentThermostatValues(bool force = false)
 
     if (confBas.saveToMqttRetained && preset != preset_lastPublished)
     {
-      Serial.print("MQTT retained save setPreset: ");
-      Serial.println(ch_preset);
+      //Serial.print("MQTT retained save setPreset: ");
+      //Serial.println(ch_preset);
+      char buf1[30];
+      sprintf_P(buf1, "%s %s %u", PGMStr_mqttRetainedSave, PGMStr_preset, preset);
+      sendLog(buf1);
       mqttclient.publish(mqtt_topic_in_setPreset, ch_preset, true);
     }
 
@@ -108,18 +133,34 @@ void publishCurrentThermostatValues(bool force = false)
 
   yield();
 
+  // turnHeatingOn
+  char ch_turnHeatingOn[5];
+  unsigned int currOnOffTime;
+  if (turnHeatingOn)
+  {
+    strcpy(ch_turnHeatingOn, "on");
+    currOnOffTime = heatingOnTime;
+  }
+  else
+  {
+    strcpy(ch_turnHeatingOn, "off");
+    currOnOffTime = heatingOffTime;
+  }
   if (force || turnHeatingOn != turnHeatingOn_lastPublished)
   {
-    char ch_turnHeatingOn[5];
-    if (turnHeatingOn)
-      strcpy(ch_turnHeatingOn, "on");
-    else
-      strcpy(ch_turnHeatingOn, "off");
     sprintf(tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "heating");
     mqttclient.publish(tmp_topic_out, ch_turnHeatingOn, confMqtt.mqtt_outRetain);
-    yield();
+    turnHeatingOn_lastPublished = turnHeatingOn;
   }
 
+  //sendLog(F("heating ON"));
+  //char logBuf[40];
+  sprintf_P(logBuf, "%s: %s=%s (%us)", PGMStr_thermostat, PGMStr_heating, ch_turnHeatingOn, currOnOffTime);
+  sendLog(logBuf, LOGLEVEL_INFO);
+  // END turnHeatingOn
+
+  yield();
+
   char buf[101];
   sprintf(buf, "%lu", heatingOnTime);
   sprintf(tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "heatingOnTime");
@@ -129,6 +170,7 @@ void publishCurrentThermostatValues(bool force = false)
   sprintf(buf, "%lu", heatingOffTime);
   sprintf(tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "heatingOffTime");
   mqttclient.publish(tmp_topic_out, buf);
+
   yield();
 }
 
@@ -146,12 +188,12 @@ void publishCurrentSensorValues(bool force = false)
     dtostrf(currTemp, 1, 1, temp_chararr);
     sprintf(hum_chararr, "%2i", currHum);
 
-    if (serialdebug)
-    {
-      Serial.print(F("sensors: {"));
-      Serial.print("'temp':");
-      Serial.print(temp_chararr);
-    }
+    // if (serialdebug)
+    // {
+    //   Serial.print(F("sensors: {"));
+    //   Serial.print("'temp':");
+    //   Serial.print(temp_chararr);
+    // }
 
     if (force || currTemp != currTemp_lastPublished)
     {
@@ -161,11 +203,11 @@ void publishCurrentSensorValues(bool force = false)
       yield();
     }
 
-    if (serialdebug)
-    {
-      Serial.print(F(",'hum':"));
-      Serial.print(currHum);
-    }
+    // if (serialdebug)
+    // {
+    //   Serial.print(F(",'hum':"));
+    //   Serial.print(currHum);
+    // }
 
     if (force || currHum != currHum_lastPublished)
     {
@@ -177,25 +219,29 @@ void publishCurrentSensorValues(bool force = false)
     dtostrf(currTemp_raw, 1, 1, temp_chararr);
     sprintf(hum_chararr, "%2i", currHum_raw);
 
-    if (serialdebug)
-    {
-      Serial.print(F(",'temp_raw':"));
-      Serial.print(temp_chararr);
-    }
+    // if (serialdebug)
+    // {
+    //   Serial.print(F(",'temp_raw':"));
+    //   Serial.print(temp_chararr);
+    // }
     sprintf(tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "temp_raw");
     mqttclient.publish(tmp_topic_out, temp_chararr);
     yield();
-    if (serialdebug)
-    {
-      Serial.print(F(",'hum_raw':"));
-      Serial.print(currHum_raw);
-    }
+    // if (serialdebug)
+    // {
+    //   Serial.print(F(",'hum_raw':"));
+    //   Serial.print(currHum_raw);
+    // }
     sprintf(tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "hum_raw");
     mqttclient.publish(tmp_topic_out, hum_chararr);
     yield();
 
-    if (serialdebug)
-      Serial.println("}");
+    // if (serialdebug)
+    //   Serial.println("}");
+
+    char logBuf[101];
+    sprintf_P(logBuf, "SENS: temp=%2.1f, hum=%u, tempRaw=%2.1f, humRaw=%u", currTemp, currHum, currTemp_raw, currHum_raw);
+    sendLog(logBuf, LOGLEVEL_INFO);
   }
 }
 
@@ -208,10 +254,12 @@ void publishCurrentPIRValue()
   if (PIRSensorOn)
   {
     mqttclient.publish(tmp_topic_out, confAdd.mqtt_payload_pir_on);
+    sendLog("SENS: PIR=ON", LOGLEVEL_INFO);
   }
   else
   {
     mqttclient.publish(tmp_topic_out, confAdd.mqtt_payload_pir_off);
+    sendLog("SENS: PIR=OFF", LOGLEVEL_INFO);
   }
 
   // PIR additional topic
@@ -239,6 +287,8 @@ void publishDeleteRetainedSavedStates()
     mqttclient.publish(mqtt_topic_in_setMode, "", true);
     mqttclient.publish(mqtt_topic_in_setPreset, "", true);
 
+    sendLog("MQTT: deleted retained saved states", LOGLEVEL_INFO);
+
     publishCurrentThermostatValues(true); // force publish current values again
   }
 }
@@ -281,6 +331,8 @@ void publishDeleteRetainedOutMessages()
     sprintf(tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "heatingOffTime");
     mqttclient.publish(tmp_topic_out, "", true);
 
+    sendLog("MQTT: deleted retained messages (states)", LOGLEVEL_INFO);
+
     publishCurrentThermostatValues(true); // force publish current values again
   }
 }
@@ -299,6 +351,8 @@ void publishDeleteRetainedOutMessages_sensors()
     sprintf(tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "hum");
     mqttclient.publish(tmp_topic_out, "", true);
 
+    sendLog("MQTT: deleted retained messages (sensors)", LOGLEVEL_INFO);
+
     publishCurrentSensorValues(true); // force publish current values again
   }
 }

+ 11 - 4
src/WiFiThermostat/outTempHum.ino

@@ -12,15 +12,22 @@ void outTempHum_updateOnNewValue() {
 void outTemp_update() {
   outTemp = atof(outTemp_newValue);
   outTempHumLastUpdate = millis();
-  Serial.print("outTemp=");
-  Serial.println(outTemp);
+
+  char buf[40];
+  sprintf(buf, "MQTT: received OUTTemp=%2.1f", outTemp);
+  sendLog(buf, LOGLEVEL_INFO);
+  //Serial.print("outTemp=");
+  //Serial.println(outTemp);
   outTemp_parseNewValue = false;
 }
 
 void outHum_update() {
   outHum = atoi(outHum_newValue);
   outTempHumLastUpdate = millis();
-  Serial.print("outHum=");
-  Serial.println(outHum);
+  char buf[40];
+  sprintf(buf, "MQTT: received OUTHum=%u", outHum);
+  sendLog(buf, LOGLEVEL_INFO);
+  //Serial.print("outHum=");
+  //Serial.println(outHum);
   outHum_parseNewValue = false;
 }

+ 16 - 0
src/WiFiThermostat/scheduler.ino

@@ -22,9 +22,18 @@ void everySecond() {
     everyMinute();
   }
   
+  //webSocket.sendTXT(num, "Test\n");
+  // if(confWeb.enableConsole && webSocket.connectedClients() > 0) {
+  //   char buf[40];
+  //   sprintf(buf, "%u\n", millis());
+  //   webSocket.broadcastTXT(buf);
+  // }
+
   handleDisplayTimeout();
   checkValuesChanged();
 
+  clearValidSessionId();
+
   if (countMeasureInterval < confBas.measureInterval) countMeasureInterval++;
   else {
     countMeasureInterval = 0;
@@ -55,6 +64,13 @@ unsigned int outPubInterval_sensors_count = 0;
 void everyMinute() {
   updateUptime();
   buildUptimeString();
+
+  if(confTime.ntpEnable) {
+    updateTimeFromNTP();
+  }
+
+  logSysdata();
+
   if(confMqtt.mqtt_enable) {
     
     if(!confMqtt.mqtt_willRetain) mqttPublishConnectMsg();

+ 66 - 42
src/WiFiThermostat/thermostat.ino

@@ -17,29 +17,29 @@ void measureTempHum() {
   for (int i = 0; i < DHTreadRetries; i++) {
     readDHTsensorTemp();
     if (!isnan(tmpTemp)) {
-      //mqttSendLog(F("DHT: reading Temp OK"));
+      sendLog(F("DHT: reading Temp OK"), LOGLEVEL_VERBOSE);
       i = DHTreadRetries;
     }
-    //else {
-    //  mqttSendLog(F("DHT: reading Temp failed - retrying.."));
-    //}
+    else {
+      sendLog(F("DHT: reading Temp failed - retrying.."), LOGLEVEL_INFO);
+    }
   }
 //
   for (int i = 0; i < DHTreadRetries; i++) {
     readDHTsensorHum();
     if (!isnan(tmpHum)) {
-      //mqttSendLog(F("DHT: reading Hum OK"));
+      sendLog(F("DHT: reading Hum OK"), LOGLEVEL_VERBOSE);
       i = DHTreadRetries;
     }
-    //else {
-    //  mqttSendLog(F("DHT: reading Hum failed - retrying.."));
-    //}
+    else {
+      sendLog(F("DHT: reading Hum failed - retrying.."), LOGLEVEL_INFO);
+    }
   }
 
   // Check if any reads failed
   if (isnan(tmpHum) || isnan(tmpTemp)) {
     //Serial.println("Failed to read from DHT sensor!");
-    mqttSendLog("Error: Failed to read from DHT sensor!");
+    sendLog(F("Error: Failed to read from DHT sensor!"), LOGLEVEL_ERROR);
   }
   else {
     tmpTemp = tmpTemp + confAdv.tempCorrVal;
@@ -101,16 +101,16 @@ void thermostat() {
   if (heatingMode > 0 && turnHeatingOn) {
     heatingOnTime = (millis() - heatingLastOnMillis) / 1000;
 
-    char buf[101];
-    sprintf(buf, "heating on since %lu s", heatingOnTime);
-    mqttSendLog(buf);
+    //char buf[101];
+    //sprintf(buf, "heating on for %lus", heatingOnTime);
+    //sendLog(buf, LOGLEVEL_INFO);
   }
   else if (heatingMode > 0 && !turnHeatingOn) {
     heatingOffTime = (millis() - heatingLastOffMillis) / 1000;
 
-    char buf[101];
-    sprintf(buf, "heating off since %lu s", heatingOffTime);
-    mqttSendLog(buf);
+    //char buf[101];
+    //sprintf(buf, "heating off for %lus", heatingOffTime);
+    //sendLog(buf, LOGLEVEL_INFO);
   }
 
 
@@ -136,8 +136,8 @@ void thermostat() {
       updateDisplay();
 
       char buf[101];
-      sprintf(buf, "switch heating OFF, on since %lu s", heatingOnTime);
-      mqttSendLog(buf);
+      sprintf_P(buf, "TSTAT: %s OFF, on for %lus", PGMStr_switchHeating, heatingOnTime);
+      sendLog(buf, LOGLEVEL_INFO);
 
       //Serial.println("heating off");
       //mqttclient.publish(tmp_topic_out, "off");
@@ -150,8 +150,8 @@ void thermostat() {
       updateDisplay();
 
       char buf[101];
-      sprintf(buf, "switch heating ON, off since %lu s", heatingOffTime);
-      mqttSendLog(buf);
+      sprintf(buf, "TSTAT: switch heating ON, off for %lus", heatingOffTime);
+      sendLog(buf, LOGLEVEL_INFO);
 
       //Serial.println("heating on");
       //mqttclient.publish(tmp_topic_out, "on");
@@ -165,8 +165,8 @@ void thermostat() {
       heatingLastOffMillis = millis();
     }
 
-    if ( lastTempUpdate != 0 ) mqttSendLog("switch heating OFF, temp reading not yet available");
-    else if ( (millis() - lastTempUpdate) > maxMeasurementAge ) mqttSendLog("switch heating OFF, last temp reading too old");
+    if ( lastTempUpdate != 0 ) sendLog(F("TSTAT: heating OFF, temp reading not yet available"), LOGLEVEL_INFO);
+    else if ( (millis() - lastTempUpdate) > maxMeasurementAge ) sendLog(F("TSTAT: heating OFF, last temp reading too old"), LOGLEVEL_WARN);
 
     //mqttclient.publish(tmp_topic_out, "off");
     publishCurrentThermostatValues();
@@ -190,7 +190,7 @@ void toggleOnOff() {
 }
 
 void togglePreset() {
-  Serial.print("switch preset to ");
+  //sendLog(F("switch preset to "));
   if (pendingPreset < 0 || pendingPreset > 2) pendingPreset = preset;
 
   //    if (pendingPresetToggle && preset == 0 && pendingPreset == 0) pendingPreset = 1;
@@ -221,10 +221,10 @@ void togglePreset() {
   //updateDisplay();
   //pendingPresetToggle = true;
   displayShowLine2OverlayMessage(pendingPresetName);
-  Serial.print(pendingPreset);
-  Serial.print(" - \"");
-  Serial.print(currentPresetName);
-  Serial.println("\"");
+  //Serial.print(pendingPreset);
+  //Serial.print(" - \"");
+  //Serial.print(currentPresetName);
+  //Serial.println("\"");
 }
 
 void updateCurrentHeatingModeName() {
@@ -246,7 +246,7 @@ void updatePendingPresetName() {
 
 void setTempStepUp() {
   if (heatingMode == 1) {
-    Serial.println("setTemp +0.5");
+    sendLog(F("TSTAT: setTemp +0.5"));
     if ( setTemp <= (confBas.setTempMax - 0.5)) {
       setTemp += 0.5;
       lastValueChange = millis();
@@ -258,7 +258,7 @@ void setTempStepUp() {
 
 void setTempStepDown() {
   if (heatingMode == 1) {
-    Serial.println("setTemp -0.5");
+    sendLog(F("TSTAT: setTemp -0.5"));
     if ( setTemp >= (confBas.setTempMin + 0.5)) {
       setTemp -= 0.5;
       lastValueChange = millis();
@@ -290,13 +290,18 @@ void setTempTo(float setTo) {
         setTempAlreadySaved = false;
         updateDisplay();
         publishCurrentThermostatValues();
-        Serial.println("setTemp CHANGED");
+
+        char buf1[30];
+        //sprintf(buf1, "setTemp changed to %2.1f", setTemp);
+        sprintf_P(buf1, "%s: %s %s %2.1f", PGMStr_thermostat, PGMStr_setTemp, PGMStr_changedTo, setTemp);
+        //sendLog(F("setTemp changed"));
+        sendLog(buf1);
       }
     }
 }
 
 void setTempLowStepUp() {
-  Serial.println("setTempLow +0.5");
+  sendLog(F("TSTAT: setTempLow +0.5"));
   if ( setTempLow <= (setTempLowMax - 0.5)) {
     setTempLow += 0.5;
     lastValueChange = millis();
@@ -306,7 +311,7 @@ void setTempLowStepUp() {
 }
 
 void setTempLowStepDown() {
-  Serial.println("setTempLow -0.5");
+  sendLog(F("TSTAT: setTempLow -0.5"));
   if ( setTempLow >= (setTempLowMin + 0.5)) {
     setTempLow -= 0.5;
     lastValueChange = millis();
@@ -337,13 +342,18 @@ void setTempLowTo(float setTo) {
       setTempLowAlreadySaved = false;
       updateDisplay();
       publishCurrentThermostatValues();
-      Serial.println("setTempLow CHANGED");
+      
+      //Serial.println("setTempLow CHANGED");
+      char buf1[30];
+      //sprintf(buf1, "setTempLow changed to %2.1f", setTempLow);
+      sprintf_P(buf1, "%s: %s %s %2.1f", PGMStr_thermostat, PGMStr_setTempLow, PGMStr_changedTo, setTempLow);
+      sendLog(buf1);
     }
   }
 }
 
 void setTempLow2StepUp() {
-  Serial.println("setTempLow2 +0.5");
+  sendLog(F("TSTAT: setTempLow2 +0.5"));
   if ( setTempLow2 <= (setTempLowMax - 0.5)) {
     setTempLow2 += 0.5;
     lastValueChange = millis();
@@ -353,7 +363,7 @@ void setTempLow2StepUp() {
 }
 
 void setTempLow2StepDown() {
-  Serial.println("setTempLow2 -0.5");
+  sendLog(F("TSTAT: setTempLow2 -0.5"));
   if ( setTempLow2 >= (setTempLowMin + 0.5)) {
     setTempLow2 -= 0.5;
     lastValueChange = millis();
@@ -384,7 +394,12 @@ void setTempLow2To(float setTo) {
       setTempLow2AlreadySaved = false;
       updateDisplay();
       publishCurrentThermostatValues();
-      Serial.println("setTempLow2 CHANGED");
+      
+      //Serial.println("setTempLow2 CHANGED");
+      char buf1[30];
+      //sprintf(buf1, "setTempLow2 changed to %2.1f", setTempLow2);
+      sprintf_P(buf1, "%s: %s %s %2.1f", PGMStr_thermostat, PGMStr_setTempLow2, PGMStr_changedTo, setTempLow2);
+      sendLog(buf1);
     }
   }
 }
@@ -398,7 +413,12 @@ void setHeatingmodeTo(unsigned char setTo) {
     updateCurrentHeatingModeName();
     updateDisplay();
     publishCurrentThermostatValues();
-    Serial.println("heatingMode CHANGED");
+
+    //Serial.println("heatingMode CHANGED");
+    char buf1[30];
+    //sprintf(buf1, "heatingMode changed to %u", heatingMode);
+    sprintf_P(buf1, "%s: %s %s %u", PGMStr_thermostat, PGMStr_heatingMode, PGMStr_changedTo, heatingMode);
+    sendLog(buf1);
   }
 }
 
@@ -413,7 +433,11 @@ void setPresetTo(unsigned char setTo) {
       updateCurrentPresetName();
       updateDisplay();
       publishCurrentThermostatValues();
-      Serial.println("preset CHANGED");
+      
+      //Serial.println("preset CHANGED");
+      char buf1[30];
+      sprintf_P(buf1, "%s: %s %s %u", PGMStr_thermostat, PGMStr_preset, PGMStr_changedTo, preset);
+      sendLog(buf1);
     }
   }
 }
@@ -425,7 +449,7 @@ void checkValuesChanged() { // called every second by everySecond() / scheduler.
         lastUpdate_setTemp = millis();
         if (confBas.autoSaveSetTemp && setTemp != setTempSaved) {
           saveSetTemp();
-          mqttSendLog("setTemp autosave done");
+          sendLog(F("TSTAT: setTemp autosave done"));
         }
         setTempAlreadySaved = true;
       }
@@ -433,7 +457,7 @@ void checkValuesChanged() { // called every second by everySecond() / scheduler.
         lastUpdate_setTempLow = millis();
         if (confBas.autoSaveSetTemp && setTempLow != setTempLowSaved) {
           saveSetTempLow();
-          mqttSendLog("setTempLow autosave done");
+          sendLog(F("TSTAT: setTempLow autosave done"));
         }
         setTempLowAlreadySaved = true;
       }
@@ -441,7 +465,7 @@ void checkValuesChanged() { // called every second by everySecond() / scheduler.
         lastUpdate_setTempLow2 = millis();
         if (confBas.autoSaveSetTemp && setTempLow2 != setTempLow2Saved) {
           saveSetTempLow2();
-          mqttSendLog("setTempLow2 autosave done");
+          sendLog(F("TSTAT: setTempLow2 autosave done"));
         }
         setTempLow2AlreadySaved = true;
       }
@@ -449,7 +473,7 @@ void checkValuesChanged() { // called every second by everySecond() / scheduler.
         lastUpdate_heatingMode = millis();
         if (confBas.autoSaveHeatingMode && heatingMode != heatingModeSaved) {
           saveHeatingMode();
-          mqttSendLog("heatingMode autosave done");
+          sendLog(F("TSTAT: heatingMode autosave done"));
         }
         heatingModeAlreadySaved = true;
       }
@@ -458,7 +482,7 @@ void checkValuesChanged() { // called every second by everySecond() / scheduler.
         preset = pendingPreset;
         if (confBas.autoSaveHeatingMode && preset != presetSaved) {
           savePreset();
-          mqttSendLog("preset autosave done");
+          sendLog(F("TSTAT: preset autosave done"));
         }
         presetAlreadySaved = true;
       }

+ 95 - 0
src/WiFiThermostat/time.ino

@@ -0,0 +1,95 @@
+bool setupTime()
+{
+    if (strlen(confTime.ntpServer1) > 4 && strlen(confTime.ntpServer2) > 4)
+    {
+        //configTime(gmtOffset_sec, daylightOffset_sec, ntpServer [, ntpServer2[, ntpServer3]]);
+        configTime(0, 0, confTime.ntpServer1, confTime.ntpServer2);
+    }
+    else if (strlen(confTime.ntpServer1) > 4)
+    {
+        configTime(0, 0, confTime.ntpServer1);
+    }
+    else return false;
+    setenv("TZ", confTime.timeZoneStr, 1); // Zeitzone einstellen https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
+    return true;
+}
+
+void updateTimeFromNTP()
+{
+    time_t now = time(&now);
+    static time_t lastsek{0};
+    if (lt.tm_sec != lastsek)
+    {
+        lastsek = lt.tm_sec;
+        if (!(time(&now) % confTime.ntpSyncInterval))
+        {
+            //Serial.println("updating time from NTP server");
+            sendLog(F("NTP: updating time"), LOGLEVEL_INFO);
+            setupTime();
+        }
+    }
+}
+
+void updateTime() {
+    time_t now = time(&now);
+    localtime_r(&now, &lt);
+}
+
+// char getDateTime() {
+//     char* buf[30]; // je nach Format von "strftime" eventuell anpassen
+//     time_t now = time(&now);
+//     localtime_r(&now, &lt);
+//     strftime(&buf, sizeof(buf), "%Y-%m-%d %T", &lt); // http://www.cplusplus.com/reference/ctime/strftime/
+//     return buf;
+// }
+
+
+// char getDate() {
+//     char buf[13];
+//     updateTime();
+//     strftime(buf, sizeof(buf), "%d.%m.%Y", &lt); // http://www.cplusplus.com/reference/ctime/strftime/
+//     //return buf;
+// }
+
+// char getDate_YMD() {
+//     char* buf[13];
+//     time_t now = time(&now);
+//     localtime_r(&now, &lt);
+    
+//     strftime(&buf, sizeof(buf), "%Y-%m-%d", &lt); // http://www.cplusplus.com/reference/ctime/strftime/
+//     return buf;
+// }
+
+// char getTime() {
+//     char* buf[6];
+//     time_t now = time(&now);
+//     localtime_r(&now, &lt);
+    
+//     strftime(&buf, sizeof(buf), "%H:%M", &lt); // http://www.cplusplus.com/reference/ctime/strftime/
+//     return buf;
+// }
+
+// char getTime_HMS() {
+//     char* buf[9];
+//     time_t now = time(&now);
+//     localtime_r(&now, &lt);
+    
+//     strftime(&buf, sizeof(buf), "%H:%M:%S", &lt); // http://www.cplusplus.com/reference/ctime/strftime/
+//     return buf;
+// }
+
+/*void printTimeToSerial()
+{
+    static char buf[30]; // je nach Format von "strftime" eventuell anpassen
+    time_t now = time(&now);
+    localtime_r(&now, &lt);
+    //gmtime_r(&now, &utc);
+
+    strftime(buf, sizeof(buf), "%Y-%m-%d %T", &lt); // http://www.cplusplus.com/reference/ctime/strftime/
+    //Serial.printf("%s %s", buf, dayShortNames[lt.tm_wday]);
+    Serial.printf("%s", buf);
+    Serial.println();
+
+    //Serial.printf("UTC: %.2d:%.2d:%.2d\n", utc.tm_hour, utc.tm_min, utc.tm_sec);
+    //Serial.printf("%s: %.2d:%.2d:%.2d %s\n\n", *tzname, lt.tm_hour, lt.tm_min, lt.tm_sec, lt.tm_isdst ? "Sommerzeit" : "Normalzeit");
+}*/

+ 98 - 0
src/WiFiThermostat/websockets.ino

@@ -0,0 +1,98 @@
+/*
+ * Returns a bool value as an indicator to describe whether a user is allowed to initiate a websocket upgrade
+ * based on the value of a cookie. This function expects the rawCookieHeaderValue to look like this "sessionId=<someSessionIdNumberValue>|"
+ */
+bool isCookieValid(String rawCookieHeaderValue) {
+    if(wsValidSessionId != 0) {
+	  if (rawCookieHeaderValue.indexOf("sessionId") != -1) {
+	    String sessionIdStr = rawCookieHeaderValue.substring(rawCookieHeaderValue.indexOf("sessionId=") + 10, rawCookieHeaderValue.indexOf("|"));
+	    unsigned long int sessionId = strtoul(sessionIdStr.c_str(), NULL, 10);
+	    return sessionId == wsValidSessionId;
+	  }
+    }
+	return false;
+}
+
+void clearValidSessionId() {
+    // wsValidSessionId = millis() -> clear after timeout to somehow improve "security"
+    if( wsValidSessionId > 0 && (millis() - wsValidSessionId) > 2000 ) wsValidSessionId = 0;
+}
+
+/*
+ * The WebSocketServerHttpHeaderValFunc delegate passed to webSocket.onValidateHttpHeader
+ */
+bool validateHttpHeader(String headerName, String headerValue) {
+	//assume a true response for any headers not handled by this validator
+	bool valid = true;
+
+	if(headerName.equalsIgnoreCase("Cookie")) {
+		//if the header passed is the Cookie header, validate it according to the rules in 'isCookieValid' function
+		valid = isCookieValid(headerValue);
+	}
+	return valid;
+}
+
+
+void startWebSocketServer() { // Start a WebSocket server
+  //if(strlen(confWeb.http_user) > 0 && strlen(confWeb.http_pass) > 0) webSocket.setAuthorization(confWeb.http_user, confWeb.http_pass);
+  const char * headerkeys[] = { "Cookie" };
+  size_t headerKeyCount = sizeof(headerkeys) / sizeof(char*);
+  webSocket.onValidateHttpHeader(validateHttpHeader, headerkeys, headerKeyCount);
+
+  webSocket.begin();                          // start the websocket server
+  webSocket.onEvent(webSocketEvent);          // if there's an incomming websocket message, go to function 'webSocketEvent'
+  //Serial.println("WebSocket server started.");
+  sendLog("WS: WebSocket server started on Port 81", LOGLEVEL_INFO);
+}
+
+void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) {
+    if(length > 0) { // filter message with length=0, otherwise ESP8266 crashes
+    char logBuf[60];
+        switch(type) {
+            case WStype_DISCONNECTED:
+                Serial.printf("[%u] Disconnected!\n", num);
+                break;
+            case WStype_CONNECTED:
+                {
+                    IPAddress ip = webSocket.remoteIP(num);
+                    //Serial.printf("WS: Connected Client-[%u], %d.%d.%d.%d, URL: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload);
+                    sprintf(logBuf, "WS: Client[%u] connected, %d.%d.%d.%d, URL: %s", num, ip[0], ip[1], ip[2], ip[3], payload);
+                    sendLog(logBuf, LOGLEVEL_INFO);
+    
+	    			// send message to client
+	    			webSocket.sendTXT(num, "Connected\r\n");
+                }
+                break;
+            case WStype_TEXT:
+                //Serial.printf("WS: received from Client-[%u]: %s\n", num, payload);
+                sprintf(logBuf, "WS: received from Client[%u]: %s", num, payload);
+                sendLog(logBuf, LOGLEVEL_INFO);
+
+                char buf[31];
+                sprintf(buf, "%s", payload);
+
+                // send received text to command handler
+                if(strlen(buf) > 3) strlcpy(cmdPayload, buf, sizeof(cmdPayload));
+                cmdInQueue = true; // will be checked in next loop() run
+
+                // send message to client
+                // webSocket.sendTXT(num, "message here");
+
+                // send data to all connected clients
+                // webSocket.broadcastTXT("message here");
+                break;
+            case WStype_ERROR:
+                break;
+            case WStype_BIN:    
+                Serial.printf("[%u] get binary length: %u\n", num, length);
+                hexdump(payload, length);
+                break;
+            default:
+                break;
+            //  
+            //    // send message to client
+            //    // webSocket.sendBIN(num, payload, length);
+            //    break;
+        }
+    }
+}

+ 1 - 1
src/webinterface/api

@@ -1 +1 @@
-{"devname":"WTherm-3598","ssid":"Kinf","WiFiNum":1,"uptime":"00:20","freeheap":36064,"mqttstate":"CONNECTED","mqtthost":"mqtt.lan","mqttreconn":0,"setTemp":22.5,"currSetTemp":22.5,"temp":25.25867,"hum":21,"heating":false,"mode":1,"modeName":"heat","pset":0,"psetName":"Normalbetrieb","psetName0":"Normalbetrieb","psetName1":"Absenkung 1","psetName2":"Absenkung 2","tempLow":20.5,"tempLow2":17.5}
+{"devname":"WTherm-3598","ssid":"Kinf","WiFiNum":1,"uptime":"00:01","time":"23:26","date":"13.01.2020","freeheap":29904,"mqttstate":"CONNECTED","mqtthost":"mqtt.lan","mqttreconn":0,"setTemp":23,"currSetTemp":20.5,"temp":25.981,"hum":21,"heating":false,"mode":1,"modeName":"heat","pset":1,"psetName":"Absenkung 1","psetName0":"Normalbetrieb","psetName1":"Absenkung 1","psetName2":"Absenkung 2","tempLow":20.5,"tempLow2":17.5,"outTemp":0,"outHum":88}

+ 9 - 3
src/webinterface/conf

@@ -1,21 +1,27 @@
 <html><head>
 <meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>
 <link rel='stylesheet' href='style.css'>
-<title>WiFiThermostat - WTherm-3598</title></head>
+<title>WiFiThermostat - WTherm-T5WZ</title></head>
 <body>
 <div id='main'>
-<div id='head'>WiFiThermostat - WTherm-3598
+<div id='head'>WiFiThermostat - WTherm-T5WZ
 </div><hr>
 <div></div>
 <p><b>Configuration</b></p>
 <table style='width:100%'>
 <tr><td><form action='confdevwifi' method='get'><button>Device &amp; WiFi</button></form></td></tr>
+<tr><td><form action='conftime' method='get'><button>Date &amp; Time</button></form></td></tr>
+<tr><td><form action='conflog' method='get'><button>Logging</button></form></td></tr>
 <tr><td><form action='confweb' method='get'><button>Web Interface</button></form></td></tr>
 <tr><td><form action='confmqtt' method='get'><button>MQTT</button></form></td></tr>
 <tr><td><form action='confbas' method='get'><button>Thermostat Basic</button></form></td></tr>
 <tr><td><form action='confadv' method='get'><button>Thermostat Advanced</button></form></td></tr>
 <tr><td><form action='confadd' method='get'><button>Additional Functions</button></form></td></tr>
 <tr><td><form action='update' method='get'><button>OTA Firmware Update</button></form></td></tr>
+
+<tr><td>&nbsp;</td></tr>
+<tr><td><form action='console' method='get'><button>Console</button></form></td></tr>
+
 </table>
 <div></div>
 <table style='width:100%'><tr>
@@ -23,5 +29,5 @@
 <td><form action='/' method='get' onsubmit='return confirm("Confirm Restart");'><button name='restart' class='bred'>Restart</button></form></td>
 </tr></table>
 
-<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.5.0&nbsp;<span style='color:red;font-weight:bold;'>DEBUG</span> by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.6.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
 </div></body></html>

+ 1 - 1
src/webinterface/confDataAdd

@@ -1 +1 @@
-{"outTempTop":"","outHumTop":"","PIRTop":"","PIROnPld":"ON","PIROffPld":"OFF"}
+{"outTempTop":"wetter/atemp","outHumTop":"wetter/ahum","PIRTop":"","PIROnPld":"ON","PIROffPld":"OFF"}

+ 1 - 1
src/webinterface/confDataAdv

@@ -1 +1 @@
-{"minOffTime":120,"tempDec":0,"hyst":0.2,"tempCorr":0,"humCorr":0,"offMsg":"Heizung aus","modeName0":"off","modeName1":"heat","psetName0":"Normalbetrieb","psetName1":"Absenkung 1","psetName2":"Absenkung 2","iTempLab":"I","oTempLab":"A"}
+{"minOffTime":120,"tempDec":0,"hyst":0.15,"tempCorr":0,"humCorr":0,"offMsg":"Heizung aus","modeName0":"off","modeName1":"heat","psetName0":"Normalbetrieb","psetName1":"Absenkung 1","psetName2":"Absenkung 2","iTempLab":"I","oTempLab":"A"}

+ 1 - 1
src/webinterface/confDataBas

@@ -1 +1 @@
-{"autoSaveTemp":1,"autoSaveMode":0,"saveToMqttRet":1,"tempMin":16,"tempMax":26,"measInt":18,"dispInt":7,"dispTout":27,"PIRenDisp":1,"PIRenDispPs0":1,"togTHdisp":0}
+{"autoSaveTemp":1,"autoSaveMode":0,"saveToMqttRet":1,"tempMin":16,"tempMax":26,"measInt":15,"dispInt":3,"dispTout":30,"PIRenDisp":0,"PIRenDispPs0":1,"togTHdisp":0}

+ 1 - 1
src/webinterface/confDataDevWiFi

@@ -1 +1 @@
-{"devName":"WTherm-3598","hostName":"","SSID1":"Kinf","WPW1":"****","SSID2":"Knet","WPW2":"****","SSIDAP":"WTherm-3598","WPWAP":"","WAPtout":3,"WConnCheck":20,"Wretry":10,"Wreboot":60}
+{"devName":"WTherm-T5WZ","hostName":"WThermT5WZ","SSID1":"Kinf","WPW1":"****","SSID2":"","WPW2":"","SSIDAP":"WTherm-T5WZ","WPWAP":"****","WAPtout":3,"WConnCheck":20,"Wretry":10,"Wreboot":60}

+ 1 - 0
src/webinterface/confDataLog

@@ -0,0 +1 @@
+{"logLevSer":4,"logLevWeb":4,"logLevMqtt":3}

+ 1 - 1
src/webinterface/confDataMqtt

@@ -1 +1 @@
-{"mqttEnable":1,"mqttHost":"mqtt.lan","mqttPort":1883,"mqttUser":"espclient","mqttPass":"****","inTop":"Test/Thermostat","outTop":"Test/Thermostat/stat","outRet":1,"outRetSens":0,"outPubInt":2,"outPubIntSens":5,"willTop":"Test/Thermostat/availability","willQos":2,"willRet":0,"willMsg":"offline","connMsg":"online","hbEnable":1,"hbReconn":3,"hbReboot":30}
+{"mqttEnable":1,"mqttHost":"mqtt.lan","mqttPort":1883,"mqttUser":"espclient9","mqttPass":"****","inTop":"T5/Thermostat","outTop":"T5/Thermostat/stat","outRet":1,"outRetSens":0,"outPubInt":10,"outPubIntSens":5,"willTop":"T5/Thermostat/availability","willQos":2,"willRet":1,"willMsg":"offline","connMsg":"online","hbEnable":1,"hbReconn":3,"hbReboot":30}

+ 1 - 0
src/webinterface/confDataTime

@@ -0,0 +1 @@
+{"NTPEnable":1,"NTPServer1":"ntp.lan","NTPServer2":"","TZStr":"CET-1CEST,M3.5.0/02,M10.5.0/03","NTPSyncInt":7200}

+ 1 - 1
src/webinterface/confDataWeb

@@ -1 +1 @@
-{"apiToken":"","httpUA":"admin","httpPA":"****","httpAuth":1,"httpU1":"flo","httpP1":"****","httpU2":"","httpP2":""}
+{"apiToken":"","httpUA":"admin","httpPA":"****","httpAuth":1,"httpU1":"flo","httpP1":"****","httpU2":"","httpP2":"","enableConsole":1}

+ 7 - 7
src/webinterface/confadd

@@ -1,7 +1,7 @@
 <html><head>
 <meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>
 <link rel='stylesheet' href='style.css'>
-<title>WiFiThermostat - WTherm-3598</title>
+<title>WiFiThermostat - WTherm-T5WZ</title>
 <script>
   function g(i) { return document.getElementById(i) };
   function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
@@ -24,9 +24,9 @@
       el.style.visibility = 'hidden';
     }
   }
-
+  
   function transmit(f) {
-    if (!xhttp) {
+    if (!xhttp) {   
     reqTime = 0;
     reqFin = false;
     xhttp = new XMLHttpRequest();
@@ -55,7 +55,7 @@
    }
     return false;
   }
-
+  //transmit();
   function saveConf() {
     g('frmConf').submit();
   }
@@ -67,7 +67,7 @@
 </head>
 <body onload='init()'>
 <div id='main'>
-<div id='head'>WiFiThermostat - WTherm-3598
+<div id='head'>WiFiThermostat - WTherm-T5WZ
 </div><hr>
 <div></div>
 <b>Configuration - Additional Functions</b>
@@ -83,7 +83,7 @@
 <div></div><br>
 <fieldset>
 <legend>Outside Temp/Hum via MQTT</legend>
-<p style='font-weight:normal;font-size:0.8em;'>Only used if not empty, can be subscribed to and valid data is sent. </p>
+<p class='n'>Only used if not empty, can be subscribed to and valid data is sent. </p>
 <p><b>O-Temp In-Topic</b><br><input type='text' name='outTempTop' id='outTempTop'/></p>
 <p><b>O-Hum In-Topic</b><br><input type='text' name='outHumTop' id='outHumTop'/><br>
 </fieldset>
@@ -97,5 +97,5 @@
 </tr></table>
 </div>
 
-<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.4.1&nbsp;<span style='color:red;font-weight:bold;'>DEBUG</span> by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.6.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
 </div></body></html>

+ 6 - 6
src/webinterface/confadv

@@ -1,7 +1,7 @@
 <html><head>
 <meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>
 <link rel='stylesheet' href='style.css'>
-<title>WiFiThermostat - WTherm-3598</title>
+<title>WiFiThermostat - WTherm-T5WZ</title>
 <script>
   function g(i) { return document.getElementById(i) };
   function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
@@ -24,9 +24,9 @@
       el.style.visibility = 'hidden';
     }
   }
-
+  
   function transmit(f) {
-    if (!xhttp) {
+    if (!xhttp) {   
     reqTime = 0;
     reqFin = false;
     xhttp = new XMLHttpRequest();
@@ -63,7 +63,7 @@
    }
     return false;
   }
-
+  //transmit();
   function saveConf() {
     g('frmConf').submit();
   }
@@ -75,7 +75,7 @@
 </head>
 <body onload='init()'>
 <div id='main'>
-<div id='head'>WiFiThermostat - WTherm-3598
+<div id='head'>WiFiThermostat - WTherm-T5WZ
 </div><hr>
 <div></div>
 <b>Configuration - Thermostat Advanced</b>
@@ -132,5 +132,5 @@ Also shown on LCD at preset change, <br>therefore may not exceed <b>13 chars!</b
 </tr></table>
 </div>
 
-<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.5.0a&nbsp;<span style='color:red;font-weight:bold;'>DEBUG</span> by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.6.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
 </div></body></html>

+ 6 - 6
src/webinterface/confbas

@@ -1,7 +1,7 @@
 <html><head>
 <meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>
 <link rel='stylesheet' href='style.css'>
-<title>WiFiThermostat - WTherm-3598</title>
+<title>WiFiThermostat - WTherm-T5WZ</title>
 <script>
   function g(i) { return document.getElementById(i) };
   function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
@@ -24,9 +24,9 @@
       el.style.visibility = 'hidden';
     }
   }
-
+  
   function transmit(f) {
-    if (!xhttp) {
+    if (!xhttp) {   
     reqTime = 0;
     reqFin = false;
     updCbxVal(g('autoSaveTemp'));
@@ -67,7 +67,7 @@
    }
     return false;
   }
-
+  //transmit();
   function saveConf() {
     updCbxVal(g('autoSaveTemp'));
     updCbxVal(g('autoSaveMode'));
@@ -85,7 +85,7 @@
 </head>
 <body onload='init()'>
 <div id='main'>
-<div id='head'>WiFiThermostat - WTherm-3598
+<div id='head'>WiFiThermostat - WTherm-T5WZ
 </div><hr>
 <div></div>
 <b>Configuration - Thermostat Basic</b>
@@ -134,5 +134,5 @@ message automatically on startup/reconnect.</p>
 </tr></table>
 </div>
 
-<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.5.0&nbsp;<span style='color:red;font-weight:bold;'>DEBUG</span> by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.6.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
 </div></body></html>

+ 22 - 20
src/webinterface/confdevwifi

@@ -1,7 +1,7 @@
 <html><head>
 <meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>
 <link rel='stylesheet' href='style.css'>
-<title>WiFiThermostat - WTherm-3598</title>
+<title>WiFiThermostat - WTherm-T5WZ</title>
 <script>
   function g(i) { return document.getElementById(i) };
   function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
@@ -24,9 +24,9 @@
       el.style.visibility = 'hidden';
     }
   }
-
+  
   function transmit(f) {
-    if (!xhttp) {
+    if (!xhttp) {   
     reqTime = 0;
     reqFin = false;
     xhttp = new XMLHttpRequest();
@@ -39,16 +39,16 @@
         var data = JSON.parse(xhttp.responseText);
         g('devName').value = data.devName;
         g('hostName').value = data.hostName;
-      g('SSID1').value = data.SSID1;
+		    g('SSID1').value = data.SSID1;
         g('WPW1').value = data.WPW1;
         g('SSID2').value = data.SSID2;
         g('WPW2').value = data.WPW2;
         g('SSIDAP').innerHTML = data.SSIDAP;
-      g('WPWAP').value = data.WPWAP;
-      g('WAPtout').value = data.WAPtout;
+		    g('WPWAP').value = data.WPWAP;
+		    g('WAPtout').value = data.WAPtout;
         g('WConnCheck').value = data.WConnCheck;
-      g('Wretry').value = data.Wretry;
-      g('Wreboot').value = data.Wreboot;
+		    g('Wretry').value = data.Wretry;
+		    g('Wreboot').value = data.Wreboot;
         xhttp = null;
         reqFin = true;
        }
@@ -62,7 +62,7 @@
    }
     return false;
   }
-
+  //transmit();
   function saveConf() {
     g('frmConf').submit();
   }
@@ -77,7 +77,7 @@
 </head>
 <body onload='init()'>
 <div id='main'>
-<div id='head'>WiFiThermostat - WTherm-3598
+<div id='head'>WiFiThermostat - WTherm-T5WZ
 </div><hr>
 <div></div>
 <b>Configuration - Device & WiFi</b>
@@ -90,29 +90,31 @@
 <p class='n'>if blank, random hostname will be generated</p>
 </fieldset>
 <br>
-<p class='n'>After boot, firmware will first try to connect to Wifi-1, then WiFi-2. <br>
-If both fails it switches to AP-Mode. <br>
-AP-Mode is disabled again after configured timeout and a reconnect is tried <br>
-every some minutes as configured below. If no connection can be established <br>
-the module will reboot after another timeout (if configured). <br>
-Reconnect always tries Wifi-1 first, then WiFi-2 (also if connection is lost).</p>
+<p class='n'>After boot, firmware will first try to connect to Main Wifi-AP, <br>
+then if unsuccessful Fallback-AP. <br>
+If both fails it switches to Configuration AP-Mode. <br>
+AP-Mode is disabled again after configured timeout and a reconnect to the set <br>
+APs is tried every some minutes as configured below. <br>
+If no connection can be established the module will reboot after another <br>
+timeout if configured. <br>
+Reconnect always tries Main-AP first, then Fallback-AP.</p>
 <div></div>
 <fieldset>
-<legend>WiFi-1</legend>
+<legend>WiFi - Default-AP</legend>
 <p><b>Set</b>&nbsp;<input type='checkbox' id='WPW1Set' name='WPW1Set'></p>
 <p><b>SSID</b><br><input type='text' length=32 name='SSID1' id='SSID1'></p>
 <p><b>Password</b>&nbsp;<input type='checkbox' onclick='sp("WPW1")'>&nbsp;show<br><input type='password' length=64 name='WPW1' id='WPW1'></p>
 </fieldset>
 <br>
 <fieldset>
-<legend>WiFi-2</legend>
+<legend>WiFi - Fallback-AP</legend>
 <p><b>Set</b>&nbsp;<input type='checkbox' id='WPW2Set' name='WPW2Set'></p>
 <p><b>SSID</b><br><input type='text' length=32 name='SSID2' id='SSID2'></p>
 <p><b>Password</b>&nbsp;&nbsp;<input type='checkbox' onclick='sp("WPW2")'>&nbsp;show<br><input type='password' length=64 name='WPW2' id='WPW2'></p>
 </fieldset>
 <div></div><br>
 <fieldset>
-<legend>AP Mode</legend>
+<legend>Configuration AP-Mode</legend>
 <p><b>SSID</b>: <i><span id='SSIDAP'></span></i>
 <p><b>Password *</b><input type='checkbox' id='WPWAPSet' name='WPWAPSet' onclick='sp("WPWAP")'><br><input type='password' name='WPWAP' id='WPWAP'></p>
 <p class='n'>* min. 8 chars, empty password for open WiFi</p>
@@ -137,5 +139,5 @@ Reconnect always tries Wifi-1 first, then WiFi-2 (also if connection is lost).</
 </tr></table>
 </div>
 
-<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.5.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.6.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
 </div></body></html>

+ 99 - 0
src/webinterface/conflog

@@ -0,0 +1,99 @@
+<html><head>
+<meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>
+<link rel='stylesheet' href='style.css'>
+<title>WiFiThermostat - WTherm-T5WZ</title>
+<script>
+  function g(i) { return document.getElementById(i) };
+  function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
+  var xhttp, reqTime, reqFin;
+  function selectElement(el, val) {    
+    el.value = val;
+}
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 1000;
+    xhttp.overrideMimeType('application/json');
+    xhttp.open('POST', 'confDataLog');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+		selectElement(g('logLevSer'), data.logLevSer);
+		selectElement(g('logLevWeb'), data.logLevWeb);
+		selectElement(g('logLevMqtt'), data.logLevMqtt);
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  function saveConf() {
+    g('frmConf').submit();
+  }
+  function init() {
+    transmit();
+  }
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+</head>
+<body onload='init()'>
+<div id='main'>
+<div id='head'>WiFiThermostat - WTherm-T5WZ
+</div><hr>
+<div></div>
+<p><b>Configuration - Logging</b></p>
+<div class='config'>
+<form id='frmConf' action='setConfLog' method='POST'>
+<fieldset>
+<legend>Log Levels</legend>
+<p><b>Serial</b><br>
+<select name='logLevSer' id='logLevSer'>
+  <option value="0">Off</option>
+  <option value="1">Error</option>
+  <option value="2">Warn</option>
+  <option value="3">Info</option>
+  <option value="4">Debug</option>
+  <option value="5">Verbose</option>
+</select></p>
+<p><b>Web-Console</b><br>
+<select name='logLevWeb' id='logLevWeb'>
+  <option value="0">Off</option>
+  <option value="1">Error</option>
+  <option value="2">Warn</option>
+  <option value="3">Info</option>
+  <option value="4">Debug</option>
+  <option value="5">Verbose</option>
+</select></p>
+<p><b>MQTT</b><br>
+<select name='logLevMqtt' id='logLevMqtt'>
+  <option value="0">Off</option>
+  <option value="1">Error</option>
+  <option value="2">Warn</option>
+  <option value="3">Info</option>
+  <option value="4">Debug</option>
+  <option value="5">Verbose</option>
+</select></p>
+<br>
+</fieldset>
+</form>
+<div></div>
+<table style='width:100%'>
+<td style='width:50%'><button onclick='location="conf";' class='bgrey'>Cancel</button></td>
+<td style='width:50%'><button onclick='return saveConf()' class='bred'>Save</button></td>
+</tr></table>
+<div></div>
+</div>
+
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.6.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
+</div></body></html>

+ 6 - 6
src/webinterface/confmqtt

@@ -1,7 +1,7 @@
 <html><head>
 <meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>
 <link rel='stylesheet' href='style.css'>
-<title>WiFiThermostat - WTherm-3598</title>
+<title>WiFiThermostat - WTherm-T5WZ</title>
 <script>
   function g(i) { return document.getElementById(i) };
   function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
@@ -24,9 +24,9 @@
       el.style.visibility = 'hidden';
     }
   }
-
+  
   function transmit(f) {
-    if (!xhttp) {
+    if (!xhttp) {   
     reqTime = 0;
     reqFin = false;
     updCbxVal(g('mqttEnable'));
@@ -74,7 +74,7 @@
    }
     return false;
   }
-
+  //transmit();
   function saveConf() {
     updCbxVal(g('mqttEnable'));
     updCbxVal(g('outRet'));
@@ -92,7 +92,7 @@
 </head>
 <body onload='init()'>
 <div id='main'>
-<div id='head'>WiFiThermostat - WTherm-3598
+<div id='head'>WiFiThermostat - WTherm-T5WZ
 </div><hr>
 <div></div>
 <b>Configuration - MQTT</b>
@@ -152,5 +152,5 @@ Just set them to 0 to disable.</p>
 </tr></table>
 </div>
 
-<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.5.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.6.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
 </div></body></html>

+ 98 - 0
src/webinterface/conftime

@@ -0,0 +1,98 @@
+<html><head>
+<meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>
+<link rel='stylesheet' href='style.css'>
+<title>WiFiThermostat - WTherm-T5WZ</title>
+<script>
+  function g(i) { return document.getElementById(i) };
+  function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
+  var xhttp, reqTime, reqFin;
+  function setCbx(el, da) {
+    if(da == '1') {
+      el.checked = true;
+      el.style.visibility = 'visible';
+    }
+    else {
+      el.checked = false;
+      el.style.visibility = 'visible';
+    }
+  }
+  function updCbxVal(el) {
+    if (el.checked) el.value = '1';
+    else {
+      el.checked = true;
+    el.value = '0';
+      el.style.visibility = 'hidden';
+    }
+  }
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+    updCbxVal(g('NTPEnable'));
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 1000;
+    xhttp.overrideMimeType('application/json');
+    xhttp.open('POST', 'confDataTime');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+        setCbx(g('NTPEnable'), data.NTPEnable);
+        g('NTPServer1').value = data.NTPServer1;
+        g('NTPServer2').value = data.NTPServer2;
+        g('TZStr').value = data.TZStr;
+        g('NTPSyncInt').value = data.NTPSyncInt;
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  //transmit();
+  function saveConf() {
+    updCbxVal(g('NTPEnable'));
+    g('frmConf').submit();
+  }
+  function init() {
+    transmit();
+  }
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+</head>
+<body onload='init()'>
+<div id='main'>
+<div id='head'>WiFiThermostat - WTherm-T5WZ
+</div><hr>
+<div></div>
+<b>Configuration - Date &amp; Time</b>
+<div class='config'>
+<form id='frmConf' action='setConfTime' method='POST'>
+<br>
+<fieldset>
+<legend>NTP-Server</legend>
+<p><b>Enable NTP Time Sync</b>&nbsp;<input type='checkbox' name='NTPEnable' id='NTPEnable'></p>
+<p><b>NTP Server 1</b><br><input type='text' name='NTPServer1' id='NTPServer1'></p>
+<p><b>NTP Server 2</b><br><input type='text' name='NTPServer2' id='NTPServer2'></p>
+<p><b>Timezone String</b><br><input type='text' name='TZStr' id='TZStr'></p>
+<p class='n'>a valid TZ string (right column) from here: <br><a href='https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv'>zones.csv</a></p>
+<p><b>NTP Sync Interval [s]</b><br><input type='text' name='NTPSyncInt' id='NTPSyncInt'></p>
+</fieldset>
+<div></div><br>
+</form>
+<div></div>
+<table style='width:100%'>
+<td style='width:50%'><button onclick='location="conf";' class='bgrey'>Cancel</button></td>
+<td style='width:50%'><button onclick='return saveConf()' class='bred'>Save</button></td>
+</tr></table>
+</div>
+
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.6.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
+</div></body></html>

+ 17 - 9
src/webinterface/confweb

@@ -1,7 +1,7 @@
 <html><head>
 <meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>
 <link rel='stylesheet' href='style.css'>
-<title>WiFiThermostat - WTherm-3598</title>
+<title>WiFiThermostat - WTherm-T5WZ</title>
 <script>
   function g(i) { return document.getElementById(i) };
   function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
@@ -24,12 +24,13 @@
       el.style.visibility = 'hidden';
     }
   }
-
+  
   function transmit(f) {
-    if (!xhttp) {
+    if (!xhttp) {   
     reqTime = 0;
     reqFin = false;
     updCbxVal(g('httpAuth'));
+    updCbxVal(g('enableConsole'));
     xhttp = new XMLHttpRequest();
     xhttp.timeout = 1000;
     xhttp.overrideMimeType('application/json');
@@ -46,6 +47,7 @@
         g('httpP1').value = data.httpP1;
         g('httpU2').value = data.httpU2;
         g('httpP2').value = data.httpP2;
+        setCbx(g('enableConsole'), data.enableConsole);
         xhttp = null;
         reqFin = true;
        }
@@ -59,10 +61,11 @@
    }
     return false;
   }
-
+  //transmit();
   function saveConf() {
     updCbxVal(g('httpAuth'));
-    if(g('httpPA').value != g('httpPAC').value) {
+    updCbxVal(g('enableConsole'));
+    if(g('httpPASet').checked && g('httpPA').value != g('httpPAC').value) {
       alert("Admin password verification failed!");
     }
     else g('frmConf').submit();
@@ -78,7 +81,7 @@
 </head>
 <body onload='init()'>
 <div id='main'>
-<div id='head'>WiFiThermostat - WTherm-3598
+<div id='head'>WiFiThermostat - WTherm-T5WZ
 </div><hr>
 <div></div>
 <p><b>Configuration - Web</b></p>
@@ -95,13 +98,13 @@
 <p><b>Username *</b><br><input type='text' name='httpUA' id='httpUA'></p>
 <p><b>Password *</b>&nbsp;<input type='checkbox' onclick='sp("httpPA")'>&nbsp;show<br><input type='password' name='httpPA' id='httpPA'></p>
 <p><b>Confirm Password *</b><br><input type='password' name='httpPAC' id='httpPAC'></p>
-<p style='font-weight:normal;font-size:0.8em;'>Admin Password can only be set but is not displayed for security reasons.</p>
+<p class='n'>Admin Password can only be set but is not displayed for security reasons.</p>
 </fieldset>
 <br>
 <fieldset>
 <legend>Users</legend>
 <p><b>Enable User-Authentication *</b>&nbsp;<input type='checkbox' name='httpAuth' id='httpAuth'></p>
-<p style='font-weight:normal;font-size:0.8em;'>If User-Auth is off and Admin-PW is set, <br>
+<p class='n'>If User-Auth is off and Admin-PW is set, <br>
 Web-Interface can only be accessed by Admin.<br>
 Enable User-Auth and set User 1 with emtpy username and password to <br>
 make usaccessible without Auth.</p>
@@ -114,6 +117,11 @@ make usaccessible without Auth.</p>
 <p><b>User 2 *</b><br><input type='text' name='httpU2' id='httpU2'></p>
 <p><b>User 2 Password *</b>&nbsp;<input type='checkbox' onclick='sp("httpP2")'>&nbsp;show<br><input type='password' name='httpP2' id='httpP2'></p>
 </fieldset>
+<br>
+<fieldset>
+<legend>Console</legend>
+<p><b>Enable Web-Console (Experimental)</b>&nbsp;<input type='checkbox' name='enableConsole' id='enableConsole'></p>
+</fieldset>
 </form>
 <div></div>
 <table style='width:100%'>
@@ -123,5 +131,5 @@ make usaccessible without Auth.</p>
 <div></div>
 </div>
 
-<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.5.0a&nbsp;<span style='color:red;font-weight:bold;'>DEBUG</span> by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.6.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
 </div></body></html>

+ 22 - 0
src/webinterface/console

@@ -0,0 +1,22 @@
+<html><head>
+<meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>
+<link rel='stylesheet' href='style.css'>
+<title>WiFiThermostat - WTherm-T5WZ</title></head>
+<body>
+<div id='main'>
+<div id='head'>WiFiThermostat - WTherm-T5WZ
+</div><hr>
+<div></div>
+<p><b>Console</b></p>
+<div id="status">Connecting...</div>
+<table style='width:100%'>
+<tr><td style='width:800px'><textarea id='msgs'></textarea></td></tr>
+<tr><td style='width:100%'><input id='cmd' type='text'></td></tr>
+<tr><td style='width:100%'><button type='submit' id='btnSend' onclick='sendCmd();'>Send</button></td></tr>
+<tr><td style='width:100%'><button type='button' id='btnClose' onclick='togConn();'>Disconnect</button></td></tr>
+<tr><td style='width:100%'><form action='.' method='get'><button class='bgrey'>Main Menu</button></form></td></tr>
+</table>
+<script src="wsapp.js"></script>
+
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.6.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
+</div></body></html>

+ 46 - 30
src/webinterface/index

@@ -15,7 +15,7 @@
     else return transmit(form);
   }
   function transmit(f) {
-    if (!xhttp) {
+    if (!xhttp) { 
       reqTime = 0;
       reqFin = false;
       xhttp = new XMLHttpRequest();
@@ -32,34 +32,47 @@
           if(data.psetName0 !== undefined) g('btnPs0').innerHTML = data.psetName0;
           if(data.psetName1 !== undefined) g('btnPs1').innerHTML = data.psetName1;
           if(data.psetName2 !== undefined) g('btnPs2').innerHTML = data.psetName2;
-
-
-
-
-      if(data.temp !== undefined && data.hum !== undefined) g('currTempHum').innerHTML = data.temp.toFixed(1) + '&deg;&nbsp;&nbsp;&nbsp;' + data.hum.toFixed(0) + '%';
-      else if(data.temp !== undefined) g('currTempHum').innerHTML = data.temp.toFixed(1) + '&deg;';
+          
+          if(data.temp !== undefined && data.hum !== undefined) g('currTempHum').innerHTML = data.temp.toFixed(1) + '&deg;&nbsp;&nbsp;&nbsp;' + data.hum.toFixed(0) + '%';
+          else if(data.temp !== undefined) g('currTempHum').innerHTML = data.temp.toFixed(1) + '&deg;';
+          
+          if(data.outTemp !== undefined && data.outHum !== undefined) g('outTempHum').innerHTML = data.outTemp.toFixed(1) + '&deg;&nbsp;&nbsp;&nbsp;' + data.outHum.toFixed(0) + '%';
+          else if(data.outTemp !== undefined) g('outTempHum').innerHTML = data.outTemp.toFixed(1) + '&deg;';
 
           if(data.modeName !== undefined) g('mode').innerHTML = data.modeName.toUpperCase();
+          
           if(data.mode == '1') { g('btnModeOn').setAttribute('class', ''); g('btnModeOff').setAttribute('class', 'bgrey');}
           else { g('btnModeOn').setAttribute('class', 'bgrey'); g('btnModeOff').setAttribute('class', '');}
+          
           if(data.pset == '0') { g('btnPs0').setAttribute('class', ''); g('btnPs1').setAttribute('class', 'bgrey'); g('btnPs2').setAttribute('class', 'bgrey');}
           else if(data.pset == '1') {g('btnPs0').setAttribute('class', 'bgrey'); g('btnPs1').setAttribute('class', ''); g('btnPs2').setAttribute('class', 'bgrey');}
           else if(data.pset == '2') {g('btnPs0').setAttribute('class', 'bgrey'); g('btnPs1').setAttribute('class', 'bgrey'); g('btnPs2').setAttribute('class', ''); }
-
+          
           if(data.heating == '1' && data.mode == '1') { g('heating').innerHTML = 'HEATING'; g('heating').style = 'color:red;'; }
           else if(data.heating == '0' && data.mode == '1') { g('heating').innerHTML = 'IDLE'; g('heating').style = 'color:#1fa3ec;'; }
           else if(data.mode == '0') { g('heating').innerHTML = 'OFF'; g('heating').style = 'color:black;'; }
-
-
-          if(data.ssid !== undefined && data.WiFiNum !== undefined && data.WiFiNum > 0) g('ssid').innerHTML = data.ssid + ' (WiFi-' + data.WiFiNum + ')';
+          
+          var APname;
+          if(data.WiFiNum == 1) APname='Default';
+          else if(data.WiFiNum == 1) APname='Fallback';
+          
+          if(data.ssid !== undefined && data.WiFiNum !== undefined && data.WiFiNum > 0) g('ssid').innerHTML = data.ssid + ' (' + APname + '-AP)';
           else if(data.ssid !== undefined) g('ssid').innerHTML = data.ssid;
 
-          if(data.mqttstate !== undefined) {
+          if(data.mqttstate !== undefined) { 
             if(data.mqttstate == "CONNECTED") { g('mqttstate').innerHTML = data.mqttstate + ' to <i>' + data.mqtthost + '</i>'; }
             else g('mqttstate').innerHTML = data.mqttstate;
           }
           if(data.uptime !== undefined) g('uptime').innerHTML = data.uptime;
+          
+          if(data.date !== undefined) g('date').innerHTML = data.date;
+          else g('date').innerHTML = 'NTP disabled';
+          
+          if(data.time !== undefined) g('time').innerHTML = data.time;
+          else g('time').innerHTML = 'NTP disabled';
+          
           if(data.mqttreconn !== undefined) g('mqttreconn').innerHTML = data.mqttreconn;
+          
           xhttp = null;
           updateTime = 0;
           reqFin = true;
@@ -92,13 +105,12 @@
 <td style='width:50%'><form id='BtnFrmMinus'><input type='hidden' name='BtnMinus' value='1'><button onclick='return sendBtn("Minus")'>-</button></form></td>
 <td style='width:50%'><form id='BtnFrmPlus'><input type='hidden' name='BtnPlus' value='1'><button onclick='return sendBtn("Plus")'>+</button></form></td>
 </tr></table>
-<table style='width:100%'><tr>
-<td style='width:50%'>Current Temp/Hum </td><td><b><span id='currTempHum'></span></td>
-</tr></tr>
-<td>Actual set Temp </td><td><b><span id='currSetTemp'></span></b></td>
-</tr></tr>
-<td>Heating State </td><td><b><span id='heating'></span></b></td>
-</tr></table>
+<table style='width:100%'>
+<tr><td style='width:50%'>Current Temp/Hum </td><td><b><span id='currTempHum'></span></td></tr>
+<tr><td>Outside Temp/Hum </td><td><b><span id='outTempHum'></span></td></tr>
+<tr><td>Actual set Temp </td><td><b><span id='currSetTemp'></span></b></td></tr>
+<tr><td>Heating State </td><td><b><span id='heating'></span></b></td></tr>
+</table>
 <div></div>
 <div></div>
 <p><b>Preset</b></p>
@@ -122,16 +134,20 @@
 <hr>
 <div></div>
 <p><b>System</b></p>
-<p>WiFi connected to <i><span id='ssid'></span></i></p>
-<p>MQTT <span id='mqttstate'></span></p>
-<p>MQTT reconnects: <span id='mqttreconn'></span></p>
-<p>Uptime: <span id='uptime'></span></p>
-<table style='width:100%'><tr>
-<td style='width:50%'>
-<form action='conf' method='get'><button class='bgrey'>Config</button></form>
-</td>
-<td style='width:50%'><form action='/' method='get' onsubmit='return confirm("Confirm Restart");'><button name='restart' class='bred'>Restart</button></form></td>
-</tr></table>
+<table style='width:100%'>
+<tr><td colspan=3>WiFi connected to <i><span id='ssid'></span></i></tr></td>
+<tr><td colspan=3>MQTT <span id='mqttstate'></span></tr></td>
+<tr><td colspan=3>MQTT reconnects: <span id='mqttreconn'></span></tr></td>
+<tr><td>Uptime</td><td><span id='uptime'></span></td></tr>
+<tr><td>Date</td><td><span id='date'></span></td></tr>
+<tr><td>Time</td><td><span id='time'></span></td></tr>
+</table>
+<br>
+<table style='width:100%'>
+<tr><td style='width:100%'><form action='conf' method='get'><button>Configuration</button></form></td></tr>
+<tr><td style='width:100%'><form action='console' method='get'><button>Console</button></form></td></tr>
+<tr><td style='width:100%'><form action='/' method='get' onsubmit='return confirm("Confirm Restart");'><button name='restart' class='bred'>Restart</button></form></td></tr>
+</table>
 
-<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.5.0&nbsp;<span style='color:red;font-weight:bold;'>DEBUG</span> by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.6.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
 </div></body></html>

+ 2 - 2
src/webinterface/redTemps

@@ -15,7 +15,7 @@
     else return transmit(form);
   }
   function transmit(f) {
-    if (!xhttp) {
+    if (!xhttp) { 
       reqTime = 0;
       reqFin = false;
       xhttp = new XMLHttpRequest();
@@ -75,5 +75,5 @@
 </tr></table>
 
 
-<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.5.0&nbsp;<span style='color:red;font-weight:bold;'>DEBUG</span> by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/><a href='https://git.flokra.at/flo/WiFiThermostat' target='_blank' style='color:#AAA;'>WiFiThermostat</a> v0.6.0 by <a href='https://www.flokra.at/' target='_blank' style='color:#AAA;'>FloKra</a></div>
 </div></body></html>

+ 48 - 0
src/webinterface/wsapp.js

@@ -0,0 +1,48 @@
+var form = document.getElementById('form-msg');
+var cmd = document.getElementById('cmd');
+var listMsgs = document.getElementById('msgs');
+var socketStatus = document.getElementById('status');
+var btnClose = document.getElementById('btnClose');
+
+var wsUrl = 'ws://' + window.location.hostname + ':81';
+var socket = new WebSocket(wsUrl);
+socket.onopen = function(event) {
+  //socketStatus.innerHTML = 'Connected to: ' + event.currentTarget.URL;
+  socketStatus.innerHTML = 'Connected';
+  socketStatus.className = 'open';
+};
+
+socket.onerror = function(error) {
+  console.log('WebSocket error: ' + error);
+};
+
+socket.onmessage = function(event) {
+  var msg = event.data;
+  listMsgs.innerHTML += msg;
+  listMsgs.scrollTop = listMsgs.scrollHeight;
+};
+
+socket.onclose = function(event) {
+  socketStatus.innerHTML = 'Disconnected';
+  socketStatus.className = 'closed';
+};
+
+function sendCmd() {
+    socket.send(cmd.value);
+    listMsgs.innerHTML += 'Sent: ' + cmd.value + '\n';
+    cmd.value = '';
+    return false;
+}
+
+function togConn() {
+	if(socketStatus.className=='open') { socket.close(); btnClose.innerHTML='Reload'; }
+	else location.reload();
+}
+
+document.getElementById("cmd")
+    .addEventListener("keyup", function(event) {
+    event.preventDefault();
+    if (event.keyCode === 13) {
+        document.getElementById("btnSend").click();
+    }
+});