├── .gitignore ├── CHANGES.txt ├── LICENSE ├── README.md ├── data └── src │ ├── ESP8266Wemos-Vis.html │ └── ESPWroom32-Vis.html ├── docs ├── gauge.png └── scope.png ├── examples ├── ControlAssist-AdcMonitor │ ├── ControlAssist-AdcMonitor.ino │ └── adcMonitorPMem.h ├── ControlAssist-BrowserLog │ ├── ControlAssist-BrowserLog.ino │ └── remoteLogViewer.h ├── ControlAssist-ConnectionCtrl │ └── ControlAssist-ConnectionCtrl.ino ├── ControlAssist-ESPVisualizer │ ├── ControlAssist-ESPVisualizer.ino │ ├── gpioPMemESP32.h │ └── gpioPMemESP8266.h ├── ControlAssist-FadeLed │ └── ControlAssist-FadeLed.ino ├── ControlAssist-Gauge │ ├── ControlAssist-Gauge.ino │ └── gaugePMem.h ├── ControlAssist-Joystick │ ├── ControlAssist-Joystick.ino │ └── joystickPMem.h ├── ControlAssist-Minimal │ └── ControlAssist-Minimal.ino ├── ControlAssist-Scope │ ├── ControlAssist-Scope.ino │ └── scopePMem.h ├── ControlAssist-Simple │ └── ControlAssist-Simple.ino └── ControlAssist-ToggleLed │ └── ControlAssist-ToggleLed.ino ├── keywords.txt ├── library,json ├── library.properties └── src ├── ControlAssist.cpp ├── ControlAssist.h ├── controlAssistPMem.h └── espLogger.h /.gitignore: -------------------------------------------------------------------------------- 1 | /test/ 2 | /debug.cfg 3 | /debug_custom.json 4 | /esp32.svd 5 | .pio/ 6 | .vscode/ 7 | platformio.ini -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | 1.1.6 (2025-01-27) 2 | * Set default AutoSendOnCon to true 3 | 4 | 1.1.5 (2024-12-24) 5 | * Use littleFS 6 | * Added log file in BrowserLog example 7 | 8 | 1.1.4 (2024-07-24) 9 | * Added sendSystemMsg to send connection close messages 10 | 11 | 1.1.3 (2024-05-27) 12 | * Removed webserver, added gthHtmlChunk for webserver handlers 13 | 14 | 1.1.2 (2024-05-17) 15 | * Removed sstrem, Fixed errors 16 | 17 | 1.1.1 (2024-01-19) 18 | * Fixed streamFile 19 | 20 | 1.1.0 (2024-01-09) 21 | * Added websockets status text on main page 22 | 23 | 1.0.9 (2023-11-28) 24 | * Added Joystick example. 25 | 26 | 1.0.8 (2023-11-22) 27 | * Load html code from spiffs or progmem. 28 | 29 | 1.0.7 (2023-11-11) 30 | * Fixed errors, ESPVisualizer example added. 31 | 32 | 1.0.6 (2023-11-06) 33 | * Update values on all connected clients. 34 | 35 | 1.0.5 (2023-11-04) 36 | * Auto send keys on ws connections. Added Adc monitor example. 37 | 38 | 1.0.4 (2023-11-01) 39 | * Added Browser log example. 40 | 41 | 1.0.2 (2023-10-30) 42 | * Fixed errors 43 | 44 | 1.0.1 (2023-10-28) 45 | * Fixed errors, Added gauge example 46 | 47 | 1.0.0 (2023-10-25) 48 | * Created ControlAssist library 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 gemi254 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ControlAssist 2 | Multi-platform library for controlling html elements in a esp webpages at runtime using web sockets. 3 | 4 | ## Description 5 | A library allowing linking **html elements** to **sketch variables** on pages hosted on **esp32/esp8266** devices. It uses a **web socket server** on the esp **device** and a JavaScript **web socket client** implementation on the web **page** allowing bi-directional real-time communication between **device** and **page**. 6 | 7 | In a typical webpage, html **elements** like ``input box``, ``textbox``, ``range``, ``checkbox`` can be **binded** with ControlAssist internal variables using their unique **html ids** in order to associate their values. A **vectors** list will be generated to hold all associated element keys and their values. 8 | 9 | ## How it works 10 | Every time an element is changing its value in the **web page**, a web socket message will be send to server and ControlAssist will update its internal value. Also if you change a ControlAssist value inside your sketch, a message will be automatically send to client and the value of the associated html element will be updated. 11 | 12 | ControlAssist will automatically add JavaScript **onChange** handlers to the web page html code, so the binded elements will transmit their changes automatically to the server. It will also add JavaScript code to handle **incoming** web sockets messages so the values of the binded html elements can be updated. 13 | 14 | ESP device will transmit **changes** to all connected **web socket** clients. This will make possible to have one ESP device and multiple Web pages opened from different places inside the network, receiving and sending changes. 15 | 16 | ## Features 17 | * Automate **variables** and html **elements** in a typical ``ESP32/ESP8266`` project using web sockets communication. 18 | * Automatic reconnects on wifi disconnections. 19 | * Auto **synchronize** ESP32/ESP8266 internal **variables** with webpage elements. 20 | * Automatically generate required **webpage scripts** to handle connections and changes. 21 | * Support bi-directional hi-speed communications. 22 | * Allow **mult-client** applications. (One esp, multiple pages) 23 | * Support web sockets over **AP** connections. 24 | 25 | 26 |

27 | 28 |

29 | 30 | ## How to use 31 | Define yours internal page html code sections at Program mem. 32 | 33 | ``` 34 | PROGMEM const char HTML_HEADERS[] = R"=====()====="; 35 | PROGMEM const char HTML_BODY[] = R"=====()====="; 36 | PROGMEM const char HTML_FOOTER[] = R"=====()====="; 37 | ``` 38 | You can also upload page html code sections to spiffs as a html files 39 | 40 | ## ControlAssist init functions 41 | Define and initialize you class 42 | + include the **ControlAssist** class 43 | - `#include //ControlAssist class` 44 | 45 | + Define your static instance 46 | - `ControlAssist ctrl; //Default port 81 ` 47 | - `ControlAssist ctrl(port); //Use port ` 48 | 49 | + in your setup you must initialize control class by setting your webpage html code. 50 | - `ctrl.setHtmlHeaders(HTML_HEADERS);` 51 | - `ctrl.setHtmlBody(HTML_BODY);` 52 | - `ctrl.setHtmlFooter(HTML_SCRIPT);` 53 | 54 | + You can also use spiffs for the html code. Upload the files contained in the /data folder to your spiffs and define the file names. 55 | - `#define HTML_HEADERS_FILENAME "/src/ESPVisualizer/ESP8266Wemos-VisH.html"` 56 | - `#define HTML_BODY_FILENAME "/src/ESPVisualizer/ESP8266Wemos-VisB.html"` 57 | - `#define HTML_SCRIPT_FILENAME "/src/ESPVisualizer/ESP8266Wemos-VisF.html"` 58 | 59 | + Set the files to be loaded when the page is requested. 60 | - `ctrl.setHtmlHeadersFile(HTML_HEADERS_FILENAME);` 61 | - `ctrl.setHtmlBodyFile(HTML_BODY_FILENAME);` 62 | - `ctrl.setHtmlFooterFile(HTML_SCRIPT_FILENAME);` 63 | 64 | See example ControlAssist-ESPVisualizer 65 | 66 | + in your setup you must bind the html elements you want to control. 67 | - `ctrl.bind("html_id");` to link the html element 68 | - `ctrl.bind("html_id", start_value );` if you need to bind and init for sending on connection 69 | - `ctrl.bind("html_id", start_value, changeFunction);` if you need also to handle changes 70 | 71 | + in your setup specify if you want to auto send key initial values during web socket connection. 72 | - `ctrl.setAutoSendOnCon("html_id",true /* send: enable/disable */);` 73 | - `ctrl.put("html_id", value); // Set a default value to be send` 74 | 75 | + Configure web server to handle control assist page on a uri 76 | - ``` // Setup webserver 77 | server.on("/", []() { 78 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 79 | String res = ""; 80 | res.reserve(CTRLASSIST_STREAM_CHUNKSIZE); 81 | while( ctrl.getHtmlChunk(res)){ 82 | server.sendContent(res); 83 | } 84 | }); 85 | ``` 86 | 87 | + If you want to use a global callback function to handle key changes 88 | - `ctrl.setGlobalCallback(globalChangeFuncion);` 89 | 90 | 91 | + Start websockets server and listen for web socket client connections 92 | - `ctrl.begin();` 93 | 94 | 95 | ## ControlAssist control functions 96 | Controlling your elements inside you loop function 97 | 98 | + Change the values of html elements. 99 | 100 | - `ctrl.put("html_id", value, /* forceSend: send even no change */, /* forceAdd: add key if not exists */ );` 101 | 102 | + Read current value of html element 103 | - `html_val = ctrl["html_id"]` 104 | 105 | + Handle changes inside your code with a handler function 106 | - `void globalChangeFuncion(uint8_t ndx){ String key = ctrl[ndx].key; int val = ctrl[ndx].val.toInt() }` 107 | 108 | + Inside your main loop() call ControlAssist loop() to handle web sockets server clients 109 | - `ctrl.loop();` 110 | 111 | ##### Note that when a html control is "binded" (linked to some backend variable), the library checks whether the new value in ctrl.put differs from the previous send or initial value. If there’s no change, ControlAssist by default does not resend the same value to conserve bandwidth. You can use ``forceSend`` in ctrl.put to send even if there is no change. This optimization is particularly important in applications where frequent updates could lead to performance bottlenecks or excessive network usage (e.g., sliders, real-time monitoring systems, or IoT dashboards). 112 | 113 | 114 | ## JavaScript handlers inside your webpage 115 | A JavaScript event ``wsChange`` will be automatically send to each html element when the esp device changes it's value. You can add a JavaScript event listener on this event at your web page. So it will be possible to perform custom tasks when elements value is updated by web sockets. 116 | 117 | ``` 118 | html_id.addEventListener("wsChange", (event) => { 119 | //Get the changed value 120 | value = event.target.value 121 | event.preventDefault(); 122 | return false; 123 | }); 124 | ``` 125 | See example ``ControlAssist-Gauge.ino`` 126 | 127 |

128 | 129 |

130 | 131 | ## Logging and debugging with log level 132 | In you application you use **LOG_E**, **LOG_W**, **LOG_I**, **LOG_D** macros instead of **Serial.prinf** to print your messages. **ControlAssist** displays these messages with **timestamps** 133 | 134 | You can define log level for each module 135 | ``` 136 | #define LOGGER_LOG_LEVEL 4 137 | or 138 | build_flags = -DLOGGER_LOG_LEVEL=5 139 | ``` 140 | ``` 141 | #define _LOG_LEVEL_NONE (0) 142 | #define _LOG_LEVEL_ERROR (1) 143 | #define _LOG_LEVEL_WARN (2) 144 | #define _LOG_LEVEL_INFO (3) 145 | #define _LOG_LEVEL_DEBUG (4) 146 | #define _LOG_LEVEL_VERBOSE (5) 147 | ``` 148 | 149 | ## Compile 150 | Download library files and place them on ./libraries directory under ArduinoProjects 151 | Then include the **ControlAssist.h** in your application and compile.. 152 | 153 | + Compile for arduino-esp32 or arduino-esp8266. 154 | + In order to compile you must install **WebSocketsServer** library. 155 | + Use compiler flags build_flags = -DCA_USE_LITTLEFS to use LITTLEFS instead of SPIFFS 156 | 157 | 158 | ###### If you get compilation errors on arduino-esp32 you need to update your arduino-esp32 library in the IDE using Boards Manager 159 | 160 | -------------------------------------------------------------------------------- /docs/gauge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gemi254/ControlAssist-ESP32-ESP8266/f53a4ec26cb3fc1ba58f20c48dd843e2dbff5554/docs/gauge.png -------------------------------------------------------------------------------- /docs/scope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gemi254/ControlAssist-ESP32-ESP8266/f53a4ec26cb3fc1ba58f20c48dd843e2dbff5554/docs/scope.png -------------------------------------------------------------------------------- /examples/ControlAssist-AdcMonitor/ControlAssist-AdcMonitor.ino: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) 2 | #include 3 | #include 4 | #define WEB_SERVER WebServer 5 | #else 6 | #include 7 | #include 8 | #define WEB_SERVER ESP8266WebServer 9 | #endif 10 | 11 | #define LOGGER_LOG_LEVEL 5 // Define log level for this module 12 | #include // Control assist class 13 | 14 | #include "adcMonitorPmem.h" // Html page Program mem 15 | 16 | const char st_ssid[]=""; // Put connection SSID here. On empty an AP will be started 17 | const char st_pass[]=""; // Put your wifi passowrd. 18 | unsigned long pingMillis = millis(); // Ping millis 19 | 20 | unsigned long speed = 40; 21 | bool isPlaying = false; 22 | #ifdef ESP32 23 | // ADC channel 1 24 | // ADC channel 2 canot be used while WiFi is on, pins 27,26,25 15,14,13,12,3,2,0 25 | int adcPins[] ={ 39, 38, 37, 36, 35, 34, 33, 32}; 26 | #else 27 | int adcPins[] ={ 0 }; 28 | #endif 29 | 30 | ControlAssist ctrl; // Control assist class 31 | WEB_SERVER server(80); // Web server on port 80 32 | 33 | static int ctrlsNdx[ sizeof(adcPins) / sizeof(int) ]; // Array of control indexes 34 | 35 | // Init adc ports 36 | void initAdcadcPins(){ 37 | for (byte i = 0; i < sizeof(adcPins) / sizeof(int); i++) { 38 | LOG_I("Init pin no: %i\n", adcPins[i]); 39 | pinMode(adcPins[i], INPUT); 40 | ctrl.bind( ("adc_" + String(adcPins[i])).c_str() ); 41 | } 42 | // Get positions of the variables 43 | for (byte i = 0; i < sizeof(adcPins) / sizeof(int); i ++) { 44 | ctrlsNdx[i] = ctrl.getKeyNdx(("adc_" + String(adcPins[i])).c_str()); 45 | } 46 | } 47 | // Read adc ports 48 | void readAdcadcPins(){ 49 | for (byte i = 0; i < sizeof(adcPins) / sizeof(int); i++) { 50 | uint16_t v = analogRead( adcPins[i]); 51 | ctrl.set(ctrlsNdx[i], v); 52 | LOG_N("Read pin no: %i = %u\n", adcPins[i], v); 53 | } 54 | } 55 | // Change handler 56 | void speedChange(){ 57 | LOG_V("speedChange %s\n", ctrl["speed"].c_str()); 58 | speed = ctrl["speed"].toInt(); 59 | } 60 | // Change handler 61 | void changeOnOff(){ 62 | LOG_V("changeOnOff %li\n", ctrl["on-off"].toInt()); 63 | isPlaying = ctrl["on-off"].toInt(); 64 | } 65 | 66 | void setup() { 67 | Serial.begin(115200); 68 | Serial.print("\n\n\n\n"); 69 | Serial.flush(); 70 | LOG_I("Starting..\n"); 71 | 72 | // Connect WIFI ? 73 | if(strlen(st_ssid)>0){ 74 | LOG_E("Connect Wifi to %s.\n", st_ssid); 75 | WiFi.mode(WIFI_STA); 76 | WiFi.begin(st_ssid, st_pass); 77 | uint32_t startAttemptTime = millis(); 78 | while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) { 79 | Serial.print("."); 80 | delay(500); 81 | Serial.flush(); 82 | } 83 | Serial.println(); 84 | } 85 | 86 | // Check connection 87 | if(WiFi.status() == WL_CONNECTED ){ 88 | LOG_I("Wifi AP SSID: %s connected, use 'http://%s' to connect\n", st_ssid, WiFi.localIP().toString().c_str()); 89 | }else{ 90 | LOG_E("Connect failed.\n"); 91 | LOG_I("Starting AP.\n"); 92 | String mac = WiFi.macAddress(); 93 | mac.replace(":",""); 94 | String hostName = "ControlAssist_" + mac.substring(6); 95 | WiFi.mode(WIFI_AP); 96 | WiFi.softAP(hostName.c_str(),"",1); 97 | LOG_I("Wifi AP SSID: %s started, use 'http://%s' to connect\n", WiFi.softAPSSID().c_str(), WiFi.softAPIP().toString().c_str()); 98 | if (MDNS.begin(hostName.c_str())) LOG_V("AP MDNS responder Started\n"); 99 | } 100 | 101 | // Generate body html and JavaScripts 102 | ctrl.setHtmlHeaders(CONTROLASSIST_HTML_HEADER); 103 | static String htmlBody(CONTROLASSIST_HTML_BODY); 104 | String barsScripts=""; 105 | for (byte i = 0; i < sizeof(adcPins) / sizeof(int); i = i + 1) { 106 | String bar(CONTROLASSIST_HTML_BAR); 107 | bar.replace("{key}", String(adcPins[i])); 108 | htmlBody += bar; 109 | 110 | String barScript(CONTROLASSIST_HTML_BAR_SCRIPT); 111 | barScript.replace("{key}", String(adcPins[i])); 112 | barsScripts += barScript; 113 | } 114 | 115 | // Setup control assist 116 | ctrl.setHtmlBody(htmlBody.c_str()); 117 | static String htmlFooter(CONTROLASSIST_HTML_FOOTER); 118 | htmlFooter.replace("/*{SUB_SCRIPT}*/",barsScripts); 119 | ctrl.setHtmlFooter(htmlFooter.c_str()); 120 | 121 | // Bind controls 122 | ctrl.bind("on-off", isPlaying, changeOnOff); 123 | ctrl.bind("speed", speed, speedChange); 124 | // Handle web server root request and send html to client 125 | // Auto send all previous vars on ws connection 126 | ctrl.setAutoSendOnCon(true); 127 | // Init and bind all pins 128 | initAdcadcPins(); 129 | 130 | // Start web sockets 131 | ctrl.begin(); 132 | LOG_I("ControlAssist started.\n"); 133 | 134 | // Add a web server handler on url "/" 135 | server.on("/", []() { 136 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 137 | String res = ""; 138 | res.reserve(CTRLASSIST_STREAM_CHUNKSIZE); 139 | while( ctrl.getHtmlChunk(res)){ 140 | server.sendContent(res); 141 | } 142 | }); 143 | // Dump binded controls handler 144 | server.on("/d", []() { 145 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 146 | server.sendContent("Serial dump\n"); 147 | String res = ""; 148 | while( ctrl.dump(res) ){ 149 | server.sendContent(res); 150 | } 151 | }); 152 | // Start webserver 153 | server.begin(); 154 | LOG_I("HTTP server started\n"); 155 | } 156 | 157 | void loop() { 158 | if (millis() - pingMillis >= speed){ 159 | if(isPlaying) readAdcadcPins(); 160 | pingMillis = millis(); 161 | } 162 | 163 | #if not defined(ESP32) 164 | if(MDNS.isRunning()) MDNS.update(); //Handle MDNS 165 | #endif 166 | // Handler webserver clients 167 | server.handleClient(); 168 | // Handle websockets 169 | ctrl.loop(); 170 | } 171 | 172 | -------------------------------------------------------------------------------- /examples/ControlAssist-AdcMonitor/adcMonitorPMem.h: -------------------------------------------------------------------------------- 1 | PROGMEM const char CONTROLASSIST_HTML_HEADER[] = R"=====( 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ControlAssist 10 | 77 | )====="; 78 | 79 | PROGMEM const char CONTROLASSIST_HTML_BODY[] = R"=====( 80 | 81 | 82 |

Monitor all ESP adc pins

83 |
84 |
85 | 86 | 87 | 88 | 89 | 90 |
91 |
92 |
)====="; 93 | 94 | PROGMEM const char CONTROLASSIST_HTML_BAR[] = R"=====( 95 |
96 |
97 |
98 | 0 99 |
100 |
101 |
102 |
)====="; 103 | 104 | PROGMEM const char CONTROLASSIST_HTML_BAR_SCRIPT[] = R"=====( 105 | var adc_{key} = new bar('adc_{key}', 0, 4096, '') 106 | adc_{key}.ctrl.addEventListener("wsChange", (event) => { 107 | event.target.class.update(Math.ceil(event.target.value * gain)) 108 | event.preventDefault(); 109 | return false; 110 | }); 111 | )====="; 112 | 113 | PROGMEM const char CONTROLASSIST_HTML_FOOTER[] = R"=====( 114 |
115 | 116 | 195 | 196 | )====="; 197 | -------------------------------------------------------------------------------- /examples/ControlAssist-BrowserLog/ControlAssist-BrowserLog.ino: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) 2 | #include 3 | #include 4 | #define WEB_SERVER WebServer 5 | #else 6 | #include 7 | #include 8 | #define WEB_SERVER ESP8266WebServer 9 | #endif 10 | 11 | const char st_ssid[]="mdk3"; // Put connection SSID here. On empty an AP will be started 12 | const char st_pass[]="2843028858"; // Put your wifi passowrd. 13 | unsigned long pingMillis = millis(); // Ping millis 14 | 15 | #define LOGGER_LOG_MODE 3 // Set default logging mode using external function 16 | #define LOGGER_LOG_LEVEL 5 // Define log level for this module 17 | static void _log_printf(const char *format, ...); // Custom log function, defined in weblogger.h 18 | 19 | bool logToFile = true; // Set to false to disable log file 20 | 21 | #include // Control assist class 22 | #include "remoteLogViewer.h" // Web based remote log page using web sockets 23 | 24 | WEB_SERVER server(80); // Web server on port 80 25 | 26 | RemoteLogViewer remoteLogView(85); // The remote live log viewer page 27 | 28 | uint32_t loopNo = 0; 29 | 30 | void debugMemory(const char* caller) { 31 | #if defined(ESP32) 32 | LOG_D("%s %i> Free: heap %u, block: %u, pSRAM %u\n", caller, ++loopNo, ESP.getFreeHeap(), heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL), ESP.getFreePsram()); 33 | #else 34 | LOG_D("%s %i > Free: heap %u\n", caller, ++loopNo, ESP.getFreeHeap()); 35 | #endif 36 | } 37 | 38 | void setup() { 39 | Serial.begin(115200); 40 | Serial.print("\n\n\n\n"); 41 | Serial.flush(); 42 | 43 | if(logToFile){ 44 | if (!STORAGE.begin()) { 45 | Serial.println("An Error has occurred while mounting SPIFFS"); 46 | return; 47 | }else{ 48 | Serial.println("Storage statred."); 49 | } 50 | LOGGER_OPEN_LOG(); 51 | } 52 | 53 | // Setup the remote web debugger in order to store log lines, url "/log" 54 | // When no connection is present store log lines in a buffer until connection 55 | remoteLogView.init(); 56 | 57 | LOG_I("Starting..\n"); 58 | // Connect WIFI ? 59 | if(strlen(st_ssid)>0){ 60 | LOG_D("Connect Wifi to %s.\n", st_ssid); 61 | WiFi.mode(WIFI_STA); 62 | WiFi.begin(st_ssid, st_pass); 63 | uint32_t startAttemptTime = millis(); 64 | while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) { 65 | Serial.print("."); 66 | delay(500); 67 | Serial.flush(); 68 | } 69 | Serial.println(); 70 | } 71 | 72 | // Check connection 73 | if(WiFi.status() == WL_CONNECTED ){ 74 | LOG_I("Wifi AP SSID: %s connected, use 'http://%s' to connect\n", st_ssid, WiFi.localIP().toString().c_str()); 75 | }else{ 76 | LOG_E("Connect failed.\n"); 77 | LOG_I("Starting AP.\n"); 78 | String mac = WiFi.macAddress(); 79 | mac.replace(":",""); 80 | String hostName = "ControlAssist_" + mac.substring(6); 81 | WiFi.mode(WIFI_AP); 82 | WiFi.softAP(hostName.c_str(),"",1); 83 | LOG_I("Wifi AP SSID: %s started, use 'http://%s' to connect\n", WiFi.softAPSSID().c_str(), WiFi.softAPIP().toString().c_str()); 84 | if (MDNS.begin(hostName.c_str())) LOG_V("AP MDNS responder Started\n"); 85 | } 86 | 87 | 88 | remoteLogView.begin(); 89 | LOG_I("RemoteLogViewer started.\n"); 90 | 91 | // Setup webserver 92 | server.on("/", []() { 93 | server.send(200, "text/html", "

This is root page


View log  View log file  Reset log file"); 94 | }); 95 | 96 | server.on("/log", []() { // Setup log handler 97 | remoteLogView.handleLog(server); 98 | }); 99 | 100 | server.on("/logFile", []() { // Setup log file handler 101 | remoteLogView.handleLogFile(server); 102 | }); 103 | 104 | // Start webserver 105 | server.begin(); 106 | LOG_V("HTTP server started\n"); 107 | } 108 | 109 | void loop() { 110 | if (millis() - pingMillis >= 2000){ 111 | debugMemory("Loop"); 112 | pingMillis = millis(); 113 | } 114 | 115 | #if not defined(ESP32) 116 | if(MDNS.isRunning()) MDNS.update(); // Handle MDNS 117 | #endif 118 | // Handler webserver clients 119 | server.handleClient(); 120 | // Handle websockets 121 | remoteLogView.loop(); 122 | } 123 | 124 | -------------------------------------------------------------------------------- /examples/ControlAssist-BrowserLog/remoteLogViewer.h: -------------------------------------------------------------------------------- 1 | /* A web remote debugger using ControlAssist web sockets */ 2 | #if !defined(_REMOTE_LOG_VIEWER_H) 3 | #define _REMOTE_LOG_VIEWER_H 4 | 5 | PROGMEM const char LOGGER_VIEWER_HTML_BODY[] = R"=====( 6 | 31 | 32 | 33 | 34 | 35 | 36 |
 
Disconnected
37 | 38 | 39 | 40 | 41 | 52 | 53 | 54 | )====="; 55 | 56 | PROGMEM const char LOGGER_VIEWER_HTML_SCRIPT[] = R"=====( 57 | 104 | 105 | )====="; 106 | 107 | #include 108 | // Log print arguments 109 | #define MAX_LOG_BUFFER_LEN 8192 // Maximum size of log buffer to store when disconnected 110 | #define MAX_LOG_FMT 256 // Maximum size of log format 111 | static char fmtBuf[MAX_LOG_FMT]; // Format buffer 112 | static char outBuf[512]; // Output buffer 113 | static va_list arglist; 114 | static File log_file; 115 | // Logger file open/close 116 | // Must called before LOG_I function to write to file and 117 | // closed on application shutdown 118 | #define LOGGER_OPEN_LOG() if(!log_file) log_file = STORAGE.open(LOGGER_LOG_FILENAME, "a+") 119 | #define LOGGER_CLOSE_LOG() if(log_file) log_file.close() 120 | static class RemoteLogViewer *_pLogView = NULL; // Pointer to instance of log print viewer class 121 | void wsStatusChange(); // Handles connect / disconnect 122 | 123 | // Remote log print viewer class 124 | class RemoteLogViewer: public ControlAssist{ 125 | public: 126 | RemoteLogViewer() { 127 | _logBuffer = ""; 128 | } 129 | RemoteLogViewer(uint16_t port) { ControlAssist::setPort(port); _logBuffer = ""; } 130 | virtual ~RemoteLogViewer() { } 131 | public: 132 | void init(){ 133 | // Must not initalized 134 | if(_pLogView){ 135 | LOG_E("Already initialized with addr: %p\n", _pLogView); 136 | return; 137 | } 138 | // Store this instance pointer to global 139 | _pLogView = this; 140 | // Setup control assist 141 | ControlAssist::setHtmlBody(LOGGER_VIEWER_HTML_BODY); 142 | ControlAssist::setHtmlFooter(LOGGER_VIEWER_HTML_SCRIPT); 143 | ControlAssist::bind("wsStatus","Disconnected", wsStatusChange); 144 | ControlAssist::bind("logLine"); 145 | } 146 | // Delete the log file 147 | void resetLogFile(){ 148 | if(!STORAGE.exists(LOGGER_LOG_FILENAME)) return; 149 | LOG_W("Reseting log\n"); 150 | LOGGER_CLOSE_LOG(); 151 | STORAGE.remove(LOGGER_LOG_FILENAME); 152 | // Reopen log file to store log lines 153 | LOGGER_OPEN_LOG(); 154 | } 155 | 156 | // Send server responce on /log 157 | void handleLog(WEB_SERVER &server){ 158 | String res = ""; 159 | while( getHtmlChunk(res) ){ 160 | server.sendContent(res); 161 | } 162 | server.sendContent(""); 163 | } 164 | // Reset or display log file 165 | void handleLogFile(WEB_SERVER &server){ 166 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 167 | if(server.hasArg("reset")){ 168 | resetLogFile(); 169 | server.sendContent("Reseted log"); 170 | return; 171 | } 172 | if(!STORAGE.exists(LOGGER_LOG_FILENAME)){ 173 | server.sendContent("Logging file not exists"); 174 | return; 175 | } 176 | LOGGER_CLOSE_LOG(); 177 | // Open for reading 178 | File file = STORAGE.open(LOGGER_LOG_FILENAME, "r"); 179 | // Send log file contents 180 | String res = ""; 181 | while( file.available() ){ 182 | res = file.readStringUntil('\n') + "\n"; 183 | server.sendContent(res); 184 | } 185 | file.close(); 186 | server.sendContent(""); 187 | // Reopen log file to store log lines 188 | LOGGER_OPEN_LOG(); 189 | } 190 | void sendBuffer(){ 191 | if(_logBuffer == "") return; 192 | String logLine = ""; 193 | int l = 0; 194 | // Send log buffer lines 195 | while(_logBuffer.length() > 0){ 196 | l = _logBuffer.indexOf("\n", 0); 197 | if(l < 0) break; 198 | logLine = _logBuffer.substring(0, l ); 199 | ControlAssist::put("logLine", logLine ); 200 | _logBuffer = _logBuffer.substring(l + 1 , _logBuffer.length() ); 201 | } 202 | } 203 | 204 | // Custom log print function 205 | void print(String line){ 206 | // Are clients connected? 207 | if(ControlAssist::getClientsNum()>0){ 208 | if(_logBuffer != ""){ 209 | sendBuffer(); 210 | _logBuffer = ""; 211 | } 212 | // Send current line 213 | ControlAssist::put("logLine", line, true); 214 | }else{ // No clients store line to _logBuffer 215 | // Contains \n by default 216 | _logBuffer += String(line); 217 | } 218 | // Truncate buffer on oversize 219 | if(_logBuffer.length() > MAX_LOG_BUFFER_LEN){ 220 | int l = _logBuffer.indexOf("\n", 0); 221 | if(l >= 0) 222 | _logBuffer = _logBuffer.substring(l + 1, _logBuffer.length()); 223 | } 224 | } 225 | public: 226 | String _logBuffer; // String buffer to hold log lines 227 | }; 228 | 229 | // Custom log print function.. Prints on _pLogView instance 230 | void _log_printf(const char *format, ...){ 231 | strncpy(fmtBuf, format, MAX_LOG_FMT); 232 | fmtBuf[MAX_LOG_FMT - 1] = 0; 233 | va_start(arglist, format); 234 | vsnprintf(outBuf, MAX_LOG_FMT, fmtBuf, arglist); 235 | va_end(arglist); 236 | Serial.print(outBuf); 237 | if(_pLogView) _pLogView->print(outBuf); 238 | if(log_file){ 239 | log_file.print(outBuf); 240 | log_file.flush(); 241 | } 242 | } 243 | // Handle websocket connections 244 | void wsStatusChange(){ 245 | if(!_pLogView) return; 246 | String wsStatus = _pLogView->getVal("wsStatus"); 247 | if(wsStatus.startsWith("Connected:")) 248 | _pLogView->sendBuffer(); 249 | } 250 | 251 | #endif -------------------------------------------------------------------------------- /examples/ControlAssist-ConnectionCtrl/ControlAssist-ConnectionCtrl.ino: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) 2 | #include 3 | #include 4 | #define WEB_SERVER WebServer 5 | #define ADC_PIN 36 6 | #else 7 | #include 8 | #include 9 | #define WEB_SERVER ESP8266WebServer 10 | #define ADC_PIN A0 11 | #endif 12 | 13 | #define LOGGER_LOG_LEVEL 5 // Define log level for this module 14 | #include // Control assist class 15 | 16 | const char st_ssid[]=""; // Put connection SSID here. On empty an AP will be started 17 | const char st_pass[]=""; // Put your wifi passowrd. 18 | unsigned long pingMillis = millis(); // Ping millis 19 | unsigned long disconnMillis = millis(); // Wifi disconnect millis 20 | 21 | ControlAssist ctrl; // Control assist class 22 | WEB_SERVER server(80); // Web server on port 80 23 | 24 | time_t disconTime = 10 * 1000; // Wifi disconnection duration 25 | 26 | PROGMEM const char HTML_BODY[] = R"=====( 27 | 44 | 45 |
46 |
47 |

ControlAssist Websockets connection

48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
MacAddressAA:DD:CC:EE:FF:KK
Wifi RSSI
Disconnect 62 |  WiFi for 63 |  Seconds 64 |
 
 
Disconnected
72 |
73 |
74 | 75 | 76 | 87 | )====="; 88 | 89 | // Disconnect WiFi 90 | void disconnect(){ 91 | LOG_D("Disconnect WiFi for %s seconds\n", String(disconTime / 1000L).c_str()); 92 | ctrl.put("wifi_rssi", -120 ); 93 | ctrl.loop(); 94 | ctrl.sendSystemMsg("C"); 95 | time_t s = millis(); 96 | while(millis() - s < 100 ) 97 | ctrl.loop(); 98 | WiFi.disconnect(); 99 | disconnMillis = millis(); 100 | } 101 | 102 | // Change handler to handle websockets changes 103 | void changeHandler(uint8_t ndx){ 104 | String key = ctrl[ndx].key; 105 | if(key == "sleep_time" ) 106 | disconTime = ctrl[key].toInt() * 1000; 107 | else if(key == "disconnect_button") 108 | disconnect(); 109 | 110 | LOG_D("changeHandler: ndx: %02i, key: %s = %s\n",ndx, key.c_str(), ctrl[key].c_str()); 111 | } 112 | // Connect WiFi 113 | void connect(){ 114 | if(WiFi.status() == WL_CONNECTED ) return; 115 | // Connect WIFI ? 116 | if(strlen(st_ssid)>0){ 117 | LOG_E("Connect Wifi to %s.\n", st_ssid); 118 | WiFi.mode(WIFI_STA); 119 | WiFi.begin(st_ssid, st_pass); 120 | uint32_t startAttemptTime = millis(); 121 | while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) { 122 | Serial.print("."); 123 | delay(500); 124 | Serial.flush(); 125 | } 126 | Serial.println(); 127 | } 128 | 129 | // Check connection 130 | if(WiFi.status() == WL_CONNECTED ){ 131 | LOG_I("Wifi AP SSID: %s connected, use 'http://%s' to connect\n", st_ssid, WiFi.localIP().toString().c_str()); 132 | }else{ 133 | LOG_E("Connect failed.\n"); 134 | LOG_I("Starting AP.\n"); 135 | String mac = WiFi.macAddress(); 136 | mac.replace(":",""); 137 | String hostName = "ControlAssist_" + mac.substring(6); 138 | WiFi.mode(WIFI_AP); 139 | WiFi.softAP(hostName.c_str(),"",1); 140 | LOG_I("Wifi AP SSID: %s started, use 'http://%s' to connect\n", WiFi.softAPSSID().c_str(), WiFi.softAPIP().toString().c_str()); 141 | if (MDNS.begin(hostName.c_str())) LOG_V("AP MDNS responder Started\n"); 142 | } 143 | } 144 | 145 | void setup() { 146 | Serial.begin(115200); 147 | Serial.print("\n\n\n\n"); 148 | Serial.flush(); 149 | LOG_I("Starting..\n"); 150 | // Connect WiFi 151 | connect(); 152 | 153 | // Control assist setup 154 | ctrl.setHtmlBody(HTML_BODY); 155 | ctrl.bind("mac_address", WiFi.macAddress().c_str() ); 156 | ctrl.bind("wifi_rssi"); 157 | ctrl.bind("sleep_time", disconTime / 1000); 158 | ctrl.bind("disconnect_button"); 159 | ctrl.setAutoSendOnCon(true); 160 | // Every time a variable changed changeHandler will be called 161 | ctrl.setGlobalCallback(changeHandler); 162 | 163 | // Start web sockets 164 | ctrl.begin(); 165 | //ctrl.put("sleep_time", disconTime / 1000,true); 166 | LOG_V("ControlAssist started.\n"); 167 | 168 | // Setup webserver 169 | server.on("/", []() { 170 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 171 | String res = ""; 172 | res.reserve(CTRLASSIST_STREAM_CHUNKSIZE); 173 | while( ctrl.getHtmlChunk(res)){ 174 | server.sendContent(res); 175 | } 176 | }); 177 | 178 | // Start webs server 179 | server.begin(); 180 | LOG_V("HTTP server started\n"); 181 | } 182 | 183 | void loop() { 184 | // Change html control values 185 | if (millis() - pingMillis >= 3000){ 186 | // Update control assist variables 187 | ctrl.put("wifi_rssi", WiFi.RSSI() ); 188 | 189 | pingMillis = millis(); 190 | } 191 | // Change html control values 192 | if ( WiFi.status() != WL_CONNECTED && millis() - disconnMillis >= disconTime){ 193 | // Re connect 194 | connect(); 195 | disconnMillis = millis(); 196 | } 197 | #if not defined(ESP32) 198 | if(MDNS.isRunning()) MDNS.update(); // Handle MDNS 199 | #endif 200 | // Handler webserver clients 201 | server.handleClient(); 202 | // Handle websockets 203 | ctrl.loop(); 204 | } -------------------------------------------------------------------------------- /examples/ControlAssist-ESPVisualizer/ControlAssist-ESPVisualizer.ino: -------------------------------------------------------------------------------- 1 | 2 | #if defined(ESP32) 3 | #include 4 | #include 5 | #define WEB_SERVER WebServer 6 | #else 7 | #include 8 | #include 9 | #include 10 | #define WEB_SERVER ESP8266WebServer 11 | #endif 12 | 13 | // Setting to true will need to upload contents of /data 14 | // directory to esp SPIFFS using image upload 15 | #define USE_STORAGE_FOR_PAGES false 16 | 17 | #if defined(ESP32) 18 | #if USE_STORAGE_FOR_PAGES 19 | #define BODY_FILE_NAME "/src/ESPWroom32-Vis.html" 20 | #else 21 | #include "gpioPMemESP32.h" 22 | #endif 23 | #define TOTAL_PINS 40 24 | #else 25 | #if USE_STORAGE_FOR_PAGES 26 | #define BODY_FILE_NAME "/src/ESP8266Wemos-Vis.html" 27 | #else 28 | #include "gpioPMemESP8266.h" 29 | //#define BODY_FILE_NAME "/src/test.html" 30 | #endif 31 | #define TOTAL_PINS 17 32 | #endif 33 | 34 | #define LOGGER_LOG_LEVEL 5 35 | #include // Control assist class 36 | 37 | WEB_SERVER server(80); 38 | ControlAssist ctrl(81); // Web socket control on port 81 39 | 40 | const char st_ssid[]=""; // Put connection SSID here. On empty an AP will be started 41 | const char st_pass[]=""; // Put your wifi passowrd. 42 | unsigned long pingMillis = millis(); // Ping millis 43 | 44 | #ifndef LED_BUILTIN 45 | #define LED_BUILTIN 22 // Define the pin tha the led is connected 46 | #endif 47 | 48 | // Toggle led on/off and send pin update 49 | void setLed(bool ledState){ 50 | if( ledState ){ 51 | digitalWrite(LED_BUILTIN, LOW); // Turn LED ON 52 | LOG_D("Led on\n"); 53 | }else{ 54 | digitalWrite(LED_BUILTIN, HIGH); // Turn LED OFF 55 | LOG_D("Led off\n"); 56 | } 57 | } 58 | 59 | // A key is changed by web sockets 60 | void globalChangeHandler(uint8_t ndx){ 61 | LOG_N("globalChangeHandler ndx: %02u, key: '%s', val: %s\n", ndx, ctrl[ndx].key.c_str(), ctrl[ndx].val.c_str()); 62 | if(ctrl[ndx].key == "led"){ // Led clicked in web page 63 | // Incoming message, Toggle led and dont send update 64 | setLed(ctrl[ndx].val.toInt()); 65 | LOG_I("Setting led to %i\n",(int)ctrl[ndx].val.toInt()); 66 | // Set the connected led pin 67 | String ledKey = String(LED_BUILTIN); 68 | if(LED_BUILTIN<10) ledKey = "0" + ledKey; 69 | ctrl.put(ledKey.c_str(), !ctrl[ndx].val.toInt() ); 70 | }else{ // Pin clciked in web page 71 | int pin = ctrl[ndx].key.toInt(); 72 | LOG_I("Change pin GPIO%i: %s\n", pin, ctrl[ndx].val.c_str()); 73 | // Set selected pin 74 | pinMode(pin, OUTPUT); 75 | digitalWrite(pin, ctrl[ndx].val.toInt()); 76 | // On led toggle connected pin 77 | if(pin == LED_BUILTIN) ctrl.put("led", !ctrl[ndx].val.toInt() ); 78 | } 79 | } 80 | #if defined(ESP32) 81 | // Read a gpio pin state on ESP32 82 | int readGPIO(int pinNo ) { //0-31 83 | gpio_num_t pin = (gpio_num_t)(pinNo & 0x1F); 84 | int state = 0; 85 | if (GPIO_REG_READ(GPIO_ENABLE_REG) & BIT(pin)){ //pin is output - read the GPIO_OUT_REG register 86 | state = (GPIO_REG_READ(GPIO_OUT_REG) >> pin) & 1U; 87 | }else{ //pin is input - read the GPIO_IN_REG registe 88 | state = (GPIO_REG_READ(GPIO_IN_REG) >> pin) & 1U; 89 | } 90 | return state; 91 | } 92 | #else 93 | // Read a gpio pin on ESP8266 94 | // Independent of the setting of port direction (input or output) 95 | int readGPIO(int pinNo ) { //0-17 96 | return digitalRead(pinNo); 97 | } 98 | #endif 99 | 100 | // Read the state of all available pins 101 | void readAllGpio(){ 102 | for(uint i=0; i0){ 147 | LOG_D("Connect Wifi to %s.\n", st_ssid); 148 | WiFi.mode(WIFI_STA); 149 | WiFi.begin(st_ssid, st_pass); 150 | uint32_t startAttemptTime = millis(); 151 | while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) { 152 | Serial.print("."); 153 | delay(500); 154 | Serial.flush(); 155 | } 156 | Serial.println(); 157 | } 158 | 159 | // Check connection 160 | if(WiFi.status() == WL_CONNECTED ){ 161 | LOG_I("Wifi AP SSID: %s connected, use 'http://%s' to connect\n", st_ssid, WiFi.localIP().toString().c_str()); 162 | }else{ 163 | LOG_E("Connect failed.\n"); 164 | LOG_I("Starting AP.\n"); 165 | String mac = WiFi.macAddress(); 166 | mac.replace(":",""); 167 | String hostName = "ControlAssist_" + mac.substring(6); 168 | WiFi.mode(WIFI_AP); 169 | WiFi.softAP(hostName.c_str(),"",1); 170 | LOG_I("Wifi AP SSID: %s started, use 'http://%s' to connect\n", WiFi.softAPSSID().c_str(), WiFi.softAPIP().toString().c_str()); 171 | if (MDNS.begin(hostName.c_str())) LOG_I("AP MDNS responder Started\n"); 172 | } 173 | 174 | // Use default CONTROLASSIST_HTML_HEADER and CONTROLASSIST_HTML_FOOTER 175 | #if USE_STORAGE_FOR_PAGES 176 | ctrl.setHtmlBodyFile(BODY_FILE_NAME); 177 | #else 178 | ctrl.setHtmlBody(HTML_PAGE); 179 | #endif 180 | // Bind led 181 | pinMode(LED_BUILTIN, OUTPUT); 182 | digitalWrite(LED_BUILTIN, HIGH); // Turn LED OFF 183 | ctrl.bind("led", readGPIO(LED_BUILTIN)); 184 | 185 | // Bind all pins 186 | for(int i=0; i= 5000){ 229 | // Toggle led 230 | setLed(!ctrl["led"].toInt()); 231 | // Update config assist variable and send ws message 232 | ctrl.put("led", !ctrl["led"].toInt() ); 233 | // Read the state of all gpio 234 | readAllGpio(); 235 | pingMillis = millis(); 236 | } 237 | 238 | #if not defined(ESP32) 239 | if(MDNS.isRunning()) MDNS.update(); // Handle MDNS 240 | #endif 241 | // Handler webserver clients 242 | server.handleClient(); 243 | 244 | // Handle websockets 245 | ctrl.loop(); 246 | } 247 | 248 | -------------------------------------------------------------------------------- /examples/ControlAssist-FadeLed/ControlAssist-FadeLed.ino: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) 2 | #include 3 | #include 4 | #define WEB_SERVER WebServer 5 | #else 6 | #include 7 | #include 8 | #define WEB_SERVER ESP8266WebServer 9 | #endif 10 | 11 | #define LOGGER_LOG_LEVEL 5 // Define log level for this module 12 | #include // Control assist class 13 | 14 | const char st_ssid[]=""; // Put connection SSID here. On empty an AP will be started 15 | const char st_pass[]=""; // Put your wifi passowrd. 16 | unsigned long pingMillis = millis(); // Ping millis 17 | 18 | #ifndef LED_BUILTIN 19 | #define LED_BUILTIN 22 20 | #endif 21 | 22 | int ledLevel = 0; 23 | ControlAssist ctrl; // Control assist class 24 | WEB_SERVER server(80); // Web server on port 80 25 | 26 | 27 | PROGMEM const char HTML_BODY[] = R"=====( 28 | 87 | 88 |
89 |

ControlAssist example

90 |
91 |
92 |
Fade led
93 |
94 |
95 |
96 | 97 |
98 |
99 |
100 | 101 |
102 |
103 |
104 |
105 |
106 |
107 | 108 |
109 | 143 | 144 | 145 | )====="; 146 | // Set led brightness 147 | void lampLevel(){ 148 | ledLevel = ctrl["lampLevel"].toInt(); 149 | LOG_D("lampLevel: %i\n", ledLevel); 150 | analogWrite(LED_BUILTIN, 255 - ledLevel); 151 | } 152 | 153 | void setup() { 154 | Serial.begin(115200); 155 | Serial.print("\n\n\n\n"); 156 | Serial.flush(); 157 | LOG_I("Starting..\n"); 158 | // Connect WIFI ? 159 | if(strlen(st_ssid)>0){ 160 | LOG_E("Connect Wifi to %s.\n", st_ssid); 161 | WiFi.mode(WIFI_STA); 162 | WiFi.begin(st_ssid, st_pass); 163 | uint32_t startAttemptTime = millis(); 164 | while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) { 165 | Serial.print("."); 166 | delay(500); 167 | Serial.flush(); 168 | } 169 | Serial.println(); 170 | } 171 | 172 | // Check connection 173 | if(WiFi.status() == WL_CONNECTED ){ 174 | LOG_I("Wifi AP SSID: %s connected, use 'http://%s' to connect\n", st_ssid, WiFi.localIP().toString().c_str()); 175 | }else{ 176 | LOG_E("Connect failed.\n"); 177 | LOG_I("Starting AP.\n"); 178 | String mac = WiFi.macAddress(); 179 | mac.replace(":",""); 180 | String hostName = "ControlAssist_" + mac.substring(6); 181 | WiFi.mode(WIFI_AP_STA); 182 | WiFi.softAP(hostName.c_str(),"",1); 183 | LOG_I("Wifi AP SSID: %s started, use 'http://%s' to connect\n", WiFi.softAPSSID().c_str(), WiFi.softAPIP().toString().c_str()); 184 | if (MDNS.begin(hostName.c_str())) LOG_V("AP MDNS responder Started\n"); 185 | } 186 | 187 | // Setup control assist 188 | ctrl.setHtmlBody(HTML_BODY); 189 | ctrl.bind("lampLevel", ledLevel, lampLevel); 190 | // Auto send on connect 191 | ctrl.setAutoSendOnCon("lampLevel", true); 192 | // Start web sockets 193 | ctrl.begin(); 194 | LOG_V("ControlAssist started.\n"); 195 | 196 | // Setup webserver 197 | server.on("/", []() { 198 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 199 | String res = ""; 200 | res.reserve(CTRLASSIST_STREAM_CHUNKSIZE); 201 | while( ctrl.getHtmlChunk(res)){ 202 | server.sendContent(res); 203 | } 204 | }); 205 | 206 | // Dump binded controls handler 207 | server.on("/d", []() { 208 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 209 | server.sendContent("Serial dump\n"); 210 | String res = ""; 211 | while( ctrl.dump(res) ){ 212 | server.sendContent(res); 213 | } 214 | }); 215 | 216 | // Setup webserver 217 | server.begin(); 218 | LOG_V("HTTP server started\n"); 219 | 220 | // Setup led 221 | pinMode(LED_BUILTIN, OUTPUT); 222 | analogWrite(LED_BUILTIN, 1024); 223 | LOG_I("Setup Lamp Led for ESP8266 board\n"); 224 | 225 | // Dump binded controls to serial 226 | String res=""; 227 | while(ctrl.dump(res)) Serial.print(res); 228 | } 229 | 230 | void loop() { 231 | #if not defined(ESP32) 232 | if(MDNS.isRunning()) MDNS.update(); // Handle MDNS 233 | #endif 234 | // Handler webserver clients 235 | server.handleClient(); 236 | // Handle websockets 237 | ctrl.loop();; 238 | } 239 | 240 | 241 | -------------------------------------------------------------------------------- /examples/ControlAssist-Gauge/ControlAssist-Gauge.ino: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) 2 | #ifdef __cplusplus 3 | extern "C" { 4 | #endif 5 | uint8_t temprature_sens_read(); 6 | #ifdef __cplusplus 7 | } 8 | #endif 9 | uint8_t temprature_sens_read(); 10 | #include 11 | #include 12 | #define WEB_SERVER WebServer 13 | #else 14 | #include 15 | #include 16 | #define WEB_SERVER ESP8266WebServer 17 | #endif 18 | 19 | 20 | #define LOGGER_LOG_LEVEL 5 // Define log level for this module 21 | #include // Control assist class 22 | 23 | const char st_ssid[]=""; // Put connection SSID here. On empty an AP will be started 24 | const char st_pass[]=""; // Put your wifi passowrd. 25 | unsigned long pingMillis = millis(); // Ping millis 26 | 27 | char chBuff[128]; 28 | static bool buttonState = false; 29 | #define DELAY_MS 1000 // Measurements delay 30 | 31 | WEB_SERVER server(80); // Web server on port 80 32 | ControlAssist ctrl; // Control assist class 33 | 34 | #include "gaugePMem.h" // Program html code definitions 35 | 36 | // Change handler to handle web sockets changes 37 | void changeHandler(uint8_t ndx){ 38 | String key = ctrl[ndx].key; 39 | if(key == "check_ctrl" ) 40 | buttonState = ctrl["check_ctrl"].toInt(); 41 | LOG_D("changeHandler: ndx: %02i, key: %s = %s\n",ndx, key.c_str(), ctrl[key].c_str()); 42 | } 43 | 44 | #if defined(ESP32) 45 | int getMemPerc(){ 46 | uint32_t freeHeapBytes = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); 47 | uint32_t totalHeapBytes = heap_caps_get_total_size(MALLOC_CAP_DEFAULT); 48 | float percentageHeapUsed = 100 - freeHeapBytes * 100.0f / (float)totalHeapBytes; 49 | return round(percentageHeapUsed); 50 | } 51 | #else 52 | int getMemPerc(){ 53 | uint32_t freeHeapBytes = ESP.getFreeHeap(); 54 | uint32_t totalHeapBytes = 96000; //1060000; //ESP.getFlashChipSizeByChipId(); 55 | float percentageHeapUsed = 100 - freeHeapBytes * 100.0f / (float)totalHeapBytes; 56 | return round(percentageHeapUsed); 57 | } 58 | #endif 59 | 60 | void setup() { 61 | Serial.begin(115200); 62 | Serial.print("\n\n\n\n"); 63 | Serial.flush(); 64 | LOG_I("Starting..\n"); 65 | 66 | // Connect WIFI ? 67 | if(strlen(st_ssid)>0){ 68 | LOG_E("Connect Wifi to %s.\n", st_ssid); 69 | WiFi.mode(WIFI_STA); 70 | WiFi.begin(st_ssid, st_pass); 71 | uint32_t startAttemptTime = millis(); 72 | while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) { 73 | Serial.print("."); 74 | delay(500); 75 | Serial.flush(); 76 | } 77 | Serial.println(); 78 | } 79 | 80 | // Check connection 81 | if(WiFi.status() == WL_CONNECTED ){ 82 | LOG_I("Wifi AP SSID: %s connected, use 'http://%s' to connect\n", st_ssid, WiFi.localIP().toString().c_str()); 83 | }else{ 84 | LOG_E("Connect failed.\n"); 85 | LOG_I("Starting AP.\n"); 86 | String mac = WiFi.macAddress(); 87 | mac.replace(":",""); 88 | String hostName = "ControlAssist_" + mac.substring(6); 89 | WiFi.mode(WIFI_AP_STA); 90 | if(WiFi.softAP(hostName.c_str(),"",8)) 91 | LOG_I("Wifi AP SSID: %s started, use 'http://%s' to connect\n", WiFi.softAPSSID().c_str(), WiFi.softAPIP().toString().c_str()); 92 | else 93 | LOG_E("Wifi AP SSID: %s FAILED!\n", WiFi.softAPSSID().c_str()); 94 | if (MDNS.begin(hostName.c_str())) LOG_V("AP MDNS responder Started\n"); 95 | } 96 | 97 | 98 | // Control assist setup 99 | ctrl.setHtmlHeaders(HTML_HEADERS); 100 | ctrl.setHtmlBody(HTML_BODY); 101 | ctrl.bind("rssi"); 102 | ctrl.bind("mem"); 103 | #if defined(ESP32) 104 | ctrl.bind("temp"); 105 | ctrl.bind("hall"); 106 | #else 107 | ctrl.bind("vcc"); 108 | ctrl.bind("hall"); 109 | #endif 110 | // Every time a variable changed changeHandler will be called 111 | ctrl.setGlobalCallback(changeHandler); 112 | // Add a web server handler on url "/" 113 | 114 | ctrl.begin(); 115 | LOG_V("ControlAssist started.\n"); 116 | // Setup webserver 117 | server.on("/", []() { 118 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 119 | String res = ""; 120 | while( ctrl.getHtmlChunk(res)){ 121 | server.sendContent(res); 122 | } 123 | }); 124 | 125 | // Dump binded controls handler 126 | server.on("/d", []() { 127 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 128 | server.sendContent("Serial dump\n"); 129 | String res = ""; 130 | while( ctrl.dump(res) ){ 131 | server.sendContent(res); 132 | } 133 | }); 134 | // Start web server 135 | server.begin(); 136 | LOG_V("HTTP server started\n"); 137 | } 138 | 139 | void loop() { 140 | // Update html control values 141 | if (millis() - pingMillis >= DELAY_MS){ 142 | ctrl.put("rssi", String( WiFi.RSSI() ) ); 143 | ctrl.put("mem", String( getMemPerc() ) ); 144 | #if defined(ESP32) 145 | ctrl.put("temp", String( ((temprature_sens_read() - 32) / 1.8), 1 )); 146 | ctrl.put("hall", String( hallRead() ) ); 147 | #else 148 | ctrl.put("vcc", String( ESP.getVcc() )); 149 | #endif 150 | buttonState = !buttonState; 151 | pingMillis = millis(); 152 | } 153 | 154 | // Handler webserver clients 155 | server.handleClient(); 156 | // Handle websockets 157 | ctrl.loop(); 158 | #if not defined(ESP32) 159 | if(MDNS.isRunning()) MDNS.update(); // Handle MDNS 160 | #endif 161 | } 162 | -------------------------------------------------------------------------------- /examples/ControlAssist-Gauge/gaugePMem.h: -------------------------------------------------------------------------------- 1 | PROGMEM const char HTML_HEADERS[] = R"=====( 2 | 3 | 4 | 5 | 6 | 7 | ESP Monitor 8 | 82 | 83 | )====="; 84 | 85 | #if defined(ESP32) 86 | PROGMEM const char HTML_BODY[] = R"=====( 87 | 88 |

ESP32 system monitor

89 |
90 |
91 |
92 |
93 | 0% 94 | RSSI 95 |
96 |
97 |
98 |
99 | 0% 100 | Memory 101 |
102 |
103 |
104 |
105 | 0% 106 | Temperature 107 |
108 |
109 |
110 |
111 | 0% 112 | HallRead 113 |
114 |
115 | 170 | 171 | )====="; 172 | #else 173 | PROGMEM const char HTML_BODY[] = R"=====( 174 | 175 |

ESP8266 system monitor

176 |
177 |
178 |
179 |
180 | 0% 181 | RSSI 182 |
183 |
184 |
185 |
186 | 0% 187 | Memory 188 |
189 |
190 |
191 |
192 | 0 V 193 | Vcc 194 |
195 |
196 | 245 | 246 | )====="; 247 | #endif 248 | -------------------------------------------------------------------------------- /examples/ControlAssist-Joystick/ControlAssist-Joystick.ino: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) 2 | #include 3 | #include 4 | #define WEB_SERVER WebServer 5 | #else 6 | #include 7 | #include 8 | #define WEB_SERVER ESP8266WebServer 9 | #endif 10 | 11 | #define LOGGER_LOG_LEVEL 5 // Define log level for this module 12 | #include // Control assist class 13 | 14 | const char st_ssid[]=""; // Put connection SSID here. On empty an AP will be started 15 | const char st_pass[]=""; // Put your wifi passowrd. 16 | unsigned long pingMillis = millis(); // Ping millis 17 | 18 | #include "joystickPMem.h" 19 | 20 | ControlAssist ctrl; // Control assist class 21 | WEB_SERVER server(80); // Web server on port 80 22 | 23 | // Change handlers 24 | void xChange(){ 25 | LOG_I("X pos: %lu\n", ctrl["x_coordinate"].toInt()); 26 | } 27 | void yChange(){ 28 | LOG_I("Y pos: %lu\n", ctrl["y_coordinate"].toInt()); 29 | } 30 | void speedChange(){ 31 | LOG_I("Speed: %lu %%\n", ctrl["speed"].toInt()); 32 | } 33 | void angleChange(){ 34 | LOG_I("Angle: %lu °\n", ctrl["angle"].toInt()); 35 | 36 | } 37 | 38 | void setup() { 39 | Serial.begin(115200); 40 | Serial.print("\n\n\n\n"); 41 | Serial.flush(); 42 | LOG_I("Starting..\n"); 43 | 44 | // Connect WIFI ? 45 | if(strlen(st_ssid)>0){ 46 | LOG_E("Connect Wifi to %s.\n", st_ssid); 47 | WiFi.mode(WIFI_STA); 48 | WiFi.begin(st_ssid, st_pass); 49 | uint32_t startAttemptTime = millis(); 50 | while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) { 51 | Serial.print("."); 52 | delay(500); 53 | Serial.flush(); 54 | } 55 | Serial.println(); 56 | } 57 | 58 | // Check connection 59 | if(WiFi.status() == WL_CONNECTED ){ 60 | LOG_I("Wifi AP SSID: %s connected, use 'http://%s' to connect\n", st_ssid, WiFi.localIP().toString().c_str()); 61 | }else{ 62 | LOG_E("Connect failed.\n"); 63 | LOG_I("Starting AP.\n"); 64 | String mac = WiFi.macAddress(); 65 | mac.replace(":",""); 66 | String hostName = "ControlAssist_" + mac.substring(6); 67 | WiFi.mode(WIFI_AP_STA); 68 | WiFi.softAP(hostName.c_str(),"",1); 69 | LOG_I("Wifi AP SSID: %s started, use 'http://%s' to connect\n", WiFi.softAPSSID().c_str(), WiFi.softAPIP().toString().c_str()); 70 | if (MDNS.begin(hostName.c_str())) LOG_V("AP MDNS responder Started\n"); 71 | } 72 | 73 | // Control assist setup 74 | ctrl.setHtmlHeaders(HTML_HEADERS); 75 | ctrl.setHtmlBody(HTML_BODY); 76 | ctrl.setHtmlFooter(HTML_FOOTER); 77 | 78 | // Bind span controls 79 | ctrl.bind("x_coordinate","0", xChange); 80 | ctrl.bind("y_coordinate","0", yChange); 81 | ctrl.bind("speed", speedChange); 82 | ctrl.bind("angle", angleChange); 83 | 84 | // Start web sockets 85 | ctrl.begin(); 86 | LOG_I("ControlAssist started.\n"); 87 | 88 | // Add a web server handler on url "/" 89 | server.on("/", []() { 90 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 91 | String res = ""; 92 | res.reserve(CTRLASSIST_STREAM_CHUNKSIZE); 93 | while( ctrl.getHtmlChunk(res)){ 94 | server.sendContent(res); 95 | } 96 | }); 97 | 98 | // Start web server 99 | server.begin(); 100 | LOG_I("HTTP server started\n"); 101 | } 102 | 103 | void loop() { 104 | #if not defined(ESP32) 105 | if(MDNS.isRunning()) MDNS.update(); // Handle MDNS 106 | #endif 107 | // Handler webserver clients 108 | server.handleClient(); 109 | // Handle websockets 110 | ctrl.loop(); 111 | } -------------------------------------------------------------------------------- /examples/ControlAssist-Joystick/joystickPMem.h: -------------------------------------------------------------------------------- 1 | PROGMEM const char HTML_HEADERS[] = R"=====( 2 | 3 | 4 | 5 | 6 | 7 | ESP joystick 8 | 9 | 24 | )====="; 25 | 26 | PROGMEM const char HTML_BODY[] = R"=====( 27 | 28 |

29 | ESP joystick

30 |

31 | X:   32 | Y:   33 | Speed:   34 | Angle: 35 |

36 | 37 | 187 | 188 | )====="; 189 | 190 | PROGMEM const char HTML_FOOTER[] = R"=====( 191 | 192 | )====="; 193 | -------------------------------------------------------------------------------- /examples/ControlAssist-Minimal/ControlAssist-Minimal.ino: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) 2 | #include 3 | #include 4 | #define WEB_SERVER WebServer 5 | #else 6 | #include 7 | #include 8 | #define WEB_SERVER ESP8266WebServer 9 | #endif 10 | 11 | #define LOGGER_LOG_LEVEL 5 // Define log level for this module 12 | #include // Control assist class 13 | 14 | const char st_ssid[]=""; // Put connection SSID here. On empty an AP will be started 15 | const char st_pass[]=""; // Put your wifi passowrd. 16 | unsigned long pingMillis = millis(); // Ping millis 17 | 18 | ControlAssist ctrl; // Control assist class 19 | WEB_SERVER server(80); // Web server on port 80 20 | 21 | void setup() { 22 | Serial.begin(115200); 23 | Serial.print("\n\n\n\n"); 24 | Serial.flush(); 25 | LOG_I("Starting..\n"); 26 | 27 | // Connect WIFI ? 28 | if(strlen(st_ssid)>0){ 29 | LOG_E("Connect Wifi to %s.\n", st_ssid); 30 | WiFi.mode(WIFI_STA); 31 | WiFi.begin(st_ssid, st_pass); 32 | uint32_t startAttemptTime = millis(); 33 | while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) { 34 | Serial.print("."); 35 | delay(500); 36 | Serial.flush(); 37 | } 38 | Serial.println(); 39 | } 40 | 41 | // Check connection 42 | if(WiFi.status() == WL_CONNECTED ){ 43 | LOG_I("Wifi AP SSID: %s connected, use 'http://%s' to connect\n", st_ssid, WiFi.localIP().toString().c_str()); 44 | }else{ 45 | LOG_E("Connect failed.\n"); 46 | LOG_I("Starting AP.\n"); 47 | String mac = WiFi.macAddress(); 48 | mac.replace(":",""); 49 | String hostName = "ControlAssist_" + mac.substring(6); 50 | WiFi.mode(WIFI_AP_STA); 51 | WiFi.softAP(hostName.c_str(),"",1); 52 | LOG_I("Wifi AP SSID: %s started, use 'http://%s' to connect\n", WiFi.softAPSSID().c_str(), WiFi.softAPIP().toString().c_str()); 53 | if (MDNS.begin(hostName.c_str())) LOG_V("AP MDNS responder Started\n"); 54 | } 55 | 56 | // Start web sockets 57 | ctrl.begin(); 58 | LOG_I("ControlAssist started.\n"); 59 | 60 | // Setup webserver 61 | server.on("/", []() { 62 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 63 | String res = ""; 64 | res.reserve(CTRLASSIST_STREAM_CHUNKSIZE); 65 | while( ctrl.getHtmlChunk(res)){ 66 | server.sendContent(res); 67 | } 68 | }); 69 | // Start web server 70 | server.begin(); 71 | LOG_I("HTTP server started\n"); 72 | 73 | } 74 | 75 | void loop() { 76 | #if not defined(ESP32) 77 | if(MDNS.isRunning()) MDNS.update(); // Handle MDNS 78 | #endif 79 | // Handler webserver clients 80 | server.handleClient(); 81 | // Handle websockets 82 | ctrl.loop(); 83 | } -------------------------------------------------------------------------------- /examples/ControlAssist-Scope/ControlAssist-Scope.ino: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) 2 | #include 3 | #include 4 | #define WEB_SERVER WebServer 5 | #define ADC_PIN 36 6 | #else 7 | #include 8 | #include 9 | #define WEB_SERVER ESP8266WebServer 10 | #define ADC_PIN A0 11 | #endif 12 | 13 | #define LOGGER_LOG_LEVEL 5 // Define log level for this module 14 | #include // Control assist class 15 | 16 | const char st_ssid[]=""; // Put connection SSID here. On empty an AP will be started 17 | const char st_pass[]=""; // Put your wifi passowrd. 18 | unsigned long pingMillis = millis(); // Ping millis 19 | bool isPlaying = false; 20 | unsigned long speed = 40; // Read delay 21 | static int adc_pos = -1; 22 | 23 | #include "scopePMem.h" // Program html code 24 | 25 | ControlAssist ctrl; // Control assist class 26 | WEB_SERVER server(80); // Web server on port 80 27 | 28 | // Web server root handler 29 | void handleRoot(){ 30 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 31 | String res = ""; 32 | res.reserve(CTRLASSIST_STREAM_CHUNKSIZE); 33 | while( ctrl.getHtmlChunk(res)){ 34 | server.sendContent(res); 35 | } 36 | server.sendContent(""); 37 | } 38 | // Change handlers 39 | void changeOnOff(){ 40 | LOG_V("changeOnOff %li\n", ctrl["on-off"].toInt()); 41 | isPlaying = ctrl["on-off"].toInt(); 42 | } 43 | void speedChange(){ 44 | LOG_V("speedChange %s\n", ctrl["speed"].c_str()); 45 | speed = ctrl["speed"].toInt(); 46 | } 47 | 48 | // Setup function 49 | void setup() { 50 | Serial.begin(115200); 51 | Serial.print("\n\n\n\n"); 52 | Serial.flush(); 53 | LOG_I("Starting..\n"); 54 | // Connect WIFI ? 55 | if(strlen(st_ssid)>0){ 56 | LOG_E("Connect Wifi to %s.\n", st_ssid); 57 | WiFi.mode(WIFI_STA); 58 | WiFi.begin(st_ssid, st_pass); 59 | uint32_t startAttemptTime = millis(); 60 | while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) { 61 | Serial.print("."); 62 | delay(500); 63 | Serial.flush(); 64 | } 65 | Serial.println(); 66 | } 67 | 68 | // Check connection 69 | if(WiFi.status() == WL_CONNECTED ){ 70 | LOG_I("Wifi AP SSID: %s connected, use 'http://%s' to connect\n", st_ssid, WiFi.localIP().toString().c_str()); 71 | }else{ 72 | LOG_E("Connect failed.\n"); 73 | LOG_I("Starting AP.\n"); 74 | String mac = WiFi.macAddress(); 75 | mac.replace(":",""); 76 | String hostName = "ControlAssist_" + mac.substring(6); 77 | WiFi.mode(WIFI_AP_STA); 78 | WiFi.softAP(hostName.c_str(),"",1); 79 | LOG_I("Wifi AP SSID: %s started, use 'http://%s' to connect\n", WiFi.softAPSSID().c_str(), WiFi.softAPIP().toString().c_str()); 80 | if (MDNS.begin(hostName.c_str())) LOG_V("AP MDNS responder Started\n"); 81 | } 82 | 83 | // Control assist setup 84 | ctrl.setHtmlHeaders(HTML_HEADERS); 85 | ctrl.setHtmlBody(HTML_BODY); 86 | ctrl.setHtmlFooter(HTML_SCRIPT); 87 | // Bind controls 88 | ctrl.bind("on-off",isPlaying, changeOnOff); 89 | ctrl.bind("speed", speed, speedChange); 90 | // Auto send key values on connection 91 | ctrl.setAutoSendOnCon("on-off",true); 92 | ctrl.setAutoSendOnCon("speed",true); 93 | // Store key position on adc_val for speed 94 | // Only on last bind call the position will be valid! 95 | adc_pos = ctrl.bind("adc_val"); 96 | // Start the server 97 | ctrl.begin(); 98 | LOG_V("ControlAssist started.\n"); 99 | 100 | server.on("/", handleRoot); 101 | // Dump binded controls handler 102 | server.on("/d", []() { 103 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 104 | server.sendContent("Serial dump\n"); 105 | String res = ""; 106 | while( ctrl.dump(res) ){ 107 | server.sendContent(res); 108 | } 109 | }); 110 | 111 | server.begin(); 112 | LOG_V("HTTP server started\n"); 113 | #if defined(ESP32) 114 | pinMode(ADC_PIN, INPUT); 115 | #endif 116 | 117 | // Dump binded controls to serial 118 | String res; 119 | while(ctrl.dump(res)) Serial.print(res); 120 | } 121 | 122 | void loop() { // Run repeatedly 123 | 124 | if (millis() - pingMillis >= speed){ 125 | // Set control at position to value 126 | if(isPlaying) 127 | ctrl.set(adc_pos, analogRead(ADC_PIN), true); 128 | pingMillis = millis(); 129 | } 130 | 131 | #if not defined(ESP32) 132 | if(MDNS.isRunning()) MDNS.update(); // Handle MDNS 133 | #endif 134 | // Handler webserver clients 135 | server.handleClient(); 136 | // Handle websockets 137 | ctrl.loop(); 138 | } 139 | 140 | 141 | -------------------------------------------------------------------------------- /examples/ControlAssist-Scope/scopePMem.h: -------------------------------------------------------------------------------- 1 | PROGMEM const char HTML_HEADERS[] = R"=====( 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ControlAssist 10 | 64 | 65 | )====="; 66 | 67 | PROGMEM const char HTML_BODY[] = R"=====( 68 | 69 |
70 |

ControlAssist Monitor ADC port

71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 |
79 | 80 |
81 |
82 | 83 |
84 |
85 | 86 | )====="; 87 | 88 | PROGMEM const char HTML_SCRIPT[] = R"=====( 89 | 198 | )====="; -------------------------------------------------------------------------------- /examples/ControlAssist-Simple/ControlAssist-Simple.ino: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) 2 | #include 3 | #include 4 | #define WEB_SERVER WebServer 5 | #define ADC_PIN 36 6 | #else 7 | #include 8 | #include 9 | #define WEB_SERVER ESP8266WebServer 10 | #define ADC_PIN A0 11 | #endif 12 | 13 | #define LOGGER_LOG_LEVEL 5 // Define log level for this module 14 | #include // Control assist class 15 | 16 | const char st_ssid[]=""; // Put connection SSID here. On empty an AP will be started 17 | const char st_pass[]=""; // Put your wifi password. 18 | unsigned long pingMillis = millis(); // Ping millis 19 | 20 | char chBuff[128]; 21 | static bool buttonState = false; 22 | 23 | ControlAssist ctrl; // Control assist class 24 | WEB_SERVER server(80); // Web server on port 80 25 | 26 | PROGMEM const char HTML_BODY[] = R"=====( 27 | 28 |

ControlAssist sample page

29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 63 | 64 |
ADC valueSpan control0
Cycle countInput control
MemoryText control
Check FlagCheck control 49 | 50 |
Wifi RSSI Range control
User ButtonButton control 61 | 62 |
65 | 66 | )====="; 67 | 68 | // Change handler to handle websockets changes 69 | void changeHandler(uint8_t ndx){ 70 | String key = ctrl[ndx].key; 71 | if(key == "check_ctrl" ) 72 | buttonState = ctrl["check_ctrl"].toInt(); 73 | LOG_D("changeHandler: ndx: %02i, key: %s = %s\n",ndx, key.c_str(), ctrl[key].c_str()); 74 | } 75 | 76 | void setup() { 77 | Serial.begin(115200); 78 | Serial.print("\n\n\n\n"); 79 | Serial.flush(); 80 | LOG_I("Starting..\n"); 81 | 82 | // Connect WIFI ? 83 | if(strlen(st_ssid)>0){ 84 | LOG_E("Connect Wifi to %s.\n", st_ssid); 85 | WiFi.mode(WIFI_STA); 86 | WiFi.begin(st_ssid, st_pass); 87 | uint32_t startAttemptTime = millis(); 88 | while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) { 89 | Serial.print("."); 90 | delay(500); 91 | Serial.flush(); 92 | } 93 | Serial.println(); 94 | } 95 | 96 | // Check connection 97 | if(WiFi.status() == WL_CONNECTED ){ 98 | LOG_I("Wifi AP SSID: %s connected, use 'http://%s' to connect\n", st_ssid, WiFi.localIP().toString().c_str()); 99 | }else{ 100 | LOG_E("Connect failed.\n"); 101 | LOG_I("Starting AP.\n"); 102 | String mac = WiFi.macAddress(); 103 | mac.replace(":",""); 104 | String hostName = "ControlAssist_" + mac.substring(6); 105 | WiFi.mode(WIFI_AP); 106 | WiFi.softAP(hostName.c_str(),"",1); 107 | LOG_I("Wifi AP SSID: %s started, use 'http://%s' to connect\n", WiFi.softAPSSID().c_str(), WiFi.softAPIP().toString().c_str()); 108 | if (MDNS.begin(hostName.c_str())) LOG_V("AP MDNS responder Started\n"); 109 | } 110 | 111 | 112 | // Control assist setup 113 | ctrl.setHtmlBody(HTML_BODY); 114 | ctrl.bind("span_ctrl"); 115 | ctrl.bind("input_ctrl"); 116 | ctrl.bind("text_ctrl"); 117 | ctrl.bind("check_ctrl"); 118 | ctrl.bind("range_ctrl"); 119 | ctrl.bind("button_ctrl"); 120 | // Every time a variable changed changeHandler will be called 121 | ctrl.setGlobalCallback(changeHandler); 122 | 123 | // Start web sockets 124 | ctrl.begin(); 125 | LOG_V("ControlAssist started.\n"); 126 | // Setup webserver 127 | server.on("/", []() { 128 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 129 | String res = ""; 130 | res.reserve(CTRLASSIST_STREAM_CHUNKSIZE); 131 | while( ctrl.getHtmlChunk(res)){ 132 | server.sendContent(res); 133 | } 134 | }); 135 | 136 | // Dump binded controls handler 137 | server.on("/d", []() { 138 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 139 | server.sendContent("Serial dump\n"); 140 | String res = ""; 141 | while( ctrl.dump(res) ){ 142 | server.sendContent(res); 143 | } 144 | }); 145 | // Start webs server 146 | server.begin(); 147 | LOG_V("HTTP server started\n"); 148 | 149 | pinMode(ADC_PIN, INPUT); 150 | } 151 | 152 | void loop() { 153 | // Change html control values 154 | if (millis() - pingMillis >= 3000){ 155 | // Update control assist variables 156 | ctrl.put("span_ctrl", analogRead(ADC_PIN) ); 157 | ctrl.put("input_ctrl", String(ESP.getCycleCount())); 158 | ctrl.put("text_ctrl", chBuff); 159 | ctrl.put("check_ctrl", buttonState ); 160 | ctrl.put("range_ctrl", WiFi.RSSI() ); 161 | ctrl.put("button_ctrl", buttonState ); 162 | #if defined(ESP32) 163 | //ctrl.put("input_ctrl", String((temprature_sens_read() - 32) / 1.8 ) + " °C"); 164 | sprintf(chBuff, "Memory Free: heap %u, block: %u, pSRAM %u", ESP.getFreeHeap(), heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL), ESP.getFreePsram()); 165 | #else 166 | sprintf(chBuff,"Memory free heap: %u, stack: %u ,block: %u", ESP.getFreeHeap(), ESP.getFreeContStack(), ESP.getMaxFreeBlockSize()); 167 | #endif 168 | buttonState = !buttonState; 169 | pingMillis = millis(); 170 | } 171 | 172 | #if not defined(ESP32) 173 | if(MDNS.isRunning()) MDNS.update(); // Handle MDNS 174 | #endif 175 | // Handler webserver clients 176 | server.handleClient(); 177 | // Handle websockets 178 | ctrl.loop(); 179 | } -------------------------------------------------------------------------------- /examples/ControlAssist-ToggleLed/ControlAssist-ToggleLed.ino: -------------------------------------------------------------------------------- 1 | #if defined(ESP32) 2 | #include 3 | #include 4 | #define WEB_SERVER WebServer 5 | #else 6 | #include 7 | #include 8 | #define WEB_SERVER ESP8266WebServer 9 | #endif 10 | 11 | #define LOGGER_LOG_LEVEL 5 // Define log level for this module 12 | #include // Control assist class 13 | 14 | const char st_ssid[]=""; // Put connection SSID here. On empty an AP will be started 15 | const char st_pass[]=""; // Put your wifi passowrd. 16 | unsigned long pingMillis = millis(); // Ping millis 17 | 18 | bool ledState = false; 19 | #ifndef LED_BUILTIN 20 | #define LED_BUILTIN 22 21 | #endif 22 | 23 | ControlAssist ctrl; // Control assist class 24 | WEB_SERVER server(80); // Web server on port 80 25 | 26 | PROGMEM const char HTML_BODY[] = R"=====( 27 | 122 | 123 |
124 |

ControlAssist example page

125 |
126 |
127 |
Toggle led
128 |
129 |
130 |
131 | 132 | 133 |
134 |
135 |
136 |
137 |
138 |
139 | 140 |
141 | 172 | 173 | )====="; 174 | 175 | void ledChangeHandler(){ 176 | ledState = ctrl["toggleLed"].toInt(); 177 | LOG_D("ledChangeHandler state: %i\n", ledState); 178 | toggleLed(ledState); 179 | } 180 | 181 | void toggleLed(bool ledState){ 182 | if( ledState ){ 183 | digitalWrite(LED_BUILTIN, LOW); // Turn LED ON 184 | LOG_D("Led on\n"); 185 | }else{ 186 | digitalWrite(LED_BUILTIN, HIGH); // Turn LED OFF 187 | LOG_D("Led off\n"); 188 | } 189 | } 190 | 191 | void setup() { 192 | Serial.begin(115200); 193 | Serial.print("\n\n\n\n"); 194 | Serial.flush(); 195 | LOG_I("Starting..\n"); 196 | 197 | // Connect WIFI ? 198 | if(strlen(st_ssid)>0){ 199 | LOG_E("Connect Wifi to %s.\n", st_ssid); 200 | WiFi.mode(WIFI_STA); 201 | WiFi.begin(st_ssid, st_pass); 202 | uint32_t startAttemptTime = millis(); 203 | while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) { 204 | Serial.print("."); 205 | delay(500); 206 | Serial.flush(); 207 | } 208 | Serial.println(); 209 | } 210 | 211 | // Check connection 212 | if(WiFi.status() == WL_CONNECTED ){ 213 | LOG_I("Wifi AP SSID: %s connected, use 'http://%s' to connect\n", st_ssid, WiFi.localIP().toString().c_str()); 214 | }else{ 215 | LOG_E("Connect failed.\n"); 216 | LOG_I("Starting AP.\n"); 217 | String mac = WiFi.macAddress(); 218 | mac.replace(":",""); 219 | String hostName = "ControlAssist_" + mac.substring(6); 220 | WiFi.mode(WIFI_AP_STA); 221 | WiFi.softAP(hostName.c_str(),"",1); 222 | LOG_I("Wifi AP SSID: %s started, use 'http://%s' to connect\n", WiFi.softAPSSID().c_str(), WiFi.softAPIP().toString().c_str()); 223 | if (MDNS.begin(hostName.c_str())) LOG_V("AP MDNS responder Started\n"); 224 | } 225 | 226 | // Setup control assist 227 | ctrl.setHtmlBody(HTML_BODY); 228 | ctrl.bind("toggleLed", ledState, ledChangeHandler); 229 | // Auto send on connect 230 | ctrl.setAutoSendOnCon("toggleLed", true); 231 | ctrl.begin(); 232 | String res = ""; 233 | LOG_V("ControlAssist started.\n"); 234 | 235 | // Setup webserver 236 | server.on("/", []() { 237 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 238 | String res = ""; 239 | res.reserve(CTRLASSIST_STREAM_CHUNKSIZE); 240 | while( ctrl.getHtmlChunk(res)){ 241 | server.sendContent(res); 242 | } 243 | }); 244 | 245 | // Dump binded controls handler 246 | server.on("/d", []() { 247 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 248 | server.sendContent("Serial dump\n"); 249 | String res = ""; 250 | while( ctrl.dump(res) ){ 251 | server.sendContent(res); 252 | } 253 | }); 254 | 255 | // Start webserver 256 | server.begin(); 257 | LOG_V("HTTP server started\n"); 258 | 259 | pinMode(LED_BUILTIN, OUTPUT); 260 | digitalWrite(LED_BUILTIN, HIGH); // Turn LED OFF 261 | 262 | // Dump binded controls to serial 263 | while(ctrl.dump(res)) Serial.print(res); 264 | 265 | } 266 | 267 | 268 | void loop() { 269 | #if not defined(ESP32) 270 | if(MDNS.isRunning()) MDNS.update(); // Handle MDNS 271 | #endif 272 | // Handler webserver clients 273 | server.handleClient(); 274 | // Handle websockets 275 | ctrl.loop(); 276 | 277 | if (millis() - pingMillis >= 5000){ 278 | ledState = !ledState; 279 | toggleLed(ledState); 280 | // Set the ledState and send a websocket update 281 | ctrl.put("toggleLed", ledState ); 282 | pingMillis = millis(); 283 | } 284 | } 285 | 286 | 287 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | ########################################### 4 | # Syntax Coloring Map For ControlAssist-library 5 | ########################################### 6 | 7 | ########################################### 8 | # Datatypes (KEYWORD1) 9 | ########################################### 10 | 11 | ControlAssist KEYWORD1 12 | 13 | ########################################### 14 | # Methods and Functions (KEYWORD2) 15 | ########################################### 16 | 17 | begin KEYWORD2 18 | bind KEYWORD2 19 | put KEYWORD2 20 | set KEYWORD2 21 | setGlobalCallback KEYWORD2 22 | setHtmlHeaders KEYWORD2 23 | setHtmlBody KEYWORD2 24 | setHtmlFooter KEYWORD2 25 | setHtmlHeadersFile KEYWORD2 26 | setHtmlBodyFile KEYWORD2 27 | setHtmlFooterFile KEYWORD2 28 | getInitScript KEYWORD2 29 | setGlobalCallback KEYWORD2 30 | getClientsNum KEYWORD2 31 | getHtmlChunk KEYWORD2 32 | 33 | -------------------------------------------------------------------------------- /library,json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ControlAssist", 3 | "keywords": "javascript html arduino esp8266 runtime controller webserver websocket-server esp32 websockets websocket-client http-server page-generator multiclient", 4 | "description": "Generate webserver pages on esp devices and control their html elements at runtime using web sockets", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/gemi254/ControlAssist-ESP32-ESP8266.git" 8 | }, 9 | "version": "1.1.6", 10 | "homepage": "https://github.com/gemi254/ControlAssist-ESP32-ESP8266", 11 | "examples": "examples/*/*.ino", 12 | "frameworks": "arduino", 13 | "platforms": [ 14 | "atmelavr", 15 | "espressif8266", 16 | "espressif32" 17 | ] 18 | } -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ControlAssist 2 | version=1.1.6 3 | author=gemi254 4 | maintainer=gemi254 5 | sentence=Generate webserver pages on esp devices and control their html elements at runtime using web sockets. 6 | paragraph=A library allowing linking html elements to sketch variables on pages hosted on esp32/esp8266 devices. It uses a websocket server on the esp device and a JavaScript webocket client on the web page allowing bi-directional real-time communication between device and page. 7 | category=Device Control 8 | url=https://github.com/gemi254/ControlAssist-ESP32-ESP8266 9 | includes=ControlAssist.h 10 | architectures=* -------------------------------------------------------------------------------- /src/ControlAssist.cpp: -------------------------------------------------------------------------------- 1 | #include "ControlAssist.h" // Control assist class 2 | #include "controlAssistPMem.h" // Memory static valiables (html pages) 3 | 4 | ControlAssist::ControlAssist() { 5 | _socket = NULL; 6 | _wsEnabled = false; 7 | _ev = NULL; 8 | _html_headers = CONTROLASSIST_HTML_HEADER; 9 | _html_body = CONTROLASSIST_HTML_BODY; 10 | _html_footer = CONTROLASSIST_HTML_FOOTER; 11 | _html_headers_file = NULL; 12 | _html_body_file = NULL; 13 | _html_footer_file = NULL; 14 | _clientsNum = 0; 15 | _port = 81; 16 | } 17 | 18 | ControlAssist::ControlAssist(uint16_t port) 19 | : ControlAssist(){ 20 | _port = port; 21 | } 22 | 23 | // Start websockets 24 | void ControlAssist::begin(){ 25 | if(!_wsEnabled && (WiFi.isConnected() || WiFi.softAPSSID() != "" )){ 26 | startWebSockets(); 27 | _wsEnabled = true; 28 | } 29 | } 30 | 31 | // Stop websockets 32 | void ControlAssist::close(){ 33 | if(_wsEnabled){ 34 | stopWebSockets(); 35 | _wsEnabled = false; 36 | } 37 | } 38 | 39 | // Start the websocket server 40 | void ControlAssist::startWebSockets(){ 41 | if(_socket) return; 42 | _socket = new WebSocketsServer(_port); 43 | _socket->onEvent( std::bind(&ControlAssist::webSocketEvent, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4) ); 44 | _socket->begin(); 45 | LOG_I("Started web sockets at port: %u\n", _port); 46 | } 47 | 48 | // Stop the websocket server 49 | void ControlAssist::stopWebSockets(){ 50 | if(_socket == NULL) return; 51 | _socket->close(); 52 | delete _socket; 53 | _socket = NULL; 54 | _clientsNum = 0; 55 | LOG_I("Stoped web sockets at port: %u\n", _port); 56 | } 57 | 58 | // Get the val of a given key, Empty on not found 59 | String ControlAssist::getVal(const String &key) { 60 | int keyNdx = getKeyNdx(key); 61 | if (keyNdx >= 0) { 62 | return _ctrls[keyNdx].val; 63 | } 64 | return String(""); 65 | } 66 | 67 | // Add vectors by key (name in confPairs) 68 | size_t ControlAssist::add(const String &key, const String &val){ 69 | ctrlPairs d = { key, "", NULL, false }; 70 | return add(d); 71 | } 72 | 73 | // Add vectors pairs 74 | size_t ControlAssist::add(ctrlPairs &c){ 75 | _ctrls.push_back( {c} ); 76 | _keysNdx.push_back( {c.key, _keysNdx.size() } ); 77 | sort(); 78 | LOG_V("Added key: %s\n", c.key.c_str()); 79 | return _ctrls.size(); 80 | } 81 | 82 | // Set the val at pos, (int) 83 | bool ControlAssist::set(int keyNdx, int val, bool forceSend) { 84 | return set(keyNdx, String(val), forceSend); 85 | } 86 | 87 | // Set the val at pos, (string) 88 | bool ControlAssist::set(int keyNdx, const String &val, bool forceSend) { 89 | bool changed = false; 90 | if (keyNdx >= 0 && (size_t)keyNdx < _ctrls.size() ) { 91 | if( _ctrls[keyNdx].val != val){ 92 | LOG_V("Set [%i] = %s\n", keyNdx, val.c_str()); 93 | _ctrls[keyNdx].val = val; 94 | changed = true; 95 | } 96 | }else{ 97 | LOG_W("Set invalid ndx: [%i] = %s\n", keyNdx, val.c_str()); 98 | return false; 99 | } 100 | // Send message to client 101 | if(_clientsNum > 0 && (forceSend || changed)){ 102 | String payload = String(keyNdx + 1) + "\t" + val; 103 | if(_socket) _socket->broadcastTXT(payload); 104 | LOG_V("Send payload: %s\n", payload.c_str()); 105 | } 106 | return true; 107 | } 108 | 109 | // Update the val of key = val (int) 110 | bool ControlAssist::put(const String &key, int val, bool forceSend, bool forceAdd) { 111 | return put(key, String(val), forceSend, forceAdd); 112 | } 113 | 114 | // Update the val of key = val (string) forceAdd to add it even if not exists, forceSend = false to send changes only 115 | bool ControlAssist::put(const String &key, const String &val, bool forceSend, bool forceAdd) { 116 | int keyNdx = getKeyNdx(key); 117 | bool changed = false; 118 | if (keyNdx >= 0 && (size_t)keyNdx < _ctrls.size() ) { 119 | if( _ctrls[keyNdx].val != val){ 120 | LOG_V("Put [%i] = %s\n", keyNdx, val.c_str()); 121 | _ctrls[keyNdx].val = val; 122 | changed = true; 123 | } 124 | }else if(forceAdd) { 125 | keyNdx = add(key, val); 126 | changed = true; 127 | }else{ 128 | LOG_W("Put failed on key: %s, val: %s\n", key.c_str(), val.c_str()); 129 | return false; 130 | } 131 | //Send message to client 132 | if(_clientsNum > 0 && (forceSend || changed)){ 133 | String payload = String(keyNdx + 1) + "\t" + val; 134 | if(_socket) _socket->broadcastTXT(payload); 135 | LOG_V("Send payload: %s\n", payload.c_str()); 136 | } 137 | return true; 138 | } 139 | 140 | // Set the auto send on ws connection flag on key 141 | bool ControlAssist::setAutoSendOnCon(const String &key, bool send) { 142 | int keyNdx = getKeyNdx(key); 143 | if (keyNdx >= 0 && (size_t)keyNdx < _ctrls.size() ) { 144 | _ctrls[keyNdx].autoSendOnCon = send; 145 | return true; 146 | } 147 | LOG_E("Set auto send failed on key: %s\n", key.c_str()); 148 | return false; 149 | } 150 | 151 | // Set the auto send on ws connection flag on all keys 152 | void ControlAssist::setAutoSendOnCon(bool send) { 153 | uint8_t row = 0; 154 | while (row++ < _ctrls.size()) { 155 | _ctrls[row - 1].autoSendOnCon = send; 156 | } 157 | } 158 | 159 | // On websocket connection send the keys with auto send flag to clients 160 | void ControlAssist::autoSendKeys(uint8_t num){ 161 | uint8_t row = 0; 162 | while (row++ < _ctrls.size()) { 163 | LOG_V("Checking row: %s ,%i\n", _ctrls[row - 1].key.c_str(), _ctrls[row - 1].autoSendOnCon ); 164 | if(_ctrls[row - 1].autoSendOnCon){ 165 | String payload = String(row) + "\t" + _ctrls[row - 1].val; 166 | if(_socket) _socket->broadcastTXT(payload); 167 | LOG_D("Auto send to: %s, client num: %u, payload: %s\n",_ctrls[row - 1].key.c_str(), num, payload.c_str()); 168 | } 169 | } 170 | } 171 | 172 | // Get the location of given key to retrieve control 173 | int ControlAssist::getKeyNdx(const String &key) { 174 | if (_keysNdx.empty() || key == "") return -1; 175 | //LOG_I("getKeyNdx, key: %s\n",key.c_str()); 176 | auto lower = std::lower_bound(_keysNdx.begin(), _keysNdx.end(), key, []( 177 | const ctrlsNdx &a, const String &b) { 178 | return a.key < b;} 179 | ); 180 | int keyNdx = std::distance(_keysNdx.begin(), lower); 181 | if (_keysNdx[keyNdx].ndx < _ctrls.size() && key == _ctrls[ _keysNdx[keyNdx].ndx ].key) return _keysNdx[keyNdx].ndx; 182 | else LOG_V("Key %s not exist\n", key.c_str()); 183 | return -1; // not found 184 | } 185 | 186 | // Return next key and val from configs on each call in key order 187 | bool ControlAssist::getNextPair(ctrlPairs &c) { 188 | static uint8_t row = 0; 189 | if (row++ < _ctrls.size()) { 190 | c = _ctrls[row - 1]; 191 | return true; 192 | } 193 | // end of vector reached, reset 194 | row = 0; 195 | return false; 196 | } 197 | 198 | // Sort vectors by key (name in logKeysNdx) 199 | void ControlAssist::sort(){ 200 | std::sort(_keysNdx.begin(), _keysNdx.end(), [] ( 201 | const ctrlsNdx &a, const ctrlsNdx &b) { 202 | return a.key < b.key;} 203 | ); 204 | } 205 | // Dump config items 206 | bool ControlAssist::dump(String &res){ 207 | char outBuff[256]; 208 | static uint8_t sectNo = 0; 209 | if(sectNo == 0){ 210 | res = "Dump binded controls\n"; 211 | ctrlPairs c; 212 | uint8_t ndx = 0; 213 | while (getNextPair(c)){ 214 | sprintf(outBuff, "Ndx: %02u, chn: %02u, autoSend: %i, '%s': %s \n", ndx, ndx + 1, c.autoSendOnCon, c.key.c_str(), c.val.c_str() ); 215 | res += outBuff; 216 | ndx++; 217 | } 218 | ++sectNo; 219 | return true; 220 | }else if(sectNo == 1){ 221 | res = "\nDump indexes: \n"; 222 | size_t i = 0; 223 | while( i < _keysNdx.size() ){ 224 | sprintf(outBuff, "No: %02i, ndx: %02i, key: %s\n", i, _keysNdx[i].ndx, _keysNdx[i].key.c_str()); 225 | res += outBuff; 226 | i++; 227 | } 228 | ++sectNo; 229 | return true; 230 | } 231 | res = ""; 232 | sectNo = 0; 233 | return false; 234 | } 235 | 236 | // Bind a html control with id = key to a control variable 237 | int ControlAssist::bind(const char* key){ 238 | return bind(key, ""); 239 | } 240 | 241 | // Bind a html control with id = key and local val to a control variable 242 | int ControlAssist::bind(const char* key, const int val){ 243 | return bind(key, String(val).c_str(), NULL); 244 | } 245 | 246 | // Bind a html control with id = key and local val to a control variable 247 | int ControlAssist::bind(const char* key, const char* val){ 248 | return bind(key, val, NULL); 249 | } 250 | 251 | // Bind a html control with id = key to a local variable and an event function 252 | int ControlAssist::bind(const char* key, WebSocketServerEvent ev){ 253 | return bind(key, "", ev); 254 | } 255 | // Bind a html control with id = key and default int val to a local variable with an event function 256 | int ControlAssist::bind(const char* key, const int val, WebSocketServerEvent ev){ 257 | return bind(key, String(val).c_str(), ev); 258 | } 259 | 260 | // Bind a html control with id = key and default char val to a local variable with an event function 261 | int ControlAssist::bind(const char* key, const char* val, WebSocketServerEvent ev){ 262 | int p = getKeyNdx(key); 263 | if(p >= 0){ 264 | LOG_E("Bind key: %s failed. Already exists in pos %i!\n", key, p); 265 | return -1; 266 | } 267 | ctrlPairs ctrl = { key, val, ev, true }; 268 | LOG_D("Binding key: '%s', val: %s, chn: %02i\n", key, val, _ctrls.size() + 1); 269 | add(ctrl); 270 | return getKeyNdx(key); 271 | } 272 | 273 | bool ControlAssist::sendSystemMsg(const String &msg){ 274 | String m = String("0\t") + msg; 275 | return _socket->broadcastTXT(m); 276 | } 277 | 278 | 279 | // Add a global callback function to handle changes 280 | void ControlAssist::setGlobalCallback(WebSocketServerEventG ev){ 281 | _ev = ev; 282 | } 283 | 284 | // Response to a websocket event 285 | void ControlAssist::webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { 286 | switch(type) { 287 | case WStype_PING: 288 | case WStype_PONG: 289 | break; 290 | case WStype_DISCONNECTED: 291 | LOG_E("Client no: %02u Disconnected!\n", num); 292 | if(_clientsNum>0) _clientsNum--; 293 | break; 294 | case WStype_CONNECTED: 295 | { 296 | IPAddress ip = _socket->remoteIP(num); 297 | LOG_I("Client no: %02u connect from %d.%d.%d.%d url: %s\n", num, ip[0], ip[1], ip[2], ip[3], payload); 298 | _clientsNum++; 299 | // Send message to client 300 | _socket->sendTXT(num, "0\tCon "); 301 | // Send keys selected 302 | autoSendKeys(num); 303 | } 304 | break; 305 | case WStype_TEXT: 306 | { 307 | uint8_t * pload = payload; 308 | // Get index first 3 max bytes until tab 309 | char chn[3]; 310 | size_t i = 0; 311 | while((char)pload[i] != '\t' && i < 3){ 312 | chn[i]=( (char)pload[i] ); 313 | i++; 314 | if(i >= length) break; 315 | } 316 | if(i < length) pload += i + 1; 317 | 318 | uint8_t ndx = (uint8_t)atoi(chn); 319 | String val = reinterpret_cast(pload); 320 | 321 | LOG_N("Msg ndx: %u, val: %s\n", ndx, val.c_str()); 322 | 323 | if(ndx == 0 ){ //System message 324 | if(val == "C"){ // Close connection message 325 | LOG_W("Received close connection msg. \n"); 326 | _socket->disconnect(); 327 | }else{ 328 | LOG_V("Sys msg: %s \n",val.c_str()); 329 | } 330 | return; 331 | } 332 | 333 | // Zero based index, index 0 is for system messages 334 | ndx = ndx - 1; 335 | if(ndx >= _ctrls.size() ) return; 336 | 337 | // Change val 338 | _ctrls[ndx].val = String(val.c_str()); 339 | 340 | // Call control's change handler 341 | if(_ctrls[ndx].ev) { 342 | _ctrls[ndx].ev(); 343 | } 344 | // Call global change handler 345 | if(_ev){ 346 | _ev(ndx); 347 | } 348 | LOG_V("Ctrl msg, client: %02u, ndx: %02u, key: '%s', val: %s\n", num, ndx, _ctrls[ndx].key.c_str(), val.c_str()); 349 | // Update all other connected clients 350 | if( _clientsNum > 1 ){ 351 | for(uint8_t i = 0; i < _clientsNum; ++i){ 352 | if( i != num ) _socket->sendTXT(i, payload); 353 | } 354 | } 355 | } 356 | break; 357 | case WStype_BIN: 358 | LOG_D("Client no: %02u, get binary length: %u\n", num, length); 359 | // send message to client 360 | // webSocket.sendBIN(num, payload, length); 361 | break; 362 | case WStype_ERROR: 363 | case WStype_FRAGMENT_TEXT_START: 364 | case WStype_FRAGMENT_BIN_START: 365 | case WStype_FRAGMENT: 366 | case WStype_FRAGMENT_FIN: 367 | LOG_E("Client %02u, WS error: %x\n", num, type); 368 | break; 369 | } 370 | } 371 | 372 | // Build the initialization java script 373 | String ControlAssist::getInitScript(){ 374 | String ctlPort = "const port = " + String(_port)+";"; 375 | String keysToNdx = "const keysToNdx = { "; 376 | String ndxTokeys = "const ndxTokeys = { "; 377 | for(uint8_t no = 0; no< _ctrls.size(); no++) { 378 | keysToNdx += "'" + String(_ctrls[no].key) + "': " + no + ", "; 379 | ndxTokeys += "'" + String(no) + "': '" + String(_ctrls[no].key) + "', "; 380 | } 381 | if(keysToNdx.endsWith(", ")) keysToNdx = keysToNdx.substring(0, (keysToNdx.length() - 2)); 382 | if(ndxTokeys.endsWith(", ")) ndxTokeys = ndxTokeys.substring(0, (ndxTokeys.length() - 2)); 383 | keysToNdx += "};"; 384 | ndxTokeys += "};"; 385 | return ctlPort + "\n" + keysToNdx + "\n" + ndxTokeys + "\n"; 386 | } 387 | 388 | // Load a file into a string 389 | bool ControlAssist::loadText(const String &fPath, String &txt){ 390 | File file = STORAGE.open(fPath, "r"); 391 | if (!file || !file.size()) { 392 | LOG_E("Failed to load: %s, sz: %u B\n", fPath.c_str(), file.size()); 393 | return false; 394 | } 395 | //Load text from file 396 | txt = ""; 397 | while (file.available()) { 398 | txt += file.readString(); 399 | } 400 | LOG_D("Loaded: %s, sz: %u B\n" , fPath.c_str(), txt.length() ); 401 | file.close(); 402 | return true; 403 | } 404 | 405 | bool ControlAssist::getFileChunk(const char *fname, String &res){ 406 | static size_t chunkPos = 0; 407 | static File f; 408 | if(!f){ 409 | if(!STORAGE.exists(fname)){ 410 | LOG_E("Storage file missing: %s\n", fname); 411 | res = ""; 412 | return false; 413 | }else{ // Open the file 414 | #if defined(ESP32) 415 | f = STORAGE.open(fname); 416 | #else 417 | f = STORAGE.open(String(fname), "r"); 418 | #endif 419 | } 420 | } 421 | if(!f) { 422 | LOG_E("Failed to open file: %s\n", fname); 423 | res = ""; 424 | return false; 425 | } 426 | 427 | if(!f.seek(chunkPos)){ 428 | LOG_E("Failed to seek file: %s, at pos: %u\n", fname, chunkPos); 429 | chunkPos = 0; 430 | res = ""; 431 | return false; 432 | } 433 | 434 | uint8_t dest[ CTRLASSIST_STREAM_CHUNKSIZE + 1]; 435 | size_t chunksize = f.read(dest, CTRLASSIST_STREAM_CHUNKSIZE); 436 | 437 | if(chunksize > 0){ 438 | chunkPos += chunksize; 439 | dest[chunksize] = '\0'; 440 | res = (char *)dest; 441 | LOG_V("getFileChunk copied: %u, pos: %u\n", chunksize, chunkPos); 442 | return true; 443 | } 444 | 445 | if(f) f.close(); 446 | chunkPos = 0; 447 | res = ""; 448 | return false; 449 | } 450 | 451 | bool ControlAssist::getStringChunk(const char *src, String &res){ 452 | static size_t chunkPos = 0; 453 | static size_t srcLen = 0; 454 | if(srcLen == 0 ) srcLen = strlen_P(src); 455 | if(chunkPos < srcLen){ 456 | char dest[ CTRLASSIST_STREAM_CHUNKSIZE + 1]; 457 | snprintf_P(dest, sizeof(dest), PSTR("%s"), src + chunkPos); 458 | size_t nBytes = strlen(dest); 459 | res = String(dest); 460 | chunkPos += nBytes; 461 | LOG_V("getStringChunk copied: %u, pos: %u\n", nBytes, chunkPos); 462 | return true; 463 | } 464 | 465 | chunkPos = 0; 466 | srcLen = 0; 467 | return false; 468 | } 469 | 470 | bool ControlAssist::getHtmlChunk(String &res){ 471 | static uint8_t sectNo = 0; 472 | 473 | LOG_V("getHtmlChunk %i\n", sectNo); 474 | if(sectNo == 0){ // Headers 475 | if(_html_headers_file){ 476 | if(getFileChunk(_html_headers_file, res)){ 477 | return true; 478 | }else{ 479 | ++sectNo; 480 | return getHtmlChunk(res); 481 | } 482 | }else{ 483 | if(getStringChunk(_html_headers, res)){ 484 | return true; 485 | }else{ 486 | ++sectNo; 487 | return getHtmlChunk(res); 488 | } 489 | 490 | res = String(_html_headers); 491 | } 492 | ++sectNo; 493 | return true; 494 | }else if(sectNo == 1){ // Scripts 495 | String definitions = getInitScript(); 496 | String scripts = ""; 502 | res = scripts; 503 | ++sectNo; 504 | return true; 505 | }else if(sectNo == 2){ // Body 506 | if(_html_body_file){ 507 | if(getFileChunk(_html_body_file, res)){ 508 | return true; 509 | }else{ 510 | ++sectNo; 511 | return getHtmlChunk(res); 512 | } 513 | }else{ 514 | if(getStringChunk(_html_body, res)){ 515 | return true; 516 | }else{ 517 | ++sectNo; 518 | return getHtmlChunk(res); 519 | } 520 | } 521 | }else if(sectNo == 3){ // Footer 522 | if(_html_footer_file){ 523 | if(getFileChunk(_html_footer_file, res)){ 524 | return true; 525 | }else{ 526 | ++sectNo; 527 | return getHtmlChunk(res); 528 | } 529 | }else{ 530 | if(getStringChunk(_html_footer, res)){ 531 | return true; 532 | }else{ 533 | ++sectNo; 534 | return getHtmlChunk(res); 535 | } 536 | 537 | } 538 | ++sectNo; 539 | return true; 540 | } 541 | res = ""; 542 | sectNo = 0; 543 | return false; 544 | } -------------------------------------------------------------------------------- /src/ControlAssist.h: -------------------------------------------------------------------------------- 1 | #if !defined(_CONTROL_ASSIST_H) 2 | #define _CONTROL_ASSIST_H 3 | 4 | #include 5 | #include 6 | 7 | #ifndef LOGGER_LOG_LEVEL 8 | #define LOGGER_LOG_LEVEL 4 // Set log level for this module 9 | #endif 10 | 11 | #define CT_CLASS_VERSION "1.1.6" // Class version 12 | #define CTRLASSIST_STREAM_CHUNKSIZE 2048 // Stream file buffer size 13 | 14 | #if !defined(STORAGE) 15 | #if defined(ESP8266) || defined(CA_USE_LITTLEFS) 16 | #include 17 | #define STORAGE LittleFS // one of: SPIFFS LittleFS SD_MMC 18 | #else 19 | #include "SPIFFS.h" 20 | #define STORAGE SPIFFS // SPIFFS 21 | #endif 22 | #endif 23 | 24 | #include "espLogger.h" 25 | 26 | // Web socket events 27 | typedef std::function WebSocketServerEventG; 28 | typedef std::function WebSocketServerEvent; 29 | 30 | //Structure for control elements 31 | struct ctrlPairs { 32 | String key; 33 | String val; 34 | WebSocketServerEvent ev; 35 | bool autoSendOnCon; 36 | }; 37 | 38 | // Positions of keys inside array 39 | struct ctrlsNdx { 40 | String key; 41 | size_t ndx; 42 | }; 43 | 44 | class ControlAssist{ 45 | public: 46 | ControlAssist(); 47 | ControlAssist(uint16_t port); 48 | ~ControlAssist() {}; 49 | public: 50 | // Start web sockets server 51 | void begin(); 52 | // Set web sockets server listening port 53 | void setPort(uint16_t port) {if(!_wsEnabled) _port = port; } 54 | // Bind a html control with id = key to a control variable 55 | int bind(const char* key); 56 | // Bind a html control with id = key to a control variable and an on event function 57 | int bind(const char* key, WebSocketServerEvent ev); 58 | // Bind a html control with id = key to a control variable with start int val 59 | int bind(const char* key, const int val); 60 | // Bind a html control with id = key to a control variable with start char val 61 | int bind(const char* key, const char* val); 62 | // Bind a html control with id = key to a control variable and an on event function 63 | int bind(const char* key, const int val, WebSocketServerEvent ev); 64 | // Bind a html control with id = key to a control variable, a start val and an on event function 65 | int bind(const char* key, const char* val, WebSocketServerEvent ev); 66 | 67 | // Add a global callback function to handle changes 68 | void setGlobalCallback(WebSocketServerEventG ev); 69 | // Set html page code 70 | void setHtmlHeaders(const char *html_headers) { _html_headers = html_headers; } 71 | void setHtmlBody(const char *html_body) { _html_body = html_body; } 72 | void setHtmlFooter(const char *html_footer) { _html_footer = html_footer; } 73 | // Set html page code from file 74 | void setHtmlHeadersFile(const char *fileName) { _html_headers_file = fileName; } 75 | void setHtmlBodyFile(const char *fileName) { _html_body_file = fileName; } 76 | void setHtmlFooterFile(const char *fileName) { _html_footer_file = fileName; } 77 | // Stop web sockets 78 | void close(); 79 | public: 80 | // Implement operator [] i.e. val = config['key'] 81 | String operator [] (String k) { return getVal(k); } 82 | // Implement operator [] i.e. val = config[ndx] 83 | ctrlPairs operator [] (uint8_t ndx) { return _ctrls[ndx]; } 84 | // Return the val of a given key, Empty on not found 85 | String getVal(const String &key); 86 | // Return the position of given key 87 | int getKeyNdx(const String &key); 88 | // Return next key and value from configs on each call in key order 89 | bool getNextPair(ctrlPairs &c); 90 | // Add vectors by key (name in ctrlPairs) 91 | size_t add(const String &key, const String &val); 92 | // Add vectors pairs 93 | size_t add(ctrlPairs &c); 94 | // Set the val at key index, (int) 95 | bool set(int keyNdx, int val, bool forceSend = false); 96 | // Set the val at key index, (string) 97 | bool set(int keyNdx, const String &val, bool forceSend = false); 98 | // Put val (int) to Key. forcesend to false to send changes only, forceAdd to add it if not exists 99 | bool put(const String &key, int val, bool forceSend = false, bool forceAdd = false); 100 | // Put val (string) to Key. forcesend to false to send changes only, forceAdd to add it if not exists 101 | bool put(const String &key, const String &val, bool forceSend = false, bool forceAdd = false); 102 | // Send system message. (Connection close) 103 | bool sendSystemMsg(const String &msg); 104 | // Display config items 105 | bool dump(String &res); 106 | 107 | public: 108 | // Sort vectors by key (name in confPairs) 109 | void sort(); 110 | // Run websockets 111 | void loop() { if(_socket) _socket->loop(); } 112 | // Get the initialization java script 113 | String getInitScript(); 114 | // Get a chunk from a html file 115 | bool getFileChunk(const char *fname, String &res); 116 | // Get a chunk from a html buffer 117 | bool getStringChunk(const char *src, String &res); 118 | // Render html to client 119 | bool getHtmlChunk(String &res); 120 | // Get number of connected clients 121 | uint8_t getClientsNum() { return _clientsNum; } 122 | // Set the auto send on ws connection flag on key 123 | bool setAutoSendOnCon(const String &key, bool send); 124 | // Set the auto send on ws connection flag on all keys 125 | void setAutoSendOnCon(bool send); 126 | private: 127 | // Load a file into text 128 | bool loadText(const String &fPath, String &txt); 129 | // Start websockets 130 | void startWebSockets(); 131 | // Stop websockets 132 | void stopWebSockets(); 133 | // Send the keys with auto send flag on ws connect 134 | void autoSendKeys(uint8_t num); 135 | // Response to a websocket event 136 | void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length); 137 | private: 138 | std::vector _ctrls; 139 | std::vector _keysNdx; 140 | uint8_t _clientsNum; 141 | const char* _html_headers; 142 | const char* _html_body; 143 | const char* _html_footer; 144 | const char* _html_headers_file; 145 | const char* _html_body_file; 146 | const char* _html_footer_file; 147 | uint16_t _port; 148 | bool _wsEnabled; 149 | WebSocketsServer *_socket; 150 | WebSocketServerEventG _ev; 151 | }; 152 | 153 | #endif // _CONTROL_ASSIST_H 154 | -------------------------------------------------------------------------------- /src/controlAssistPMem.h: -------------------------------------------------------------------------------- 1 | // Capture page changes 2 | PROGMEM const char CONTROLASSIST_SCRIPT_INIT[] = R"=====( 3 | const $ = document.querySelector.bind(document); 4 | const $$ = document.querySelectorAll.bind(document); 5 | var ndxToElm = [] 6 | 7 | /* * * * Control assist functions * * * */ 8 | function updateKeys(event){ 9 | if(event.type=="wsChange") return; 10 | const e = event.target; 11 | var value = "" 12 | if(typeof(e.value) != 'undefined') 13 | value = e.value.trim(); 14 | else if(typeof(e.innerHTML) != 'undefined') 15 | value = e.innerHTML; 16 | 17 | const et = event.target.type; 18 | 19 | if (e.type === 'checkbox'){ 20 | updateKey(e.id, e.checked ? 1 : 0); 21 | }else { 22 | updateKey(e.id, value); 23 | } 24 | } 25 | 26 | function updateKey(key, value) { 27 | if(value == null ) return; 28 | sendWsTxt(keysToNdx[key] + 1 + "\t" + value); 29 | } 30 | 31 | function wsUpdatedKeys(event){ 32 | if(dbg) console.log('wsUpdatedKeys: ', keysToNdx[ event.target.id], ', key: ', event.target.id); 33 | } 34 | 35 | function initWebSocketCtrls(){ 36 | for(var i in ndxTokeys ) { 37 | const key = ndxTokeys[i]; 38 | const elm = document.getElementById(key); 39 | if(elm){ 40 | if(elm.type === "button"){ //|| elm.type === "submit"){ 41 | elm.addEventListener("click", updateKeys); 42 | }else if(elm.type === "checkbox"){ 43 | elm.addEventListener("change", updateKeys); 44 | }else{ 45 | elm.addEventListener("change", updateKeys); 46 | elm.addEventListener("input", function (event) { 47 | updateKeys(event) 48 | }); 49 | } 50 | // Websocket events 51 | elm.addEventListener("wsChange", wsUpdatedKeys); 52 | } 53 | ndxToElm[i] = elm; 54 | } 55 | } 56 | )====="; 57 | 58 | //Scripts to handle websockets 59 | PROGMEM const char CONTROLASSIST_SCRIPT_WEBSOCKETS_CLIENT[] = R"=====( 60 | /* * * * WebSockets functions * * * */ 61 | let webserver; 62 | if(port) 63 | wsServer = "ws://" + document.location.host + ":" + port + "/"; 64 | else 65 | wsServer = "ws://" + document.location.host + ":81/"; 66 | const dbg = false; 67 | let ws = null; 68 | let hbTimer = null; 69 | let refreshInterval = 15000; 70 | let wsStatus = null; 71 | let conLed = null; 72 | 73 | // close web socket on leaving page 74 | window.addEventListener('beforeunload', function (event) { 75 | if (ws) closeWS(); 76 | const delay = 500; 77 | var start = new Date().getTime(); 78 | while (new Date().getTime() < start + delay); 79 | }); 80 | 81 | function setStatus(msg){ 82 | if(!wsStatus) return; 83 | wsStatus.innerHTML = msg 84 | if(dbg) console.log(msg) 85 | var event = new Event('change'); 86 | wsStatus.dispatchEvent(event); 87 | } 88 | 89 | // Websocket handling 90 | function initWebSocket() { 91 | wsStatus = document.getElementById("wsStatus") 92 | conLed = document.getElementById("conLed") 93 | setStatus("Connecting to: " + wsServer); 94 | ws = new WebSocket(wsServer); 95 | ws.onopen = onWsOpen; 96 | ws.onclose = onWsClose; 97 | ws.onmessage = onWsMessage; 98 | ws.onerror = onWsError; 99 | } 100 | 101 | async function closeWS() { 102 | setStatus("Disconnected") 103 | ws.send('0\tC'); 104 | const delay = 500; 105 | var start = new Date().getTime(); 106 | while (new Date().getTime() < start + delay); 107 | ws.close(); 108 | } 109 | function sendWsTxt(reqStr) { 110 | ws.send(reqStr); 111 | if(dbg) console.log("> " + reqStr); 112 | } 113 | function resetHbTimer(){ 114 | clearTimeout(hbTimer); 115 | hbTimer = setTimeout(wsHeartBeat, refreshInterval); 116 | } 117 | // Check that connection is still up 118 | function wsHeartBeat() { 119 | if (!ws) return; 120 | if (ws.readyState !== 1) return; 121 | sendWsTxt("0"); 122 | resetHbTimer(); 123 | } 124 | // Connected 125 | function onWsOpen(event) { 126 | setStatus("Connected: " + wsServer) 127 | wsHeartBeat(); 128 | } 129 | function handleSysMessage(msg){ 130 | if(msg=="C"){ 131 | if(dbg) console.log("Rcv close conn") 132 | closeWS(); 133 | } 134 | } 135 | // Handle websocket messages 136 | function handleWsMessage(msg){ 137 | //Split only first tab 138 | var p = msg.indexOf("\t", 0) 139 | const chn = parseInt( msg.substr(0, p) ); 140 | if(chn < 0 || ndxToElm.length < 1) return; 141 | 142 | const val = msg.substr(p+1, msg.length - 1) 143 | if(chn == 0){ 144 | handleSysMessage(val); 145 | return; 146 | } 147 | 148 | const ndx = chn - 1; 149 | const elm = ndxToElm[ ndx ]; 150 | 151 | if(!elm){ 152 | console.log("Control no: " , ndx, " not found") 153 | return; 154 | } 155 | if(elm.type === undefined){ 156 | elm.innerHTML = val; 157 | }else if (elm.type === 'checkbox'){ 158 | elm.checked = (val=="0" ? false : true); 159 | }else{ 160 | elm.value = val; 161 | } 162 | var event = new Event('wsChange'); 163 | elm.dispatchEvent(event); 164 | } 165 | 166 | // Received WS message 167 | function onWsMessage(messageEvent) { 168 | resetHbTimer(); 169 | if(dbg) console.log("< ",messageEvent.data); 170 | if (messageEvent.data.startsWith("{")) { 171 | // json data 172 | updateData = JSON.parse(messageEvent.data); 173 | updateTable(updateData); // format received config json into html table 174 | } else { 175 | handleWsMessage(messageEvent.data); 176 | } 177 | } 178 | function onWsError(event) { 179 | setStatus("Error: " + event.code) 180 | } 181 | 182 | function onWsClose(event) { 183 | setStatus("Disconnect: " + event.code + ' - ' + event.reason); 184 | ws = null; 185 | // event.codes: 186 | // 1006 if server not available, or another web page is already open 187 | // 1005 if closed from app 188 | if (event.code == 1006) { 189 | console.log("Closed ws as a newer conn was made"); 190 | initWebSocket(); 191 | }else if (event.code != 1005) initWebSocket(); // retry if any other reason 192 | } 193 | 194 | /*{SUB_SCRIPT}*/ 195 | )====="; 196 | 197 | PROGMEM const char CONTROLASSIST_SCRIPT_INIT_CONTROLS[] = R"=====( 198 | document.addEventListener('DOMContentLoaded', function (event) { 199 | initWebSocket(); 200 | initWebSocketCtrls(); 201 | 202 | /*{SUB_SCRIPT}*/ 203 | }) 204 | )====="; 205 | //Template for header of the html page 206 | PROGMEM const char CONTROLASSIST_HTML_HEADER[] = 207 | R"=====( 208 | 209 | 210 | 211 | 212 | 213 | 214 | ControlAssist 215 | 216 | )====="; 217 | //Template for body of the html page 218 | PROGMEM const char CONTROLASSIST_HTML_BODY[] = R"=====( 219 | 220 |

No html page code defined!

221 |

Define you html code here

222 | 223 | )====="; 224 | //Template for footer of the html page 225 | PROGMEM const char CONTROLASSIST_HTML_FOOTER[] = R"=====( 226 | )====="; 227 | -------------------------------------------------------------------------------- /src/espLogger.h: -------------------------------------------------------------------------------- 1 | #ifndef __ESPLOGGER_H__ 2 | #define __ESPLOGGER_H__ 3 | 4 | #define LOGGER_LOG_MODE_SERIAL (1) //Log to serial port 5 | #define LOGGER_LOG_MODE_FILE (2) //Log to a spiffs file 6 | #define LOGGER_LOG_MODE_EXTERNAL (3) //Provide the _log_printf 7 | 8 | #ifndef LOGGER_LOG_MODE 9 | #define LOGGER_LOG_MODE LOGGER_LOG_MODE_SERIAL 10 | #endif 11 | 12 | #define _LOG_LEVEL_NONE (0) 13 | #define _LOG_LEVEL_ERROR (1) 14 | #define _LOG_LEVEL_WARN (2) 15 | #define _LOG_LEVEL_INFO (3) 16 | #define _LOG_LEVEL_DEBUG (4) 17 | #define _LOG_LEVEL_VERBOSE (5) 18 | 19 | #ifndef LOGGER_LOG_LEVEL 20 | #define LOGGER_LOG_LEVEL _LOG_LEVEL_VERBOSE 21 | #endif 22 | 23 | #ifndef LOGGER_LOG_FILENAME 24 | #define LOGGER_LOG_FILENAME "/log" 25 | #endif 26 | 27 | #if LOGGER_LOG_MODE == LOGGER_LOG_MODE_SERIAL 28 | #define _DEBUG_PORT Serial 29 | #elif LOGGER_LOG_MODE == LOGGER_LOG_MODE_FILE 30 | static File log_file; 31 | #define _DEBUG_PORT log_file 32 | #define _CHK_LOG_FILE if(!log_file) log_file = STORAGE.open(LOGGER_LOG_FILENAME, "a+") 33 | #define LOGGER_CLOSE_LOG() log_file.close(); 34 | #elif LOGGER_LOG_MODE == LOGGER_LOG_MODE_EXTERNAL 35 | #ifndef _log_printf 36 | void _log_printf(const char *format, ...); 37 | //#warning _log_printf() must be defined 38 | #endif 39 | #else 40 | #define _DEBUG_PORT Serial 41 | #endif 42 | 43 | #if LOGGER_LOG_MODE != LOGGER_LOG_MODE_EXTERNAL 44 | #define _log_printf(...) _DEBUG_PORT.printf(__VA_ARGS__) 45 | #endif 46 | 47 | #ifndef _LOG_FORMAT 48 | #ifdef ESP32 49 | #define _LOG_FORMAT(type, format) "[%s %s @ %s:%u] " format "", esp_log_system_timestamp(), #type, pathToFileName(__FILE__), __LINE__ 50 | #else 51 | #define _LOG_FORMAT(type, format) "[ %s @ %s:%u] " format "", #type, __FILE__, __LINE__ 52 | #endif 53 | #endif 54 | 55 | #if LOGGER_LOG_LEVEL >= _LOG_LEVEL_VERBOSE 56 | #if LOGGER_LOG_MODE == LOGGER_LOG_MODE_FILE 57 | #define LOG_V(format, ...) { _CHK_LOG_FILE; _log_printf(_LOG_FORMAT(V, format), ##__VA_ARGS__); } 58 | #elif LOGGER_LOG_MODE == LOGGER_LOG_MODE_EXTERNAL 59 | #define LOG_V(format, ...) _log_printf(_LOG_FORMAT(V, format), ##__VA_ARGS__) 60 | #else 61 | #define LOG_V(format, ...) _log_printf(_LOG_FORMAT(V, format), ##__VA_ARGS__) 62 | #endif 63 | #else 64 | #define LOG_V(format, ...) 65 | #endif 66 | 67 | #if LOGGER_LOG_LEVEL >= _LOG_LEVEL_DEBUG 68 | #if LOGGER_LOG_MODE == LOGGER_LOG_MODE_FILE 69 | #define LOG_D(format, ...) { _CHK_LOG_FILE; _log_printf(_LOG_FORMAT(E, format), ##__VA_ARGS__); } 70 | #elif LOGGER_LOG_MODE == LOGGER_LOG_MODE_EXTERNAL 71 | #define LOG_D(format, ...) _log_printf(_LOG_FORMAT(D, format), ##__VA_ARGS__) 72 | #else 73 | #define LOG_D(format, ...) _log_printf(_LOG_FORMAT(D, format), ##__VA_ARGS__) 74 | #endif 75 | #else 76 | #define LOG_D(format, ...) 77 | #endif 78 | 79 | #if LOGGER_LOG_LEVEL >= _LOG_LEVEL_INFO 80 | #if LOGGER_LOG_MODE == LOGGER_LOG_MODE_FILE 81 | #define LOG_I(format, ...) { _CHK_LOG_FILE; _log_printf(_LOG_FORMAT(E, format), ##__VA_ARGS__); } 82 | #elif LOGGER_LOG_MODE == LOGGER_LOG_MODE_EXTERNAL 83 | #define LOG_I(format, ...) _log_printf(_LOG_FORMAT(I, format), ##__VA_ARGS__) 84 | #else 85 | #define LOG_I(format, ...) _log_printf(_LOG_FORMAT(I, format), ##__VA_ARGS__) 86 | #endif 87 | #else 88 | #define LOG_I(format, ...) 89 | #endif 90 | 91 | #if LOGGER_LOG_LEVEL >= _LOG_LEVEL_WARN 92 | #if LOGGER_LOG_MODE == LOGGER_LOG_MODE_FILE 93 | #define LOG_W(format, ...) { _CHK_LOG_FILE; _log_printf(_LOG_FORMAT(E, format), ##__VA_ARGS__); } 94 | #elif LOGGER_LOG_MODE == LOGGER_LOG_MODE_EXTERNAL 95 | #define LOG_W(format, ...) _log_printf(_LOG_FORMAT(W, format), ##__VA_ARGS__) 96 | #else 97 | #define LOG_W(format, ...) _log_printf(_LOG_FORMAT(W, format), ##__VA_ARGS__) 98 | #endif 99 | #else 100 | #define LOG_W(format, ...) 101 | #endif 102 | 103 | #if LOGGER_LOG_LEVEL >= _LOG_LEVEL_ERROR 104 | #if LOGGER_LOG_MODE == LOGGER_LOG_MODE_FILE 105 | #define LOG_E(format, ...) { _CHK_LOG_FILE; _log_printf(_LOG_FORMAT(E, format), ##__VA_ARGS__); } 106 | #elif LOGGER_LOG_MODE == LOGGER_LOG_MODE_EXTERNAL 107 | #define LOG_E(format, ...) _log_printf(_LOG_FORMAT(E, format), ##__VA_ARGS__) 108 | #else 109 | #define LOG_E(format, ...) _log_printf(_LOG_FORMAT(E, format), ##__VA_ARGS__) 110 | #endif 111 | #else 112 | #define LOG_E(format, ...) 113 | #endif 114 | 115 | #define LOG_N(format, ...) 116 | 117 | #endif //__ESPLOGGER_H__ --------------------------------------------------------------------------------