From 0b22328bd81f82f03991f6b974830caafc708ddb Mon Sep 17 00:00:00 2001 From: David Gauchard Date: Fri, 15 Sep 2023 00:59:10 +0200 Subject: [PATCH] introduce lambda --- README.md | 24 +- examples/completeLambda/completeLambda.ino | 544 +++++++++++++++++++++ src/ESPUI.cpp | 85 +--- src/ESPUI.h | 73 ++- src/ESPUIcontrol.cpp | 14 +- src/ESPUIcontrol.h | 10 +- 6 files changed, 622 insertions(+), 128 deletions(-) create mode 100644 examples/completeLambda/completeLambda.ino diff --git a/README.md b/README.md index 4659d70..59926ab 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,9 @@ This section will explain in detail how the Library is to be used from the Ardui

Alternativly you may use the extended callback funtion which provides three parameters to the callback function `myCallback(Control *sender, int eventname, void * UserParameter)`. The `UserParameter` is provided as part of the `ESPUI.addControl` method set and allows the user to define contextual information that is to be presented to the callback function in an unmodified form.

-The below example creates a button and defines a lambda function to implicitly create an `ExtendedCallback` which then invokes a more specialized button callback handler. The example uses the `UserParameter` to hold the `this` pointer to an object instance, providing a mechanism for sending the event to a specific object without the need for a switch / map / lookup translation of the Sender Id to an object reference. +It also possible to use a lambda function in the callback parameter. It also allows the user to define, in a more C++ way, contextual information in any form. This is shown by the [completeLambda](examples/completeLambda/completeLambda.ino) example. +

+The below example creates a button and defines a lambda function to invoke a more specialized button callback handler: ``` void YourClassName::setup() { @@ -163,26 +165,18 @@ void YourClassName::setup() " Button Face Text ", ControlColor::None, ParentElementId, - [](Control *sender, int eventname, void* param) + [&](Control *sender, int eventname) { - if(param) - { - reinterpret_cast(param)->myButtonCallback(sender, eventname); - } - }, - this); // <-Third parameter for the extended callback + myButtonCallback(sender, eventname); // class method + }); // or ButtonElementId = ESPUI.button( " Button Face Text ", - [](Control *sender, int eventname, void* param) + [&](Control *sender, int eventname) { - if(param) - { - reinterpret_cast(param)->myButtonCallback(sender, eventname); - } - }, - this); // <-Third parameter for the extended callback + myButtonCallback(sender, eventname); // class method + }); } ``` ``` diff --git a/examples/completeLambda/completeLambda.ino b/examples/completeLambda/completeLambda.ino new file mode 100644 index 0000000..a435d93 --- /dev/null +++ b/examples/completeLambda/completeLambda.ino @@ -0,0 +1,544 @@ +/** + * @file completeExample.cpp + * @author Ian Gray @iangray1000 + * + * This is an example GUI to show off all of the features of ESPUI. + * This can be built using the Arduino IDE, or PlatformIO. + * + * --------------------------------------------------------------------------------------- + * If you just want to see examples of the ESPUI code, jump down to the setUpUI() function + * --------------------------------------------------------------------------------------- + * + * When this program boots, it will load an SSID and password from the EEPROM. + * The SSID is a null-terminated C string stored at EEPROM addresses 0-31 + * The password is a null-terminated C string stored at EEPROM addresses 32-95. + * If these credentials do not work for some reason, the ESP will create an Access + * Point wifi with the SSID HOSTNAME (defined below). You can then connect and use + * the controls on the "Wifi Credentials" tab to store credentials into the EEPROM. + * + * Version with lambdas. Comparing to version with only callbacks: + * diff -u ../completeExample/completeExample.cpp completeLambda.ino|less + * + */ + +#include +#include +#include + +#if defined(ESP32) +#include +#include +#else +// esp8266 +#include +#include +#include +#ifndef CORE_MOCK +#ifndef MMU_IRAM_HEAP +#warning Try MMU option '2nd heap shared' in 'tools' IDE menu (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#option-summary) +#warning use decorators: { HeapSelectIram doAllocationsInIRAM; ESPUI.addControl(...) ... } (cf. https://arduino-esp8266.readthedocs.io/en/latest/mmu.html#how-to-select-heap) +#warning then check http:///heap +#endif // MMU_IRAM_HEAP +#ifndef DEBUG_ESP_OOM +#error on ESP8266 and ESPUI, you must define OOM debug option when developping +#endif +#endif +#endif + +//Settings +#define SLOW_BOOT 0 +#define HOSTNAME "ESPUITest" +#define FORCE_USE_HOTSPOT 0 + + +//Function Prototypes +void connectWifi(); +void setUpUI(); +void textCallback(Control *sender, int type); +void generalCallback(Control *sender, int type); +void randomString(char *buf, int len); +void paramCallback(Control* sender, int type, int param); + +//UI handles +uint16_t wifi_ssid_text, wifi_pass_text; +uint16_t mainLabel, mainSwitcher, mainSlider, mainText, mainNumber, mainScrambleButton, mainTime; +uint16_t styleButton, styleLabel, styleSwitcher, styleSlider, styleButton2, styleLabel2, styleSlider2; +uint16_t graph; +volatile bool updates = false; + + + +// This is the main function which builds our GUI +void setUpUI() { + +#ifdef ESP8266 + { HeapSelectIram doAllocationsInIRAM; +#endif + + //Turn off verbose debugging + ESPUI.setVerbosity(Verbosity::Quiet); + + //Make sliders continually report their position as they are being dragged. + ESPUI.sliderContinuous = true; + + //This GUI is going to be a tabbed GUI, so we are adding most controls using ESPUI.addControl + //which allows us to set a parent control. If we didn't need tabs we could use the simpler add + //functions like: + // ESPUI.button() + // ESPUI.label() + + + /* + * Tab: Basic Controls + * This tab contains all the basic ESPUI controls, and shows how to read and update them at runtime. + *-----------------------------------------------------------------------------------------------------------*/ + auto maintab = ESPUI.addControl(Tab, "", "Basic controls"); + + ESPUI.addControl(Separator, "General controls", "", None, maintab); + ESPUI.addControl(Button, "Button", "Button 1", Alizarin, maintab, [](Control *sender, int type){ paramCallback(sender, type, 19); }); + mainLabel = ESPUI.addControl(Label, "Label", "Label text", Emerald, maintab, generalCallback); + mainSwitcher = ESPUI.addControl(Switcher, "Switcher", "", Sunflower, maintab, generalCallback); + + //Sliders default to being 0 to 100, but if you want different limits you can add a Min and Max control + mainSlider = ESPUI.addControl(Slider, "Slider", "200", Turquoise, maintab, generalCallback); + ESPUI.addControl(Min, "", "10", None, mainSlider); + ESPUI.addControl(Max, "", "400", None, mainSlider); + + //These are the values for the selector's options. (Note that they *must* be declared static + //so that the storage is allocated in global memory and not just on the stack of this function.) + static String optionValues[] {"Value 1", "Value 2", "Value 3", "Value 4", "Value 5"}; + auto mainselector = ESPUI.addControl(Select, "Selector", "Selector", Wetasphalt, maintab, generalCallback); + for(auto const& v : optionValues) { + ESPUI.addControl(Option, v.c_str(), v, None, mainselector); + } + + mainText = ESPUI.addControl(Text, "Text Input", "Initial value", Alizarin, maintab, generalCallback); + + //Number inputs also accept Min and Max components, but you should still validate the values. + mainNumber = ESPUI.addControl(Number, "Number Input", "42", Emerald, maintab, generalCallback); + ESPUI.addControl(Min, "", "10", None, mainNumber); + ESPUI.addControl(Max, "", "50", None, mainNumber); + + ESPUI.addControl(Separator, "Updates", "", None, maintab); + + //This button will update all the updatable controls on this tab to random values + mainScrambleButton = ESPUI.addControl(Button, "Scramble Values", "Scramble Values", Carrot, maintab, + //This callback updates the "values" of a bunch of controls + [](Control *sender, int type) { + static char rndString1[10]; + static char rndString2[20]; + static bool scText = false; + + if(type == B_UP) { //Button callbacks generate events for both UP and DOWN. + //Generate some random text + randomString(rndString1, 10); + randomString(rndString2, 20); + + //Set the various controls to random value to show how controls can be updated at runtime + ESPUI.updateLabel(mainLabel, String(rndString1)); + ESPUI.updateSwitcher(mainSwitcher, ESPUI.getControl(mainSwitcher)->value.toInt() ? false : true); + ESPUI.updateSlider(mainSlider, random(10, 400)); + ESPUI.updateText(mainText, String(rndString2)); + ESPUI.updateNumber(mainNumber, random(100000)); + ESPUI.updateButton(mainScrambleButton, scText ? "Scrambled!" : "Scrambled."); + scText = !scText; + } + }); + + ESPUI.addControl(Switcher, "Constant updates", "0", Carrot, maintab, + [](Control *sender, int type) { + updates = (sender->value.toInt() > 0); + }); + + mainTime = ESPUI.addControl(Time, "", "", None, 0, generalCallback); + + ESPUI.addControl(Button, "Get Time", "Get Time", Carrot, maintab, + [](Control *sender, int type) { + if(type == B_UP) { + ESPUI.updateTime(mainTime); + } + }); + + ESPUI.addControl(Separator, "Control Pads", "", None, maintab); + ESPUI.addControl(Pad, "Normal", "", Peterriver, maintab, generalCallback); + ESPUI.addControl(PadWithCenter, "With center", "", Peterriver, maintab, generalCallback); + + + /* + * Tab: Colours + * This tab shows all the basic colours + *-----------------------------------------------------------------------------------------------------------*/ + auto colourtab = ESPUI.addControl(Tab, "", "Colours"); + ESPUI.addControl(Button, "Alizarin", "Alizarin", Alizarin, colourtab, generalCallback); + ESPUI.addControl(Button, "Turquoise", "Turquoise", Turquoise, colourtab, generalCallback); + ESPUI.addControl(Button, "Emerald", "Emerald", Emerald, colourtab, generalCallback); + ESPUI.addControl(Button, "Peterriver", "Peterriver", Peterriver, colourtab, generalCallback); + ESPUI.addControl(Button, "Wetasphalt", "Wetasphalt", Wetasphalt, colourtab, generalCallback); + ESPUI.addControl(Button, "Sunflower", "Sunflower", Sunflower, colourtab, generalCallback); + ESPUI.addControl(Button, "Carrot", "Carrot", Carrot, colourtab, generalCallback); + ESPUI.addControl(Button, "Dark", "Dark", Dark, colourtab, generalCallback); + + + /* + * Tab: Styled controls + * This tab shows off how inline CSS styles can be applied to elements and panels in order + * to customise the look of the UI. + *-----------------------------------------------------------------------------------------------------------*/ + auto styletab = ESPUI.addControl(Tab, "", "Styled controls"); + styleButton = ESPUI.addControl(Button, "Styled Button", "Button", Alizarin, styletab, generalCallback); + styleLabel = ESPUI.addControl(Label, "Styled Label", "This is a label", Alizarin, styletab, generalCallback); + styleSwitcher = ESPUI.addControl(Switcher, "Styled Switcher", "1", Alizarin, styletab, generalCallback); + styleSlider = ESPUI.addControl(Slider, "Styled Slider", "0", Alizarin, styletab, generalCallback); + + //This button will randomise the colours of the above controls to show updating of inline styles + ESPUI.addControl(Button, "Randomise Colours", "Randomise Colours", Sunflower, styletab, + //This callback generates and applies inline styles to a bunch of controls to change their colour. + //The styles created are of the form: + // "border-bottom: #999 3px solid; background-color: #aabbcc;" + // "background-color: #aabbcc;" + [](Control *sender, int type) { + //Declare space for style strings. These have to be static so that they are always available + //to the websocket layer. If we'd not made them static they'd be allocated on the heap and + //will be unavailable when we leave this function. + static char stylecol1[60], stylecol2[30]; + if(type == B_UP) { + //Generate two random HTML hex colour codes, and print them into CSS style rules + sprintf(stylecol1, "border-bottom: #999 3px solid; background-color: #%06X;", (unsigned int) random(0x0, 0xFFFFFF)); + sprintf(stylecol2, "background-color: #%06X;", (unsigned int) random(0x0, 0xFFFFFF)); + + //Apply those styles to various elements to show how controls react to styling + ESPUI.setPanelStyle(styleButton, stylecol1); + ESPUI.setElementStyle(styleButton, stylecol2); + ESPUI.setPanelStyle(styleLabel, stylecol1); + ESPUI.setElementStyle(styleLabel, stylecol2); + ESPUI.setPanelStyle(styleSwitcher, stylecol1); + ESPUI.setElementStyle(styleSwitcher, stylecol2); + ESPUI.setPanelStyle(styleSlider, stylecol1); + ESPUI.setElementStyle(styleSlider, stylecol2); + } + }); + + ESPUI.addControl(Separator, "Other styling examples", "", None, styletab); + styleButton2 = ESPUI.addControl(Button, "Styled Button", "Button", Alizarin, styletab, generalCallback); + ESPUI.setPanelStyle(styleButton2, "background: linear-gradient(90deg, rgba(131,58,180,1) 0%, rgba(253,29,29,1) 50%, rgba(252,176,69,1) 100%); border-bottom: #555;"); + ESPUI.setElementStyle(styleButton2, "border-radius: 2em; border: 3px solid black; width: 30%; background-color: #8df;"); + + styleSlider2 = ESPUI.addControl(Slider, "Styled Slider", "0", Dark, styletab, generalCallback); + ESPUI.setElementStyle(styleSlider2, "background: linear-gradient(to right, red, orange, yellow, green, blue);"); + + styleLabel2 = ESPUI.addControl(Label, "Styled Label", "This is a label", Dark, styletab, generalCallback); + ESPUI.setElementStyle(styleLabel2, "text-shadow: 3px 3px #74b1ff, 6px 6px #c64ad7; font-size: 60px; font-variant-caps: small-caps; background-color: unset; color: #c4f0bb; -webkit-text-stroke: 1px black;"); + + + /* + * Tab: Grouped controls + * This tab shows how multiple control can be grouped into the same panel through the use of the + * parentControl value. This also shows how to add labels to grouped controls, and how to use vertical controls. + *-----------------------------------------------------------------------------------------------------------*/ + auto grouptab = ESPUI.addControl(Tab, "", "Grouped controls"); + + //The parent of this button is a tab, so it will create a new panel with one control. + auto groupbutton = ESPUI.addControl(Button, "Button Group", "Button A", Dark, grouptab, generalCallback); + //However the parent of this button is another control, so therefore no new panel is + //created and the button is added to the existing panel. + ESPUI.addControl(Button, "", "Button B", Alizarin, groupbutton, generalCallback); + ESPUI.addControl(Button, "", "Button C", Alizarin, groupbutton, generalCallback); + + + //Sliders can be grouped as well + //To label each slider in the group, we are going add additional labels and give them custom CSS styles + //We need this CSS style rule, which will remove the label's background and ensure that it takes up the entire width of the panel + String clearLabelStyle = "background-color: unset; width: 100%;"; + //First we add the main slider to create a panel + auto groupsliders = ESPUI.addControl(Slider, "Slider Group", "10", Dark, grouptab, generalCallback); + //Then we add a label and set its style to the clearLabelStyle. Here we've just given it the name "A" + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, groupsliders), clearLabelStyle); + //We can now continue to add additional sliders and labels + ESPUI.addControl(Slider, "", "20", None, groupsliders, generalCallback); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, groupsliders), clearLabelStyle); + ESPUI.addControl(Slider, "", "30", None, groupsliders, generalCallback); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, groupsliders), clearLabelStyle); + + //We can also usefully group switchers. + auto groupswitcher = ESPUI.addControl(Switcher, "Switcher Group", "0", Dark, grouptab, generalCallback); + ESPUI.addControl(Switcher, "", "1", Sunflower, groupswitcher, generalCallback); + ESPUI.addControl(Switcher, "", "0", Sunflower, groupswitcher, generalCallback); + ESPUI.addControl(Switcher, "", "1", Sunflower, groupswitcher, generalCallback); + //To label these switchers we need to first go onto a "new line" below the line of switchers + //To do this we add an empty label set to be clear and full width (with our clearLabelStyle) + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "", None, groupswitcher), clearLabelStyle); + //We will now need another label style. This one sets its width to the same as a switcher (and turns off the background) + String switcherLabelStyle = "width: 60px; margin-left: .3rem; margin-right: .3rem; background-color: unset;"; + //We can now just add the styled labels. + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, groupswitcher), switcherLabelStyle); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, groupswitcher), switcherLabelStyle); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, groupswitcher), switcherLabelStyle); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "D", None, groupswitcher), switcherLabelStyle); + + //You can mix and match different control types, but the results might sometimes + //need additional styling to lay out nicely. + auto grouplabel = ESPUI.addControl(Label, "Mixed Group", "Main label", Dark, grouptab); + auto grouplabel2 = ESPUI.addControl(Label, "", "Secondary label", Emerald, grouplabel); + ESPUI.addControl(Button, "", "Button D", Alizarin, grouplabel, generalCallback); + ESPUI.addControl(Switcher, "", "1", Sunflower, grouplabel, generalCallback); + ESPUI.setElementStyle(grouplabel2, "font-size: x-large; font-family: serif;"); + + //Some controls can even support vertical orientation, currently Switchers and Sliders + ESPUI.addControl(Separator, "Vertical controls", "", None, grouptab); + auto vertgroupswitcher = ESPUI.addControl(Switcher, "Vertical Switcher Group", "0", Dark, grouptab, generalCallback); + ESPUI.setVertical(vertgroupswitcher); + //On the following lines we wrap the value returned from addControl and send it straight to setVertical + ESPUI.setVertical(ESPUI.addControl(Switcher, "", "0", None, vertgroupswitcher, generalCallback)); + ESPUI.setVertical(ESPUI.addControl(Switcher, "", "0", None, vertgroupswitcher, generalCallback)); + ESPUI.setVertical(ESPUI.addControl(Switcher, "", "0", None, vertgroupswitcher, generalCallback)); + //The mechanism for labelling vertical switchers is the same as we used above for horizontal ones + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "", None, vertgroupswitcher), clearLabelStyle); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, vertgroupswitcher), switcherLabelStyle); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, vertgroupswitcher), switcherLabelStyle); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, vertgroupswitcher), switcherLabelStyle); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "D", None, vertgroupswitcher), switcherLabelStyle); + + auto vertgroupslider = ESPUI.addControl(Slider, "Vertical Slider Group", "15", Dark, grouptab, generalCallback); + ESPUI.setVertical(vertgroupslider); + ESPUI.setVertical(ESPUI.addControl(Slider, "", "25", None, vertgroupslider, generalCallback)); + ESPUI.setVertical(ESPUI.addControl(Slider, "", "35", None, vertgroupslider, generalCallback)); + ESPUI.setVertical(ESPUI.addControl(Slider, "", "45", None, vertgroupslider, generalCallback)); + //The mechanism for labelling vertical sliders is the same as we used above for switchers + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "", None, vertgroupslider), clearLabelStyle); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "A", None, vertgroupslider), switcherLabelStyle); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "B", None, vertgroupslider), switcherLabelStyle); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "C", None, vertgroupslider), switcherLabelStyle); + ESPUI.setElementStyle(ESPUI.addControl(Label, "", "D", None, vertgroupslider), switcherLabelStyle); + + //Note that combining vertical and horizontal sliders is going to result in very messy layout! + + /* + * Tab: Example UI + * An example UI for the documentation + *-----------------------------------------------------------------------------------------------------------*/ + auto exampletab = ESPUI.addControl(Tab, "Example", "Example"); + ESPUI.addControl(Separator, "Control and Status", "", None, exampletab); + ESPUI.addControl(Switcher, "Power", "1", Alizarin, exampletab, generalCallback); + ESPUI.addControl(Label, "Status", "System status: OK", Wetasphalt, exampletab, generalCallback); + + ESPUI.addControl(Separator, "Settings", "", None, exampletab); + ESPUI.addControl(PadWithCenter, "Attitude Control", "", Dark, exampletab, generalCallback); + auto examplegroup1 = ESPUI.addControl(Button, "Activate Features", "Feature A", Carrot, exampletab, generalCallback); + ESPUI.addControl(Button, "Activate Features", "Feature B", Carrot, examplegroup1, generalCallback); + ESPUI.addControl(Button, "Activate Features", "Feature C", Carrot, examplegroup1, generalCallback); + ESPUI.addControl(Slider, "Value control", "45", Peterriver, exampletab, generalCallback); + + /* + * Tab: WiFi Credentials + * You use this tab to enter the SSID and password of a wifi network to autoconnect to. + *-----------------------------------------------------------------------------------------------------------*/ + auto wifitab = ESPUI.addControl(Tab, "", "WiFi Credentials"); + wifi_ssid_text = ESPUI.addControl(Text, "SSID", "", Alizarin, wifitab, textCallback); + //Note that adding a "Max" control to a text control sets the max length + ESPUI.addControl(Max, "", "32", None, wifi_ssid_text); + wifi_pass_text = ESPUI.addControl(Text, "Password", "", Alizarin, wifitab, textCallback); + ESPUI.addControl(Max, "", "64", None, wifi_pass_text); + ESPUI.addControl(Button, "Save", "Save", Peterriver, wifitab, + [](Control *sender, int type) { + if(type == B_UP) { + Serial.println("Saving credentials to EPROM..."); + Serial.println(ESPUI.getControl(wifi_ssid_text)->value); + Serial.println(ESPUI.getControl(wifi_pass_text)->value); + unsigned int i; + EEPROM.begin(100); + for(i = 0; i < ESPUI.getControl(wifi_ssid_text)->value.length(); i++) { + EEPROM.write(i, ESPUI.getControl(wifi_ssid_text)->value.charAt(i)); + if(i==30) break; //Even though we provided a max length, user input should never be trusted + } + EEPROM.write(i, '\0'); + + for(i = 0; i < ESPUI.getControl(wifi_pass_text)->value.length(); i++) { + EEPROM.write(i + 32, ESPUI.getControl(wifi_pass_text)->value.charAt(i)); + if(i==94) break; //Even though we provided a max length, user input should never be trusted + } + EEPROM.write(i + 32, '\0'); + EEPROM.end(); + } + }); + + + //Finally, start up the UI. + //This should only be called once we are connected to WiFi. + ESPUI.begin(HOSTNAME); + +#ifdef ESP8266 + } // HeapSelectIram +#endif + +} + + +//Most elements in this test UI are assigned this generic callback which prints some +//basic information. Event types are defined in ESPUI.h +void generalCallback(Control *sender, int type) { + Serial.print("CB: id("); + Serial.print(sender->id); + Serial.print(") Type("); + Serial.print(type); + Serial.print(") '"); + Serial.print(sender->label); + Serial.print("' = "); + Serial.println(sender->value); +} + +// Most elements in this test UI are assigned this generic callback which prints some +// basic information. Event types are defined in ESPUI.h +// The extended param can be used to pass additional information +void paramCallback(Control* sender, int type, int param) +{ + Serial.print("CB: id("); + Serial.print(sender->id); + Serial.print(") Type("); + Serial.print(type); + Serial.print(") '"); + Serial.print(sender->label); + Serial.print("' = "); + Serial.println(sender->value); + Serial.print("param = "); + Serial.println(param); +} + +void setup() { + randomSeed(0); + Serial.begin(115200); + while(!Serial); + if(SLOW_BOOT) delay(5000); //Delay booting to give time to connect a serial monitor + connectWifi(); + #if defined(ESP32) + WiFi.setSleep(false); //For the ESP32: turn off sleeping to increase UI responsivness (at the cost of power use) + #endif + setUpUI(); +} + +void loop() { + static long unsigned lastTime = 0; + + //Send periodic updates if switcher is turned on + if(updates && millis() > lastTime + 500) { + static uint16_t sliderVal = 10; + + //Flick this switcher on and off + ESPUI.updateSwitcher(mainSwitcher, ESPUI.getControl(mainSwitcher)->value.toInt() ? false : true); + sliderVal += 10; + if(sliderVal > 400) sliderVal = 10; + + //Sliders, numbers, and labels can all be updated at will + ESPUI.updateSlider(mainSlider, sliderVal); + ESPUI.updateNumber(mainNumber, random(100000)); + ESPUI.updateLabel(mainLabel, String(sliderVal)); + lastTime = millis(); + } + + //Simple debug UART interface + if(Serial.available()) { + switch(Serial.read()) { + case 'w': //Print IP details + Serial.println(WiFi.localIP()); + break; + case 'W': //Reconnect wifi + connectWifi(); + break; + case 'C': //Force a crash (for testing exception decoder) + #if !defined(ESP32) + ((void (*)())0xf00fdead)(); + #endif + break; + default: + Serial.print('#'); + break; + } + } + + #if !defined(ESP32) + //We don't need to call this explicitly on ESP32 but we do on 8266 + MDNS.update(); + #endif + +} + + + + +//Utilities +// +//If you are here just to see examples of how to use ESPUI, you can ignore the following functions +//------------------------------------------------------------------------------------------------ +void readStringFromEEPROM(String& buf, int baseaddress, int size) { + buf.reserve(size); + for (int i = baseaddress; i < baseaddress+size; i++) { + char c = EEPROM.read(i); + buf += c; + if(!c) break; + } +} + +void connectWifi() { + int connect_timeout; + +#if defined(ESP32) + WiFi.setHostname(HOSTNAME); +#else + WiFi.hostname(HOSTNAME); +#endif + Serial.println("Begin wifi..."); + + //Load credentials from EEPROM + if(!(FORCE_USE_HOTSPOT)) { + yield(); + EEPROM.begin(100); + String stored_ssid, stored_pass; + readStringFromEEPROM(stored_ssid, 0, 32); + readStringFromEEPROM(stored_pass, 32, 96); + EEPROM.end(); + + //Try to connect with stored credentials, fire up an access point if they don't work. + #if defined(ESP32) + WiFi.begin(stored_ssid.c_str(), stored_pass.c_str()); + #else + WiFi.begin(stored_ssid, stored_pass); + #endif + connect_timeout = 28; //7 seconds + while (WiFi.status() != WL_CONNECTED && connect_timeout > 0) { + delay(250); + Serial.print("."); + connect_timeout--; + } + } + + if (WiFi.status() == WL_CONNECTED) { + Serial.println(WiFi.localIP()); + Serial.println("Wifi started"); + + if (!MDNS.begin(HOSTNAME)) { + Serial.println("Error setting up MDNS responder!"); + } + } else { + Serial.println("\nCreating access point..."); + WiFi.mode(WIFI_AP); + WiFi.softAPConfig(IPAddress(192, 168, 1, 1), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0)); + WiFi.softAP(HOSTNAME); + + connect_timeout = 20; + do { + delay(250); + Serial.print(","); + connect_timeout--; + } while(connect_timeout); + } +} + + +void textCallback(Control *sender, int type) { + //This callback is needed to handle the changed values, even though it doesn't do anything itself. +} + +void randomString(char *buf, int len) { + for(auto i = 0; i < len-1; i++) + buf[i] = random(0, 26) + 'A'; + buf[len-1] = '\0'; +} diff --git a/src/ESPUI.cpp b/src/ESPUI.cpp index 3223898..e9df803 100644 --- a/src/ESPUI.cpp +++ b/src/ESPUI.cpp @@ -627,27 +627,25 @@ uint16_t ESPUIClass::addControl(ControlType type, const char* label, const Strin uint16_t ESPUIClass::addControl( ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl) { - return addControl(type, label, value, color, parentControl, nullptr); + return addControl(type, label, value, color, parentControl, new Control(type, label, nullptr, value, color, true, parentControl)); } uint16_t ESPUIClass::addControl(ControlType type, const char* label, const String& value, ControlColor color, - uint16_t parentControl, void (*callback)(Control*, int)) + uint16_t parentControl, std::function callback) { - uint16_t id = addControl(type, label, value, color, parentControl, nullptr, nullptr); + uint16_t id = addControl(type, label, value, color, parentControl); // set the original style callback getControl(id)->callback = callback; return id; } -uint16_t ESPUIClass::addControl(ControlType type, const char* label, const String& value, ControlColor color, - uint16_t parentControl, void (*callback)(Control*, int, void*), void* UserData) +uint16_t ESPUIClass::addControl( + ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, Control* control) { #ifdef ESP32 xSemaphoreTake(ControlsSemaphore, portMAX_DELAY); #endif // def ESP32 - Control* control = new Control(type, label, callback, UserData, value, color, true, parentControl); - if (controls == nullptr) { controls = control; @@ -753,70 +751,37 @@ uint16_t ESPUIClass::graph(const char* label, ControlColor color) } uint16_t ESPUIClass::slider( - const char* label, void (*callback)(Control*, int), ControlColor color, int value, int min, int max) -{ - uint16_t id = slider(label, nullptr, color, value, min, max, nullptr); - getControl(id)->callback = callback; - return id; -} - -uint16_t ESPUIClass::slider(const char* label, void (*callback)(Control*, int, void*), ControlColor color, int value, - int min, int max, void* userData) + const char* label, std::function callback, ControlColor color, int value, int min, int max) { uint16_t sliderId - = addControl(ControlType::Slider, label, String(value), color, Control::noParent, callback, userData); + = addControl(ControlType::Slider, label, String(value), color, Control::noParent, callback); addControl(ControlType::Min, label, String(min), ControlColor::None, sliderId); addControl(ControlType::Max, label, String(max), ControlColor::None, sliderId); - return sliderId; } -uint16_t ESPUIClass::button(const char* label, void (*callback)(Control*, int), ControlColor color, const String& value) +uint16_t ESPUIClass::button(const char* label, std::function callback, ControlColor color, const String& value) { return addControl(ControlType::Button, label, value, color, Control::noParent, callback); } -uint16_t ESPUIClass::button( - const char* label, void (*callback)(Control*, int, void*), ControlColor color, const String& value, void* UserData) -{ - return addControl(ControlType::Button, label, value, color, Control::noParent, callback, UserData); -} - -uint16_t ESPUIClass::switcher(const char* label, void (*callback)(Control*, int), ControlColor color, bool startState) +uint16_t ESPUIClass::switcher(const char* label, std::function callback, ControlColor color, bool startState) { return addControl(ControlType::Switcher, label, startState ? "1" : "0", color, Control::noParent, callback); } -uint16_t ESPUIClass::switcher( - const char* label, void (*callback)(Control*, int, void*), ControlColor color, bool startState, void* UserData) -{ - return addControl( - ControlType::Switcher, label, startState ? "1" : "0", color, Control::noParent, callback, UserData); -} - -uint16_t ESPUIClass::pad(const char* label, void (*callback)(Control*, int), ControlColor color) +uint16_t ESPUIClass::pad(const char* label, std::function callback, ControlColor color) { return addControl(ControlType::Pad, label, "", color, Control::noParent, callback); } -uint16_t ESPUIClass::pad(const char* label, void (*callback)(Control*, int, void*), ControlColor color, void* UserData) -{ - return addControl(ControlType::Pad, label, "", color, Control::noParent, callback, UserData); -} - -uint16_t ESPUIClass::padWithCenter(const char* label, void (*callback)(Control*, int), ControlColor color) +uint16_t ESPUIClass::padWithCenter(const char* label, std::function callback, ControlColor color) { return addControl(ControlType::PadWithCenter, label, "", color, Control::noParent, callback); } -uint16_t ESPUIClass::padWithCenter( - const char* label, void (*callback)(Control*, int, void*), ControlColor color, void* UserData) -{ - return addControl(ControlType::PadWithCenter, label, "", color, Control::noParent, callback, UserData); -} - uint16_t ESPUIClass::number( - const char* label, void (*callback)(Control*, int), ControlColor color, int number, int min, int max) + const char* label, std::function callback, ControlColor color, int number, int min, int max) { uint16_t numberId = addControl(ControlType::Number, label, String(number), color, Control::noParent, callback); addControl(ControlType::Min, label, String(min), ControlColor::None, numberId); @@ -824,16 +789,6 @@ uint16_t ESPUIClass::number( return numberId; } -uint16_t ESPUIClass::number(const char* label, void (*callback)(Control*, int, void*), ControlColor color, int number, - int min, int max, void* UserData) -{ - uint16_t numberId - = addControl(ControlType::Number, label, String(number), color, Control::noParent, callback, UserData); - addControl(ControlType::Min, label, String(min), ControlColor::None, numberId); - addControl(ControlType::Max, label, String(max), ControlColor::None, numberId); - return numberId; -} - uint16_t ESPUIClass::gauge(const char* label, ControlColor color, int number, int min, int max) { uint16_t numberId = addControl(ControlType::Gauge, label, String(number), color, Control::noParent); @@ -847,28 +802,16 @@ uint16_t ESPUIClass::separator(const char* label) return addControl(ControlType::Separator, label, "", ControlColor::Alizarin, Control::noParent, nullptr); } -uint16_t ESPUIClass::accelerometer(const char* label, void (*callback)(Control*, int), ControlColor color) +uint16_t ESPUIClass::accelerometer(const char* label, std::function callback, ControlColor color) { return addControl(ControlType::Accel, label, "", color, Control::noParent, callback); } -uint16_t ESPUIClass::accelerometer( - const char* label, void (*callback)(Control*, int, void*), ControlColor color, void* UserData) -{ - return addControl(ControlType::Accel, label, "", color, Control::noParent, callback, UserData); -} - -uint16_t ESPUIClass::text(const char* label, void (*callback)(Control*, int), ControlColor color, const String& value) +uint16_t ESPUIClass::text(const char* label, std::function callback, ControlColor color, const String& value) { return addControl(ControlType::Text, label, value, color, Control::noParent, callback); } -uint16_t ESPUIClass::text( - const char* label, void (*callback)(Control*, int, void*), ControlColor color, const String& value, void* UserData) -{ - return addControl(ControlType::Text, label, value, color, Control::noParent, callback, UserData); -} - Control* ESPUIClass::getControl(uint16_t id) { #ifdef ESP32 diff --git a/src/ESPUI.h b/src/ESPUI.h index 43464fe..3b80de7 100644 --- a/src/ESPUI.h +++ b/src/ESPUI.h @@ -125,33 +125,19 @@ public: uint16_t addControl(ControlType type, const char* label, const String& value); uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color); uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl); - uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, void (*callback)(Control*, int)); - uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, void (*callback)(Control*, int, void *), void* UserData); + uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, std::function callback); bool removeControl(uint16_t id, bool force_rebuild_ui = false); // create Elements // Create Event Button - uint16_t button(const char* label, void (*callback)(Control*, int), ControlColor color, const String& value = ""); - uint16_t button(const char* label, void (*callback)(Control*, int, void*), ControlColor color, const String& value, void* UserData); - - uint16_t switcher(const char* label, void (*callback)(Control*, int), ControlColor color, bool startState = false); // Create Toggle Button - uint16_t switcher(const char* label, void (*callback)(Control*, int, void*), ControlColor color, bool startState, void* UserData); // Create Toggle Button - - uint16_t pad(const char* label, void (*callback)(Control*, int), ControlColor color); // Create Pad Control - uint16_t pad(const char* label, void (*callback)(Control*, int, void*), ControlColor color, void* UserData); // Create Pad Control - - uint16_t padWithCenter(const char* label, void (*callback)(Control*, int), ControlColor color); // Create Pad Control with Centerbutton - uint16_t padWithCenter(const char* label, void (*callback)(Control*, int, void*), ControlColor color, void* UserData); // Create Pad Control with Centerbutton - - uint16_t slider(const char* label, void (*callback)(Control*, int), ControlColor color, int value, int min = 0, int max = 100); // Create Slider Control - uint16_t slider(const char* label, void (*callback)(Control*, int, void*), ControlColor color, int value, int min, int max, void* UserData); // Create Slider Control - - uint16_t number(const char* label, void (*callback)(Control*, int), ControlColor color, int value, int min = 0, int max = 100); // Create a Number Input Control - uint16_t number(const char* label, void (*callback)(Control*, int, void*), ControlColor color, int value, int min, int max, void* UserData); // Create a Number Input Control - - uint16_t text(const char* label, void (*callback)(Control*, int), ControlColor color, const String& value = ""); // Create a Text Input Control - uint16_t text(const char* label, void (*callback)(Control*, int, void*), ControlColor color, const String& value, void* UserData); // Create a Text Input Control + uint16_t button(const char* label, std::function callback, ControlColor color, const String& value = ""); + uint16_t switcher(const char* label, std::function callback, ControlColor color, bool startState = false); // Create Toggle Button + uint16_t pad(const char* label, std::function callback, ControlColor color); // Create Pad Control + uint16_t padWithCenter(const char* label, std::function callback, ControlColor color); // Create Pad Control with Centerbutton + uint16_t slider(const char* label, std::function callback, ControlColor color, int value, int min = 0, int max = 100); // Create Slider Control + uint16_t number(const char* label, std::function callback, ControlColor color, int value, int min = 0, int max = 100); // Create a Number Input Control + uint16_t text(const char* label, std::function callback, ControlColor color, const String& value = ""); // Create a Text Input Control // Output only uint16_t label(const char* label, ControlColor color, @@ -162,8 +148,7 @@ public: uint16_t separator(const char* label); //Create separator // Input only - uint16_t accelerometer(const char* label, void (*callback)(Control*, int), ControlColor color); - uint16_t accelerometer(const char* label, void (*callback)(Control*, int, void*), ControlColor color, void* UserData); + uint16_t accelerometer(const char* label, std::function callback, ControlColor color); // Update Elements @@ -213,6 +198,44 @@ public: Verbosity verbosity = Verbosity::Quiet; AsyncWebServer* server; + // emulate former extended callback API by using an intermediate lambda (no deprecation) + uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, std::function callback, void* userData) + { + return addControl(type, label, value, color, parentControl, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }); + } + uint16_t button(const char* label, std::function callback, ControlColor color, const String& value, void* userData) + { + return button(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color, value); + } + uint16_t switcher(const char* label, std::function callback, ControlColor color, bool startState, void* userData) + { + return switcher(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color, startState); + } + uint16_t pad(const char* label, std::function callback, ControlColor color, void* userData) + { + return pad(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color); + } + uint16_t padWithCenter(const char* label, std::function callback, ControlColor color, void* userData) + { + return padWithCenter(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color); + } + uint16_t slider(const char* label, std::function callback, ControlColor color, int value, int min, int max, void* userData) + { + return slider(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color, value, min, max); + } + uint16_t number(const char* label, std::function callback, ControlColor color, int value, int min, int max, void* userData) + { + return number(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color, value, min, max); + } + uint16_t text(const char* label, std::function callback, ControlColor color, const String& value, void* userData) + { + return text(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); } , color, value); + } + uint16_t accelerometer(const char* label, std::function callback, ControlColor color, void* userData) + { + return accelerometer(label, [callback, userData](Control* sender, int type){ callback(sender, type, userData); }, color); + } + protected: friend class ESPUIclient; friend class ESPUIcontrol; @@ -226,6 +249,8 @@ protected: bool basicAuth = true; uint16_t controlCount = 0; + uint16_t addControl(ControlType type, const char* label, const String& value, ControlColor color, uint16_t parentControl, Control* control); + #define ClientUpdateType_t ESPUIclient::ClientUpdateType_t void NotifyClients(ClientUpdateType_t newState); void NotifyClient(uint32_t WsClientId, ClientUpdateType_t newState); diff --git a/src/ESPUIcontrol.cpp b/src/ESPUIcontrol.cpp index f276c68..8ae1686 100644 --- a/src/ESPUIcontrol.cpp +++ b/src/ESPUIcontrol.cpp @@ -3,13 +3,11 @@ static uint16_t idCounter = 0; static const String ControlError = "*** ESPUI ERROR: Could not transfer control ***"; -Control::Control(ControlType type, const char* label, void (*callback)(Control*, int, void*), void* UserData, +Control::Control(ControlType type, const char* label, std::function callback, const String& value, ControlColor color, bool visible, uint16_t parentControl) : type(type), label(label), - callback(nullptr), - extendedCallback(callback), - user(UserData), + callback(callback), value(value), color(color), visible(visible), @@ -27,8 +25,6 @@ Control::Control(const Control& Control) id(Control.id), label(Control.label), callback(Control.callback), - extendedCallback(Control.extendedCallback), - user(Control.user), value(Control.value), color(Control.color), visible(Control.visible), @@ -42,17 +38,11 @@ void Control::SendCallback(int type) { callback(this, type); } - - if (extendedCallback) - { - extendedCallback(this, type, user); - } } void Control::DeleteControl() { ControlSyncState = ControlSyncState_t::deleted; - extendedCallback = nullptr; callback = nullptr; } diff --git a/src/ESPUIcontrol.h b/src/ESPUIcontrol.h index d47c074..a95b153 100644 --- a/src/ESPUIcontrol.h +++ b/src/ESPUIcontrol.h @@ -2,6 +2,7 @@ #include #include +#include enum ControlType : uint8_t { @@ -53,9 +54,7 @@ public: ControlType type; uint16_t id; // just mirroring the id here for practical reasons const char* label; - void (*callback)(Control*, int); - void (*extendedCallback)(Control*, int, void*); - void* user; + std::function callback; String value; ControlColor color; bool visible; @@ -72,8 +71,7 @@ public: Control(ControlType type, const char* label, - void (*callback)(Control*, int, void*), - void* UserData, + std::function callback, const String& value, ControlColor color, bool visible, @@ -82,7 +80,7 @@ public: Control(const Control& Control); void SendCallback(int type); - bool HasCallback() { return ((nullptr != callback) || (nullptr != extendedCallback)); } + bool HasCallback() { return (nullptr != callback); } void MarshalControl(ArduinoJson::JsonObject& item, bool refresh); void MarshalErrorMessage(ArduinoJson::JsonObject& item); bool ToBeDeleted() { return (ControlSyncState_t::deleted == ControlSyncState); }