├── .gitattributes ├── .github └── workflows │ └── compile.yaml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── flasher ├── firmware │ ├── README │ ├── boot.bin │ ├── bootloader.bin │ ├── manifest.json │ └── partitions.bin └── index.html ├── include └── README ├── lib ├── README └── WebConfig │ ├── .piopm │ ├── LICENSE │ ├── README.md │ ├── examples │ ├── demo32 │ │ └── demo32.ino │ └── demo8266 │ │ └── demo8266.ino │ ├── keywords.txt │ ├── library.properties │ └── src │ ├── WebConfig.cpp │ └── WebConfig.h ├── platformio.ini ├── png2bin.zip └── src ├── ButtonManager.cpp ├── ButtonManager.h ├── MqttManager.cpp ├── MqttManager.h ├── SystemManager.cpp ├── SystemManager.h ├── font.h ├── images.h └── main.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/compile.yaml: -------------------------------------------------------------------------------- 1 | name: Compile 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | compile_firmware: 10 | name: Compile the firmware 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out the repository 14 | uses: actions/checkout@v3 15 | 16 | # See PlatformIO documentation regarding Github Actions 17 | - name: Cache pip 18 | uses: actions/cache@v2 19 | with: 20 | path: ~/.cache/pip 21 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 22 | restore-keys: | 23 | ${{ runner.os }}-pip- 24 | 25 | - name: Cache PlatformIO 26 | uses: actions/cache@v2 27 | with: 28 | path: ~/.platformio 29 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 30 | 31 | - name: Set up Python 32 | uses: actions/setup-python@v2 33 | 34 | - name: Install PlatformIO 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install --upgrade platformio 38 | 39 | - name: Install jo 40 | run: sudo apt-get -y install jq 41 | 42 | - name: Extract version number 43 | id: version_number 44 | run: | 45 | echo "::set-output name=library_version::$(grep '*VERSION' src/SystemManager.cpp | cut -d'=' -f2 | sed -r 's/"(.*)";/\1/' | awk '{$1=$1};1')" 46 | 47 | - name: Run PlatformIO 48 | run: pio run 49 | 50 | - name: Move firmware to output directory 51 | run: mv .pio/build/nodemcu-32s/firmware.bin ./flasher/firmware 52 | 53 | - name: Create new manifest.json 54 | run: cat flasher/firmware/manifest.json | jq '.version="${{ steps.version_number.outputs.library_version }}"' > flasher/firmware/new-manifest.json 55 | 56 | - name: Override previous manifest.json 57 | run: mv flasher/firmware/new-manifest.json flasher/firmware/manifest.json 58 | 59 | - name: Deploy to GitHub Pages 60 | uses: JamesIves/github-pages-deploy-action@v4 61 | with: 62 | folder: flasher 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | src/config.h 7 | src/config.h 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "array": "cpp", 4 | "*.tcc": "cpp", 5 | "functional": "cpp", 6 | "istream": "cpp", 7 | "tuple": "cpp", 8 | "utility": "cpp", 9 | "deque": "cpp", 10 | "string": "cpp", 11 | "unordered_map": "cpp", 12 | "unordered_set": "cpp", 13 | "vector": "cpp", 14 | "string_view": "cpp", 15 | "initializer_list": "cpp", 16 | "new": "cpp", 17 | "random": "cpp", 18 | "typeinfo": "cpp", 19 | "algorithm": "cpp" 20 | } 21 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## SmartPusher 2 | 3 | ![IMG_3486-2](https://user-images.githubusercontent.com/31169771/188873870-f90f919c-b7dd-423f-8c04-c9f61583a24c.jpg) 4 | 5 | Bored with smartphones and voice control? 6 | Go back to the roots with real buttons to control your smarthome! 7 | 8 | SmartPusher is a wifi enabled button array wich lets you control your smarthome via MQTT in different ways. 9 | Functions: 10 | - Click 11 | - LongClick 12 | - DoubleClick 13 | - Down and Up 14 | - Fancy light ring animations 15 | - OLED screen 16 | 17 | ## HowTo 18 | 19 | 20 | **Wiki** 21 | https://github.com/Blueforcer/SmartPusher/wiki 22 | 23 | **Discord** 24 | https://discord.gg/AbGVY4cG9e 25 | 26 | ## Support 27 | If you like this work, [please consider supporting this project!](https://paypal.me/blueforcer) 28 | 29 | ## Author 30 | 31 | Stephan Mühl, 2022 32 | 33 | ## License 34 | 35 | [Creative Commons Attribution-NonCommercial-ShareAlike (CC-BY-NC-SA)](http://creativecommons.org/licenses/by-nc-sa/4.0/). 36 | 37 | Free for personal use. Contact me in other cases (`admin@blueforcer.de`). 38 | -------------------------------------------------------------------------------- /flasher/firmware/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /flasher/firmware/boot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blueforcer/SmartPusher/c92c789723b336f6819856090d66a339cb49cd81/flasher/firmware/boot.bin -------------------------------------------------------------------------------- /flasher/firmware/bootloader.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blueforcer/SmartPusher/c92c789723b336f6819856090d66a339cb49cd81/flasher/firmware/bootloader.bin -------------------------------------------------------------------------------- /flasher/firmware/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SmartPusher", 3 | "version": "1.7", 4 | "home_assistant_domain": "SmartPusher", 5 | "funding_url": "https://blueforcer.de", 6 | "new_install_prompt_erase": true, 7 | "builds": [ 8 | { 9 | "chipFamily": "ESP32", 10 | "parts": [ 11 | { 12 | "path": "bootloader.bin", 13 | "offset": 4096 14 | }, 15 | { 16 | "path": "partitions.bin", 17 | "offset": 32768 18 | }, 19 | { 20 | "path": "boot.bin", 21 | "offset": 57344 22 | }, 23 | { 24 | "path": "firmware.bin", 25 | "offset": 65536 26 | } 27 | ] 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /flasher/firmware/partitions.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blueforcer/SmartPusher/c92c789723b336f6819856090d66a339cb49cd81/flasher/firmware/partitions.bin -------------------------------------------------------------------------------- /flasher/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SmartPusher installer 6 | 10 | 11 | 12 | 78 | 79 | 80 | 81 |
82 |

SmartPusher installer

83 | 84 |

85 | 86 |

87 | 88 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /lib/WebConfig/.piopm: -------------------------------------------------------------------------------- 1 | {"type": "library", "name": "WebConfig", "version": "1.4.1", "spec": {"owner": "gerlech", "id": 6874, "name": "WebConfig", "requirements": null, "uri": null}} -------------------------------------------------------------------------------- /lib/WebConfig/LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /lib/WebConfig/README.md: -------------------------------------------------------------------------------- 1 | # WebConfig 2 | 3 | This Arduino Library works with ESP8266 and ESP32 4 | 5 | It allows to edit configuration data by a web form. The configuration data will be storde in the SPIFFS and can be reloades from there. 6 | The web form can be free formatted using JSON. 7 | If you start an AccessPoint, you can use WebConfig to edit access information for the local Network 8 | 9 | ## Constants: 10 | 11 | maximum number of parameters 12 | **MAXVALUES 20** 13 | 14 | maximum number of options per parameters 15 | **MAXOPTIONS 15** 16 | 17 | name for the config file 18 | **CONFFILE "/WebConf.conf"** 19 | 20 | Type of HTML input fields 21 | - INPUTTEXT 0 Simple text input 22 | - INPUTPASSWORD 1 Password input showing stars 23 | - INPUTNUMBER 2 Numeric input 24 | - INPUTDATE 3 Date input 25 | - INPUTTIME 4 Time input 26 | - INPUTRANGE 5 Slider 27 | - INPUTCHECKBOX 6 Checkbox 28 | - INPUTRADIO 7 Radio buttons 29 | - INPUTSELECT 8 Drop down list 30 | - INPUTCOLOR 9 Color picker 31 | - INPUTFLOAT 10 Floatingpoint number 32 | - INPUTTEXTAREA 11 Text area with multiple lines 33 | - INPUTMULTICHECK 12 Fieldset with multiple checkboxes. The result string has one character 34 | 0 or 1 for every option. 010010 means there are 6 options and option 2 and 5 are true 35 | 36 | ## Functions 37 | 38 | **void setDescription(String parameter);** 39 | 40 | load form descriptions create the internal structure from JSON String, delete existing entries 41 | 42 | **void addDescription(String parameter);** 43 | 44 | add more form descriptions from JSON String 45 | 46 | **void handleFormRequest(WebServer * server, const char * filename);** 47 | 48 | function to respond a HTTP request for the form use the filename 49 | to save. 50 | 51 | **void handleFormRequest(WebServer * server);** 52 | 53 | function to respond a HTTP request for the form use the default filename 54 | to save 55 | 56 | **int16_t getIndex(const char * name);** 57 | 58 | get the index for a value by parameter name 59 | 60 | **boolean readConfig(const char * filename);** 61 | 62 | read configuration from file with filename 63 | 64 | **boolean readConfig();** 65 | 66 | read configuration from file with default filename 67 | 68 | **boolean writeConfig(const char * filename);** 69 | 70 | write configuration to file with filename 71 | 72 | **boolean writeConfig();** 73 | 74 | write configuration to file with default filename 75 | 76 | **boolean deleteConfig(const char * filename);** 77 | 78 | delete configuration file with filename 79 | 80 | **boolean deleteConfig();** 81 | 82 | delete configutation file with default filename 83 | 84 | **const String getString(const char * name);** 85 | 86 | get a parameter value as String by its name 87 | 88 | **const char * getValue(const char * name);** 89 | 90 | get a parameter value as pointer to character array by its name 91 | 92 | **const int getInt(const char * name);** 93 | 94 | get a parameter value as integer by its name 95 | 96 | **const float getFloat(const char * name);** 97 | 98 | get a parameter value as floating point number by its name 99 | 100 | **const boolean getBool(const char * name);** 101 | 102 | get a parameter value as boolean by its name 103 | 104 | **const char * getApName();** 105 | 106 | get the accesspoint name 107 | 108 | **uint8_t getCount();** 109 | 110 | get the number of parameters 111 | 112 | **String getName(uint8_t index);** 113 | 114 | get the name of the parameter with index 115 | 116 | **String values\[MAXVALUES\];** 117 | 118 | direkt access to the parameter values 119 | 120 | **String getResults();** 121 | 122 | get the values for all fields in a JSON formatted string 123 | 124 | **void setValues(String json);** 125 | 126 | preset the values for all fields out of a JSON formatted string 127 | 128 | **void setValue(const char * name,String value);** 129 | 130 | set the value of the field named name with the value from value 131 | 132 | **void setLabel(const char * name, const char* label);** 133 | 134 | set the label for the field named name with the value from label 135 | 136 | **void clearOptions(uint8_t index);** 137 | 138 | remove all options for the selection field with index index 139 | 140 | **void clearOptions(const char * name);** 141 | 142 | remove all options for the selection field with name name 143 | 144 | **void addOption(uint8_t index, String option);** 145 | 146 | add an option to the selection field with index index. The value from parameter option is uesd for label and value 147 | 148 | **void addOption(uint8_t index, String option, String label);** 149 | 150 | add an option to the selection field with index index. The value from parameter option is uesd for the value and the value from parameter label is used for the label 151 | 152 | **void setOption(uint8_t index, uint8_t option_index, String option, String label);** 153 | 154 | modify an option in the selection field with index index. The option with index option_index gets the value from parameter option and the label from parameter label 155 | 156 | **void setOption(char * name, uint8_t option_index, String option, String label);** 157 | 158 | modify an option in the selection field with name name. The option with index option_index gets the value from parameter option and the label from parameter label 159 | 160 | **uint8_t getOptionCount(uint8_t index);** 161 | 162 | returns the number of options in the selection field with index index 163 | 164 | **uint8_t getOptionCount(char * name);** 165 | 166 | returns the number of options in the selection field with name name 167 | 168 | **void setButtons(uint8 buttons);** 169 | 170 | set the type of form. With BTN_CONFIG (0) the configuration mode will be set. This form is typical used to setup WiFi access. The form has two buttons "SAVE" and "RESTART". Modifications will be saved to SPIFFS automatically. With BTN_DONE (1), BTN_CANCEL (2) and BTN_DELETE (4) simple forms will be shown, without automatic saving. The form gets the specified Buttons. The buttons can be combined. So BTN_DONE+BTN_CANCEL+BTN_DELETE shows all three buttons. To react on button clicks, callback functions can be registered. 171 | 172 | **void registerOnSave(void (\*callback)(String results));** 173 | 174 | this function will be called after the "SAVE" button was clicked. The parameter results holds a JSON formatted string with the values from all fields. 175 | 176 | **void registerOnDone(void (\*callback)(String results));** 177 | 178 | this function will be called after the "DONE" button was clicked. The parameter results holds a JSON formatted string with the values from all fields. 179 | 180 | **void registerOnCancel(void (\*callback)());** 181 | 182 | this function will be called after the "CANCEL" button was clicked. 183 | 184 | **void registerOnDelete(void (\*callback)(String name));** 185 | 186 | this function will be called after the "DELETE" button was clicked. The parameter name holds the value of the field named "name" if such a field exists. 187 | 188 | ## Parameter definition with JSON 189 | 190 | \[{ 191 | "name":"", 192 | "label":"", 193 | "type":0, 194 | "default":"" 195 | "min":0, 196 | "max":0, 197 | "options":\[ 198 | {"v":"","":""} 199 | \] 200 | }\] 201 | 202 | **name** String 203 | The name of the Parameter. This name will be used to save the parameter in the configuration file. It is also used to access the values. 204 | 205 | **label** String 206 | Defines the label for the web form 207 | 208 | **type** Integer 209 | Type of the HTML input element 210 | - INPUTTEXT Texteingabefeld 211 | - INPUTPASSWORD Passwort Eingabefeld 212 | - INPUTNUMBER Nummern Eingabefeld 213 | - INPUTDATE Datums Eingabefeld 214 | - INPUTTIME Zeit Eingabefeld 215 | - INPUTRANGE Slider zur Nummerneingabe 216 | - INPUTCHECKBOX Ja/Nein Auswahl 217 | - INPUTRADIO Mehrfachauswahl 218 | - INPUTSELECT Mehrfachauswahl aus Dropdown-Liste 219 | - INPUTCOLOR Farbauswahl 220 | - INPUTFLOAT Fließkommazahl 221 | - INPUTTEXTAREA Mehrzeiliges Textfeld 222 | - INPUTMULTICHECK Mehrere Checkboxen 223 | 224 | **default** String 225 | default value 226 | 227 | **min** Integer (optional) 228 | Minimum value for number input or columns for test area 229 | 230 | **max** Integer (optional) 231 | Maximum value for number input or rows for text area 232 | 233 | **options** List of objects (optional) 234 | A list to define options and values for multi select input fields on multi checkboxes the option name is used as label 235 | 236 | 237 | #### Example defines a JSON String with all types of input fields 238 | 239 | String params = "\[" 240 | "{" 241 | "'name':'ssid'," 242 | "'label':'WLAN name'," 243 | "'type':"+String(INPUTTEXT)+"," 244 | "'default':''" 245 | "}," 246 | "{" 247 | "'name':'area'," 248 | "'label':'Textarea'," 249 | "'type':"+String(INPUTTEXTAREA)+"," 250 | "'default':''," 251 | "'min':40,'max':5" //min = columns max = rows 252 | "}," 253 | "{" 254 | "'name':'pwd'," 255 | "'label':'WLAN Password'," 256 | "'type':"+String(INPUTPASSWORD)+"," 257 | "'default':''" 258 | "}," 259 | "{" 260 | "'name':'amount'," 261 | "'label':'Amount'," 262 | "'type':"+String(INPUTNUMBER)+"," 263 | "'min':-10,'max':20," 264 | "'default':'1'" 265 | "}," 266 | "{" 267 | "'name':'float'," 268 | "'label':'Floatingpoint number'," 269 | "'type':"+String(INPUTTEXT)+"," 270 | "'default':'1.00'" 271 | "}," 272 | "{" 273 | "'name':'duration'," 274 | "'label':'Duration (s)'," 275 | "'type':"+String(INPUTRANGE)+"," 276 | "'min':5,'max':30," 277 | "'default':'10'" 278 | "}," 279 | "{" 280 | "'name':'date'," 281 | "'label':'Date'," 282 | "'type':"+String(INPUTDATE)+"," 283 | "'default':'2019-08-14'" 284 | "}," 285 | "{" 286 | "'name':'time'," 287 | "'label':'Time'," 288 | "'type':"+String(INPUTTIME)+"," 289 | "'default':'18:30'" 290 | "}," 291 | "{" 292 | "'name':'col'," 293 | "'label':'Color'," 294 | "'type':"+String(INPUTCOLOR)+"," 295 | "'default':'#ffffff'" 296 | "}," 297 | "{" 298 | "'name':'switch'," 299 | "'label':'Switch'," 300 | "'type':"+String(INPUTCHECKBOX)+"," 301 | "'default':'1'" 302 | "}," 303 | "{" 304 | "'name':'gender'," 305 | "'label':'Gender'," 306 | "'type':"+String(INPUTRADIO)+"," 307 | "'options':\[" 308 | "{'v':'m','l':'male'}," 309 | "{'v':'f','l':'female'}," 310 | "{'v':'o','l':'other'}\]," 311 | "'default':'f'" 312 | "}," 313 | "{" 314 | "'name':'continent'," 315 | "'label':'Continent'," 316 | "'type':"+String(INPUTSELECT)+"," 317 | "'options':\[" 318 | "{'v':'EU','l':'Europe'}," 319 | "{'v':'AF','l':'Africa'}," 320 | "{'v':'AS','l':'Asia'}," 321 | "{'v':'AU','l':'Australia'}," 322 | "{'v':'AM','l':'America'}\]," 323 | "'default':'AM'" 324 | "}," 325 | "{" 326 | "'name':'wochentag'," 327 | "'label':'Wochentag'," 328 | "'type':"+String(INPUTMULTICHECK)+"," 329 | "'options':[" 330 | "{'v':'0','l':'Sonntag'}," 331 | "{'v':'1','l':'Montag'}," 332 | "{'v':'2','l':'Dienstag'}," 333 | "{'v':'3','l':'Mittwoch'}," 334 | "{'v':'4','l':'Donnerstag'}," 335 | "{'v':'5','l':'Freitag'}," 336 | "{'v':'6','l':'Samstag'}]," 337 | "'default':''" 338 | "}" 339 | "\]"; 340 | -------------------------------------------------------------------------------- /lib/WebConfig/examples/demo32/demo32.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | #include 6 | 7 | String params = "[" 8 | "{" 9 | "'name':'ssid'," 10 | "'label':'Name des WLAN'," 11 | "'type':"+String(INPUTTEXT)+"," 12 | "'default':''" 13 | "}," 14 | "{" 15 | "'name':'pwd'," 16 | "'label':'WLAN Passwort'," 17 | "'type':"+String(INPUTPASSWORD)+"," 18 | "'default':''" 19 | "}," 20 | "{" 21 | "'name':'amount'," 22 | "'label':'Menge'," 23 | "'type':"+String(INPUTNUMBER)+"," 24 | "'min':-10,'max':20," 25 | "'default':'1'" 26 | "}," 27 | "{" 28 | "'name':'float'," 29 | "'label':'Fließkomma Zahl'," 30 | "'type':"+String(INPUTTEXT)+"," 31 | "'default':'1.00'" 32 | "}," 33 | "{" 34 | "'name':'area'," 35 | "'label':'Mehr Text'," 36 | "'type':"+String(INPUTTEXTAREA)+"," 37 | "'default':''," 38 | "'min':40,'max':5" //min = columns max = rows 39 | "}," 40 | "{" 41 | "'name':'duration'," 42 | "'label':'Dauer(s)'," 43 | "'type':"+String(INPUTRANGE)+"," 44 | "'min':5,'max':30," 45 | "'default':'10'" 46 | "}," 47 | "{" 48 | "'name':'date'," 49 | "'label':'Datum'," 50 | "'type':"+String(INPUTDATE)+"," 51 | "'default':'2019-08-14'" 52 | "}," 53 | "{" 54 | "'name':'time'," 55 | "'label':'Zeit'," 56 | "'type':"+String(INPUTTIME)+"," 57 | "'default':'18:30'" 58 | "}," 59 | "{" 60 | "'name':'col'," 61 | "'label':'Farbe'," 62 | "'type':"+String(INPUTCOLOR)+"," 63 | "'default':'#ffffff'" 64 | "}," 65 | "{" 66 | "'name':'switch'," 67 | "'label':'Schalter'," 68 | "'type':"+String(INPUTCHECKBOX)+"," 69 | "'default':'1'" 70 | "}," 71 | "{" 72 | "'name':'gender'," 73 | "'label':'Geschlecht'," 74 | "'type':"+String(INPUTRADIO)+"," 75 | "'options':[" 76 | "{'v':'m','l':'männlich'}," 77 | "{'v':'w','l':'weiblich'}," 78 | "{'v':'x','l':'anderes'}]," 79 | "'default':'w'" 80 | "}," 81 | "{" 82 | "'name':'continent'," 83 | "'label':'Kontinent'," 84 | "'type':"+String(INPUTSELECT)+"," 85 | "'options':[" 86 | "{'v':'EU','l':'Europa'}," 87 | "{'v':'AF','l':'Afrika'}," 88 | "{'v':'AS','l':'Asien'}," 89 | "{'v':'AU','l':'Australien'}," 90 | "{'v':'AM','l':'Amerika'}]," 91 | "'default':'AM'" 92 | "}," 93 | "{" 94 | "'name':'wochentag'," 95 | "'label':'Wochentag'," 96 | "'type':"+String(INPUTMULTICHECK)+"," 97 | "'options':[" 98 | "{'v':'0','l':'Sonntag'}," 99 | "{'v':'1','l':'Montag'}," 100 | "{'v':'2','l':'Dienstag'}," 101 | "{'v':'3','l':'Mittwoch'}," 102 | "{'v':'4','l':'Donnerstag'}," 103 | "{'v':'5','l':'Freitag'}," 104 | "{'v':'6','l':'Samstag'}]," 105 | "'default':''" 106 | "}" 107 | "]"; 108 | 109 | WebServer server; 110 | WebConfig conf; 111 | 112 | boolean initWiFi() { 113 | boolean connected = false; 114 | WiFi.mode(WIFI_STA); 115 | Serial.print("Verbindung zu "); 116 | Serial.print(conf.values[0]); 117 | Serial.println(" herstellen"); 118 | if (conf.values[0] != "") { 119 | WiFi.begin(conf.values[0].c_str(),conf.values[1].c_str()); 120 | uint8_t cnt = 0; 121 | while ((WiFi.status() != WL_CONNECTED) && (cnt<20)){ 122 | delay(500); 123 | Serial.print("."); 124 | cnt++; 125 | } 126 | Serial.println(); 127 | if (WiFi.status() == WL_CONNECTED) { 128 | Serial.print("IP-Adresse = "); 129 | Serial.println(WiFi.localIP()); 130 | connected = true; 131 | } 132 | } 133 | if (!connected) { 134 | WiFi.mode(WIFI_AP); 135 | WiFi.softAP(conf.getApName(),"",1); 136 | } 137 | return connected; 138 | } 139 | 140 | void handleRoot() { 141 | conf.handleFormRequest(&server); 142 | if (server.hasArg("SAVE")) { 143 | uint8_t cnt = conf.getCount(); 144 | Serial.println("*********** Konfiguration ************"); 145 | for (uint8_t i = 0; i 2 | #include 3 | #include 4 | 5 | 6 | #include 7 | 8 | String params = "[" 9 | "{" 10 | "'name':'ssid'," 11 | "'label':'Name des WLAN'," 12 | "'type':"+String(INPUTTEXT)+"," 13 | "'default':''" 14 | "}," 15 | "{" 16 | "'name':'pwd'," 17 | "'label':'WLAN Passwort'," 18 | "'type':"+String(INPUTPASSWORD)+"," 19 | "'default':''" 20 | "}," 21 | "{" 22 | "'name':'amount'," 23 | "'label':'Menge'," 24 | "'type':"+String(INPUTNUMBER)+"," 25 | "'min':-10,'max':20," 26 | "'default':'1'" 27 | "}," 28 | "{" 29 | "'name':'float'," 30 | "'label':'Fließkomma Zahl'," 31 | "'type':"+String(INPUTTEXT)+"," 32 | "'default':'1.00'" 33 | "}," 34 | "{" 35 | "'name':'area'," 36 | "'label':'Mehr Text'," 37 | "'type':"+String(INPUTTEXTAREA)+"," 38 | "'default':''," 39 | "'min':40,'max':5" //min = columns max = rows 40 | "}," 41 | "{" 42 | "'name':'duration'," 43 | "'label':'Dauer(s)'," 44 | "'type':"+String(INPUTRANGE)+"," 45 | "'min':5,'max':30," 46 | "'default':'10'" 47 | "}," 48 | "{" 49 | "'name':'date'," 50 | "'label':'Datum'," 51 | "'type':"+String(INPUTDATE)+"," 52 | "'default':'2019-08-14'" 53 | "}," 54 | "{" 55 | "'name':'time'," 56 | "'label':'Zeit'," 57 | "'type':"+String(INPUTTIME)+"," 58 | "'default':'18:30'" 59 | "}," 60 | "{" 61 | "'name':'col'," 62 | "'label':'Farbe'," 63 | "'type':"+String(INPUTCOLOR)+"," 64 | "'default':'#ffffff'" 65 | "}," 66 | "{" 67 | "'name':'switch'," 68 | "'label':'Schalter'," 69 | "'type':"+String(INPUTCHECKBOX)+"," 70 | "'default':'1'" 71 | "}," 72 | "{" 73 | "'name':'gender'," 74 | "'label':'Geschlecht'," 75 | "'type':"+String(INPUTRADIO)+"," 76 | "'options':[" 77 | "{'v':'m','l':'männlich'}," 78 | "{'v':'w','l':'weiblich'}," 79 | "{'v':'x','l':'anderes'}]," 80 | "'default':'w'" 81 | "}," 82 | "{" 83 | "'name':'continent'," 84 | "'label':'Kontinent'," 85 | "'type':"+String(INPUTSELECT)+"," 86 | "'options':[" 87 | "{'v':'EU','l':'Europa'}," 88 | "{'v':'AF','l':'Afrika'}," 89 | "{'v':'AS','l':'Asien'}," 90 | "{'v':'AU','l':'Australien'}," 91 | "{'v':'AM','l':'Amerika'}]," 92 | "'default':'AM'" 93 | "}," 94 | "{" 95 | "'name':'wochentag'," 96 | "'label':'Wochentag'," 97 | "'type':"+String(INPUTMULTICHECK)+"," 98 | "'options':[" 99 | "{'v':'0','l':'Sonntag'}," 100 | "{'v':'1','l':'Montag'}," 101 | "{'v':'2','l':'Dienstag'}," 102 | "{'v':'3','l':'Mittwoch'}," 103 | "{'v':'4','l':'Donnerstag'}," 104 | "{'v':'5','l':'Freitag'}," 105 | "{'v':'6','l':'Samstag'}]," 106 | "'default':''" 107 | "}" 108 | "]"; 109 | 110 | ESP8266WebServer server; 111 | WebConfig conf; 112 | 113 | boolean initWiFi() { 114 | boolean connected = false; 115 | WiFi.mode(WIFI_STA); 116 | Serial.print("Verbindung zu "); 117 | Serial.print(conf.values[0]); 118 | Serial.println(" herstellen"); 119 | if (conf.values[0] != "") { 120 | WiFi.begin(conf.values[0].c_str(),conf.values[1].c_str()); 121 | uint8_t cnt = 0; 122 | while ((WiFi.status() != WL_CONNECTED) && (cnt<20)){ 123 | delay(500); 124 | Serial.print("."); 125 | cnt++; 126 | } 127 | Serial.println(); 128 | if (WiFi.status() == WL_CONNECTED) { 129 | Serial.print("IP-Adresse = "); 130 | Serial.println(WiFi.localIP()); 131 | connected = true; 132 | } 133 | } 134 | if (!connected) { 135 | WiFi.mode(WIFI_AP); 136 | WiFi.softAP(conf.getApName(),"",1); 137 | } 138 | return connected; 139 | } 140 | 141 | void handleRoot() { 142 | conf.handleFormRequest(&server); 143 | if (server.hasArg("SAVE")) { 144 | uint8_t cnt = conf.getCount(); 145 | Serial.println("*********** Konfiguration ************"); 146 | for (uint8_t i = 0; i 20 | #include 21 | #if defined(ESP32) 22 | #include "SPIFFS.h" 23 | #include 24 | #else 25 | #include 26 | #endif 27 | #include 28 | #include 29 | 30 | const char *inputtypes[] = {"text", "password", "number", "date", "time", "range", "check", "radio", "select", "color", "float"}; 31 | 32 | // HTML templates 33 | // Template for header and begin of form 34 | const char HTML_START[] PROGMEM = 35 | "\n" 36 | "\n" 37 | "\n" 38 | "\n" 39 | "\n" 40 | "Konfiguration\n" 41 | "\n" 67 | "\n" 68 | "\n" 69 | "
\n" 70 | "
\n"; 71 | 72 | // Template for one input field 73 | const char HTML_ENTRY_SIMPLE[] PROGMEM = 74 | "
%s
\n" 75 | "
\n"; 76 | const char HTML_ENTRY_AREA[] PROGMEM = 77 | "
%s
\n" 78 | "
\n"; 79 | const char HTML_ENTRY_NUMBER[] PROGMEM = 80 | "
%s
\n" 81 | "
\n"; 82 | const char HTML_ENTRY_RANGE[] PROGMEM = 83 | "
%s
\n" 84 | "
%i  %i
\n"; 85 | const char HTML_ENTRY_CHECKBOX[] PROGMEM = 86 | "
%s
\n"; 87 | const char HTML_ENTRY_RADIO_TITLE[] PROGMEM = 88 | "
%s
\n"; 89 | const char HTML_ENTRY_RADIO[] = 90 | "
%s
\n"; 91 | const char HTML_ENTRY_SELECT_START[] PROGMEM = 92 | "
%s
\n" 93 | "
\n"; 98 | const char HTML_ENTRY_MULTI_START[] PROGMEM = 99 | "
%s
\n" 100 | "
\n"; 101 | const char HTML_ENTRY_MULTI_OPTION[] PROGMEM = 102 | " %s
\n"; 103 | const char HTML_ENTRY_MULTI_END[] PROGMEM = 104 | "
\n"; 105 | 106 | // Template for save button and end of the form with save 107 | const char HTML_END[] PROGMEM = 108 | "
\n" 109 | "
\n" 110 | "
\n" 111 | "
\n" 112 | "\n" 113 | "\n"; 114 | // Template for save button and end of the form without save 115 | const char HTML_BUTTON[] PROGMEM = 116 | "\n"; 117 | 118 | WebConfig::WebConfig() 119 | { 120 | _apName = ""; 121 | }; 122 | 123 | void WebConfig::setDescription(String parameter) 124 | { 125 | _count = 0; 126 | addDescription(parameter); 127 | } 128 | 129 | void WebConfig::addDescription(String parameter) 130 | { 131 | DeserializationError error; 132 | const int capacity = JSON_ARRAY_SIZE(MAXVALUES) + MAXVALUES * JSON_OBJECT_SIZE(8); 133 | DynamicJsonDocument doc(capacity); 134 | char tmp[40]; 135 | error = deserializeJson(doc, parameter); 136 | if (error) 137 | { 138 | Serial.println(parameter); 139 | Serial.print("JSON AddDescription: "); 140 | Serial.println(error.c_str()); 141 | } 142 | else 143 | { 144 | JsonArray array = doc.as(); 145 | uint8_t j = 0; 146 | for (JsonObject obj : array) 147 | { 148 | if (_count < MAXVALUES) 149 | { 150 | _description[_count].optionCnt = 0; 151 | if (obj.containsKey("name")) 152 | strlcpy(_description[_count].name, obj["name"], NAMELENGTH); 153 | if (obj.containsKey("label")) 154 | strlcpy(_description[_count].label, obj["label"], LABELLENGTH); 155 | if (obj.containsKey("type")) 156 | { 157 | if (obj["type"].is()) 158 | { 159 | uint8_t t = 0; 160 | strlcpy(tmp, obj["type"], 30); 161 | while ((t < INPUTTYPES) && (strcmp(tmp, inputtypes[t]) != 0)) 162 | t++; 163 | if (t > INPUTTYPES) 164 | t = 0; 165 | _description[_count].type = t; 166 | } 167 | else 168 | { 169 | _description[_count].type = obj["type"]; 170 | } 171 | } 172 | else 173 | { 174 | _description[_count].type = INPUTTEXT; 175 | } 176 | _description[_count].max = (obj.containsKey("max")) ? obj["max"] : 100; 177 | _description[_count].min = (obj.containsKey("min")) ? obj["min"] : 0; 178 | if (obj.containsKey("default")) 179 | { 180 | strlcpy(tmp, obj["default"], 30); 181 | values[_count] = String(tmp); 182 | } 183 | else 184 | { 185 | values[_count] = "0"; 186 | } 187 | if (obj.containsKey("options")) 188 | { 189 | JsonArray opt = obj["options"].as(); 190 | j = 0; 191 | for (JsonObject o : opt) 192 | { 193 | if (j < MAXOPTIONS) 194 | { 195 | _description[_count].options[j] = o["v"].as(); 196 | _description[_count].labels[j] = o["l"].as(); 197 | } 198 | j++; 199 | } 200 | _description[_count].optionCnt = opt.size(); 201 | } 202 | } 203 | _count++; 204 | } 205 | } 206 | _apName = WiFi.macAddress(); 207 | _apName.replace(":", ""); 208 | if (!SPIFFS.begin()) 209 | { 210 | SPIFFS.format(); 211 | SPIFFS.begin(); 212 | } 213 | }; 214 | 215 | void createSimple(char *buf, const char *name, const char *label, const char *type, String value) 216 | { 217 | sprintf(buf, HTML_ENTRY_SIMPLE, label, type, value.c_str(), name); 218 | } 219 | 220 | void createTextarea(char *buf, DESCRIPTION descr, String value) 221 | { 222 | // max = rows min = cols 223 | sprintf(buf, HTML_ENTRY_AREA, descr.label, descr.max, descr.min, descr.name, value.c_str()); 224 | } 225 | 226 | void createNumber(char *buf, DESCRIPTION descr, String value) 227 | { 228 | sprintf(buf, HTML_ENTRY_NUMBER, descr.label, descr.min, descr.max, value.c_str(), descr.name); 229 | } 230 | 231 | void createRange(char *buf, DESCRIPTION descr, String value) 232 | { 233 | sprintf(buf, HTML_ENTRY_RANGE, descr.label, descr.min, descr.min, descr.max, value.c_str(), descr.name, descr.max); 234 | } 235 | 236 | void createCheckbox(char *buf, DESCRIPTION descr, String value) 237 | { 238 | if (value != "0") 239 | { 240 | sprintf(buf, HTML_ENTRY_CHECKBOX, descr.label, "checked", descr.name); 241 | } 242 | else 243 | { 244 | sprintf(buf, HTML_ENTRY_CHECKBOX, descr.label, "", descr.name); 245 | } 246 | } 247 | 248 | void createRadio(char *buf, DESCRIPTION descr, String value, uint8_t index) 249 | { 250 | if (value == descr.options[index]) 251 | { 252 | sprintf(buf, HTML_ENTRY_RADIO, descr.name, descr.options[index].c_str(), "checked", descr.labels[index].c_str()); 253 | } 254 | else 255 | { 256 | sprintf(buf, HTML_ENTRY_RADIO, descr.name, descr.options[index].c_str(), "", descr.labels[index].c_str()); 257 | } 258 | } 259 | 260 | void startSelect(char *buf, DESCRIPTION descr) 261 | { 262 | sprintf(buf, HTML_ENTRY_SELECT_START, descr.label, descr.name); 263 | } 264 | 265 | void addSelectOption(char *buf, String option, String label, String value) 266 | { 267 | if (option == value) 268 | { 269 | sprintf(buf, HTML_ENTRY_SELECT_OPTION, option.c_str(), "selected", label.c_str()); 270 | } 271 | else 272 | { 273 | sprintf(buf, HTML_ENTRY_SELECT_OPTION, option.c_str(), "", label.c_str()); 274 | } 275 | } 276 | 277 | void startMulti(char *buf, DESCRIPTION descr) 278 | { 279 | sprintf(buf, HTML_ENTRY_MULTI_START, descr.label); 280 | } 281 | 282 | void addMultiOption(char *buf, String name, uint8_t option, String label, String value) 283 | { 284 | if ((value.length() > option) && (value[option] == '1')) 285 | { 286 | sprintf(buf, HTML_ENTRY_MULTI_OPTION, name.c_str(), option, "checked", label.c_str()); 287 | } 288 | else 289 | { 290 | sprintf(buf, HTML_ENTRY_MULTI_OPTION, name.c_str(), option, "", label.c_str()); 291 | } 292 | } 293 | 294 | //***********Different type for ESP32 WebServer and ESP8266WebServer ******** 295 | // both classes have the same functions 296 | #if defined(ESP32) 297 | // function to respond a HTTP request for the form use the default file 298 | // to save and restart ESP after saving the new config 299 | void WebConfig::handleFormRequest(WebServer *server) 300 | { 301 | handleFormRequest(server, CONFFILE); 302 | } 303 | // function to respond a HTTP request for the form use the filename 304 | // to save. If auto is true restart ESP after saving the new config 305 | void WebConfig::handleFormRequest(WebServer *server, const char *filename) 306 | { 307 | #else 308 | // function to respond a HTTP request for the form use the default file 309 | // to save and restart ESP after saving the new config 310 | void WebConfig::handleFormRequest(ESP8266WebServer *server) 311 | { 312 | handleFormRequest(server, CONFFILE); 313 | } 314 | // function to respond a HTTP request for the form use the filename 315 | // to save. If auto is true restart ESP after saving the new config 316 | void WebConfig::handleFormRequest(ESP8266WebServer *server, const char *filename) 317 | { 318 | #endif 319 | //******************** Rest of the function has no difference *************** 320 | uint8_t a, v; 321 | String val; 322 | if (server->args() > 0) 323 | { 324 | if (server->hasArg(F("apName"))) 325 | _apName = server->arg(F("apName")); 326 | for (uint8_t i = 0; i < _count; i++) 327 | { 328 | if (_description[i].type == INPUTCHECKBOX) 329 | { 330 | values[i] = "0"; 331 | if (server->hasArg(_description[i].name)) 332 | values[i] = "1"; 333 | } 334 | else if (_description[i].type == INPUTMULTICHECK) 335 | { 336 | values[i] = ""; 337 | for (a = 0; a < _description[i].optionCnt; a++) 338 | values[i] += "0"; // clear result 339 | for (a = 0; a < server->args(); a++) 340 | { 341 | if (server->argName(a) == _description[i].name) 342 | { 343 | val = server->arg(a); 344 | v = val.toInt(); 345 | values[i].setCharAt(v, '1'); 346 | } 347 | } 348 | } 349 | else 350 | { 351 | if (server->hasArg(_description[i].name)) 352 | values[i] = server->arg(_description[i].name); 353 | } 354 | } 355 | } 356 | boolean exit = false; 357 | 358 | if (server->hasArg(F("SAVE"))) 359 | { 360 | _onSave(getResults()); 361 | writeConfig(filename); 362 | exit = false; 363 | } 364 | if (server->hasArg(F("RST"))) 365 | { 366 | ESP.restart(); 367 | } 368 | if (server->hasArg(F("DONE")) && _onDone) 369 | { 370 | _onDone(getResults()); 371 | exit = true; 372 | } 373 | if (server->hasArg(F("CANCEL")) && _onCancel) 374 | { 375 | _onCancel(); 376 | exit = true; 377 | } 378 | if (server->hasArg(F("DELETE")) && _onDelete) 379 | { 380 | _onDelete(_apName); 381 | exit = true; 382 | } 383 | if (!exit) 384 | { 385 | server->setContentLength(CONTENT_LENGTH_UNKNOWN); 386 | sprintf(_buf, HTML_START, _apName.c_str()); 387 | server->send(200, "text/html", _buf); 388 | if (_buttons == BTN_CONFIG) 389 | { 390 | 391 | server->sendContent(_buf); 392 | } 393 | 394 | for (uint8_t i = 0; i < _count; i++) 395 | { 396 | switch (_description[i].type) 397 | { 398 | case INPUTFLOAT: 399 | case INPUTTEXT: 400 | createSimple(_buf, _description[i].name, _description[i].label, "text", values[i]); 401 | break; 402 | case INPUTTEXTAREA: 403 | createTextarea(_buf, _description[i], values[i]); 404 | break; 405 | case INPUTPASSWORD: 406 | createSimple(_buf, _description[i].name, _description[i].label, "password", values[i]); 407 | break; 408 | case INPUTDATE: 409 | createSimple(_buf, _description[i].name, _description[i].label, "date", values[i]); 410 | break; 411 | case INPUTTIME: 412 | createSimple(_buf, _description[i].name, _description[i].label, "time", values[i]); 413 | break; 414 | case INPUTCOLOR: 415 | createSimple(_buf, _description[i].name, _description[i].label, "color", values[i]); 416 | break; 417 | case INPUTNUMBER: 418 | createNumber(_buf, _description[i], values[i]); 419 | break; 420 | case INPUTRANGE: 421 | createRange(_buf, _description[i], values[i]); 422 | break; 423 | case INPUTCHECKBOX: 424 | createCheckbox(_buf, _description[i], values[i]); 425 | break; 426 | case INPUTRADIO: 427 | sprintf(_buf, HTML_ENTRY_RADIO_TITLE, _description[i].label); 428 | for (uint8_t j = 0; j < _description[i].optionCnt; j++) 429 | { 430 | server->sendContent(_buf); 431 | createRadio(_buf, _description[i], values[i], j); 432 | } 433 | break; 434 | case INPUTSELECT: 435 | startSelect(_buf, _description[i]); 436 | for (uint8_t j = 0; j < _description[i].optionCnt; j++) 437 | { 438 | server->sendContent(_buf); 439 | addSelectOption(_buf, _description[i].options[j], _description[i].labels[j], values[i]); 440 | } 441 | server->sendContent(_buf); 442 | strcpy_P(_buf, HTML_ENTRY_SELECT_END); 443 | break; 444 | case INPUTMULTICHECK: 445 | startMulti(_buf, _description[i]); 446 | for (uint8_t j = 0; j < _description[i].optionCnt; j++) 447 | { 448 | server->sendContent(_buf); 449 | addMultiOption(_buf, _description[i].name, j, _description[i].labels[j], values[i]); 450 | } 451 | server->sendContent(_buf); 452 | strcpy_P(_buf, HTML_ENTRY_MULTI_END); 453 | break; 454 | default: 455 | _buf[0] = 0; 456 | break; 457 | } 458 | server->sendContent(_buf); 459 | } 460 | if (_buttons == BTN_CONFIG) 461 | { 462 | server->sendContent(HTML_END); 463 | } 464 | else 465 | { 466 | server->sendContent("
\n"); 467 | if ((_buttons & BTN_DONE) == BTN_DONE) 468 | { 469 | sprintf(_buf, HTML_BUTTON, "DONE", "Done"); 470 | server->sendContent(_buf); 471 | } 472 | if ((_buttons & BTN_CANCEL) == BTN_CANCEL) 473 | { 474 | sprintf(_buf, HTML_BUTTON, "CANCEL", "Cancel"); 475 | server->sendContent(_buf); 476 | } 477 | if ((_buttons & BTN_DELETE) == BTN_DELETE) 478 | { 479 | sprintf(_buf, HTML_BUTTON, "DELETE", "Delete"); 480 | server->sendContent(_buf); 481 | } 482 | server->sendContent("
\n"); 483 | } 484 | } 485 | } 486 | // get the index for a value by parameter name 487 | int16_t WebConfig::getIndex(const char *name) 488 | { 489 | int16_t i = _count - 1; 490 | while ((i >= 0) && (strcmp(name, _description[i].name) != 0)) 491 | { 492 | i--; 493 | } 494 | return i; 495 | } 496 | // read configuration from file 497 | boolean WebConfig::readConfig(const char *filename) 498 | { 499 | String line, name, value; 500 | uint8_t pos; 501 | int16_t index; 502 | if (!SPIFFS.exists(filename)) 503 | { 504 | // if configfile does not exist write default values 505 | writeConfig(filename); 506 | } 507 | File f = SPIFFS.open(filename, "r"); 508 | if (f) 509 | { 510 | Serial.println(F("Read configuration")); 511 | uint32_t size = f.size(); 512 | while (f.position() < size) 513 | { 514 | line = f.readStringUntil(10); 515 | pos = line.indexOf('='); 516 | name = line.substring(0, pos); 517 | value = line.substring(pos + 1); 518 | if ((name == "apName") && (value != "")) 519 | { 520 | _apName = value; 521 | Serial.println(line); 522 | } 523 | else 524 | { 525 | index = getIndex(name.c_str()); 526 | if (!(index < 0)) 527 | { 528 | value.replace("~", "\n"); 529 | values[index] = value; 530 | if (_description[index].type == INPUTPASSWORD) 531 | { 532 | Serial.printf("%s=*************\n", _description[index].name); 533 | } 534 | else 535 | { 536 | Serial.println(line); 537 | } 538 | } 539 | } 540 | } 541 | f.close(); 542 | return true; 543 | } 544 | else 545 | { 546 | Serial.println(F("Cannot read configuration")); 547 | return false; 548 | } 549 | } 550 | // read configuration from default file 551 | boolean WebConfig::readConfig() 552 | { 553 | return readConfig(CONFFILE); 554 | } 555 | // write configuration to file 556 | boolean WebConfig::writeConfig(const char *filename) 557 | { 558 | String val; 559 | File f = SPIFFS.open(filename, "w"); 560 | if (f) 561 | { 562 | f.printf("apName=%s\n", _apName.c_str()); 563 | for (uint8_t i = 0; i < _count; i++) 564 | { 565 | val = values[i]; 566 | val.replace("\n", "~"); 567 | f.printf("%s=%s\n", _description[i].name, val.c_str()); 568 | } 569 | return true; 570 | } 571 | else 572 | { 573 | Serial.println(F("Cannot write configuration")); 574 | return false; 575 | } 576 | } 577 | // write configuration to default file 578 | boolean WebConfig::writeConfig() 579 | { 580 | return writeConfig(CONFFILE); 581 | } 582 | // delete configuration file 583 | boolean WebConfig::deleteConfig(const char *filename) 584 | { 585 | return SPIFFS.remove(filename); 586 | } 587 | // delete default configutation file 588 | boolean WebConfig::deleteConfig() 589 | { 590 | return deleteConfig(CONFFILE); 591 | } 592 | 593 | // get a parameter value by its name 594 | const String WebConfig::getString(const char *name) 595 | { 596 | int16_t index; 597 | index = getIndex(name); 598 | if (index < 0) 599 | { 600 | return ""; 601 | } 602 | else 603 | { 604 | return values[index]; 605 | } 606 | } 607 | 608 | // Get results as a JSON string 609 | String WebConfig::getResults() 610 | { 611 | char buffer[1024]; 612 | StaticJsonDocument<1000> doc; 613 | for (uint8_t i = 0; i < _count; i++) 614 | { 615 | switch (_description[i].type) 616 | { 617 | case INPUTPASSWORD: 618 | case INPUTSELECT: 619 | case INPUTDATE: 620 | case INPUTTIME: 621 | case INPUTRADIO: 622 | case INPUTCOLOR: 623 | case INPUTTEXT: 624 | doc[_description[i].name] = values[i]; 625 | break; 626 | case INPUTCHECKBOX: 627 | case INPUTRANGE: 628 | case INPUTNUMBER: 629 | doc[_description[i].name] = values[i].toInt(); 630 | break; 631 | case INPUTFLOAT: 632 | doc[_description[i].name] = values[i].toFloat(); 633 | break; 634 | } 635 | } 636 | serializeJson(doc, buffer); 637 | return String(buffer); 638 | } 639 | 640 | // Ser values from a JSON string 641 | void WebConfig::setValues(String json) 642 | { 643 | int val; 644 | float fval; 645 | char sval[255]; 646 | DeserializationError error; 647 | StaticJsonDocument<1000> doc; 648 | error = deserializeJson(doc, json); 649 | if (error) 650 | { 651 | Serial.print("JSON: "); 652 | Serial.println(error.c_str()); 653 | } 654 | else 655 | { 656 | for (uint8_t i = 0; i < _count; i++) 657 | { 658 | if (doc.containsKey(_description[i].name)) 659 | { 660 | switch (_description[i].type) 661 | { 662 | case INPUTPASSWORD: 663 | case INPUTSELECT: 664 | case INPUTDATE: 665 | case INPUTTIME: 666 | case INPUTRADIO: 667 | case INPUTCOLOR: 668 | case INPUTTEXT: 669 | strlcpy(sval, doc[_description[i].name], 255); 670 | values[i] = String(sval); 671 | break; 672 | case INPUTCHECKBOX: 673 | case INPUTRANGE: 674 | case INPUTNUMBER: 675 | val = doc[_description[i].name]; 676 | values[i] = String(val); 677 | break; 678 | case INPUTFLOAT: 679 | fval = doc[_description[i].name]; 680 | values[i] = String(fval); 681 | break; 682 | } 683 | } 684 | } 685 | } 686 | } 687 | 688 | const char *WebConfig::getValue(const char *name) 689 | { 690 | int16_t index; 691 | index = getIndex(name); 692 | if (index < 0) 693 | { 694 | return ""; 695 | } 696 | else 697 | { 698 | return values[index].c_str(); 699 | } 700 | } 701 | 702 | int WebConfig::getInt(const char *name) 703 | { 704 | return getString(name).toInt(); 705 | } 706 | 707 | float WebConfig::getFloat(const char *name) 708 | { 709 | return getString(name).toFloat(); 710 | } 711 | 712 | boolean WebConfig::getBool(const char *name) 713 | { 714 | return (getString(name) != "0"); 715 | } 716 | 717 | // get the accesspoint name 718 | const char *WebConfig::getApName() 719 | { 720 | return _apName.c_str(); 721 | } 722 | // get the number of parameters 723 | uint8_t WebConfig::getCount() 724 | { 725 | return _count; 726 | } 727 | 728 | // get the name of a parameter 729 | String WebConfig::getName(uint8_t index) 730 | { 731 | if (index < _count) 732 | { 733 | return String(_description[index].name); 734 | } 735 | else 736 | { 737 | return ""; 738 | } 739 | } 740 | 741 | // set the value for a parameter 742 | void WebConfig::setValue(const char *name, String value) 743 | { 744 | int16_t i = getIndex(name); 745 | if (i >= 0) 746 | values[i] = value; 747 | } 748 | 749 | // set the label for a parameter 750 | void WebConfig::setLabel(const char *name, const char *label) 751 | { 752 | int16_t i = getIndex(name); 753 | if (i >= 0) 754 | strlcpy(_description[i].label, label, LABELLENGTH); 755 | } 756 | 757 | // remove all options 758 | void WebConfig::clearOptions(uint8_t index) 759 | { 760 | if (index < _count) 761 | _description[index].optionCnt = 0; 762 | } 763 | 764 | void WebConfig::clearOptions(const char *name) 765 | { 766 | int16_t i = getIndex(name); 767 | if (i >= 0) 768 | clearOptions(i); 769 | } 770 | 771 | // add a new option 772 | void WebConfig::addOption(uint8_t index, String option) 773 | { 774 | addOption(index, option, option); 775 | } 776 | 777 | void WebConfig::addOption(uint8_t index, String option, String label) 778 | { 779 | if (index < _count) 780 | { 781 | if (_description[index].optionCnt < MAXOPTIONS) 782 | { 783 | _description[index].options[_description[index].optionCnt] = option; 784 | _description[index].labels[_description[index].optionCnt] = label; 785 | _description[index].optionCnt++; 786 | } 787 | } 788 | } 789 | 790 | // modify an option 791 | void WebConfig::setOption(uint8_t index, uint8_t option_index, String option, String label) 792 | { 793 | if (index < _count) 794 | { 795 | if (option_index < _description[index].optionCnt) 796 | { 797 | _description[index].options[option_index] = option; 798 | _description[index].labels[option_index] = label; 799 | } 800 | } 801 | } 802 | 803 | void WebConfig::setOption(char *name, uint8_t option_index, String option, String label) 804 | { 805 | int16_t i = getIndex(name); 806 | if (i >= 0) 807 | setOption(i, option_index, option, label); 808 | } 809 | 810 | // get the options count 811 | uint8_t WebConfig::getOptionCount(uint8_t index) 812 | { 813 | if (index < _count) 814 | { 815 | return _description[index].optionCnt; 816 | } 817 | else 818 | { 819 | return 0; 820 | } 821 | } 822 | 823 | uint8_t WebConfig::getOptionCount(char *name) 824 | { 825 | int16_t i = getIndex(name); 826 | if (i >= 0) 827 | { 828 | return getOptionCount(i); 829 | } 830 | else 831 | { 832 | return 0; 833 | } 834 | } 835 | 836 | // set form type to doen cancel 837 | void WebConfig::setButtons(uint8_t buttons) 838 | { 839 | _buttons = buttons; 840 | } 841 | // register onSave callback 842 | void WebConfig::registerOnSave(void (*callback)(String results)) 843 | { 844 | _onSave = callback; 845 | } 846 | // register onSave callback 847 | void WebConfig::registerOnDone(void (*callback)(String results)) 848 | { 849 | _onDone = callback; 850 | } 851 | // register onSave callback 852 | void WebConfig::registerOnCancel(void (*callback)()) 853 | { 854 | _onCancel = callback; 855 | } 856 | // register onDelete callback 857 | void WebConfig::registerOnDelete(void (*callback)(String name)) 858 | { 859 | _onDelete = callback; 860 | } 861 | -------------------------------------------------------------------------------- /lib/WebConfig/src/WebConfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | File WebConfig.h 4 | Version 1.4 5 | Author Gerald Lechner 6 | contakt lechge@gmail.com 7 | 8 | Description 9 | This library builds a web page with a smart phone friendly form to edit 10 | a free definable number of configuration parameters. 11 | The submitted data will bestored in the SPIFFS 12 | The library works with ESP8266 and ESP32 13 | 14 | Dependencies: 15 | ESP8266WebServer.h 16 | ArduinoJson.h 17 | 18 | */ 19 | #ifndef WebConfig_h 20 | #define WebConfig_h 21 | 22 | #include 23 | #if defined(ESP32) 24 | #include 25 | #else 26 | #include 27 | #endif 28 | 29 | //maximum number of parameters 30 | #define MAXVALUES 20 31 | 32 | //maximum number of options per parameters 33 | #define MAXOPTIONS 15 34 | 35 | //character limits 36 | #define NAMELENGTH 20 37 | #define LABELLENGTH 40 38 | 39 | //name for the config file 40 | #define CONFFILE "/WebConf.conf" 41 | 42 | #define INPUTTEXT 0 43 | #define INPUTPASSWORD 1 44 | #define INPUTNUMBER 2 45 | #define INPUTDATE 3 46 | #define INPUTTIME 4 47 | #define INPUTRANGE 5 48 | #define INPUTCHECKBOX 6 49 | #define INPUTRADIO 7 50 | #define INPUTSELECT 8 51 | #define INPUTCOLOR 9 52 | #define INPUTFLOAT 10 53 | #define INPUTTEXTAREA 11 54 | #define INPUTMULTICHECK 12 55 | //number of types 56 | #define INPUTTYPES 13 57 | 58 | #define BTN_CONFIG 0 59 | #define BTN_DONE 1 60 | #define BTN_CANCEL 2 61 | #define BTN_DELETE 4 62 | //data structure to hold the parameter Description 63 | typedef //Struktur eines Datenpakets 64 | struct { 65 | char name[NAMELENGTH]; 66 | char label[LABELLENGTH]; 67 | uint8_t type; 68 | int min; 69 | int max; 70 | uint8_t optionCnt; 71 | String options[MAXOPTIONS]; 72 | String labels[MAXOPTIONS]; 73 | } DESCRIPTION; 74 | 75 | class WebConfig { 76 | public: 77 | WebConfig(); 78 | //load form descriptions 79 | void setDescription(String parameter); 80 | //Add extra descriptions 81 | void addDescription(String parameter); 82 | //function to respond a HTTP request for the form use the filename 83 | //to save. 84 | #if defined(ESP32) 85 | void handleFormRequest(WebServer * server, const char * filename); 86 | //function to respond a HTTP request for the form use the default file 87 | //to save and restart ESP after saving the new config 88 | void handleFormRequest(WebServer * server); 89 | //get the index for a value by parameter name 90 | #else 91 | void handleFormRequest(ESP8266WebServer * server, const char * filename); 92 | //function to respond a HTTP request for the form use the default file 93 | //to save and restart ESP after saving the new config 94 | void handleFormRequest(ESP8266WebServer * server); 95 | //get the index for a value by parameter name 96 | #endif 97 | int16_t getIndex(const char * name); 98 | //read configuration from file 99 | boolean readConfig(const char * filename); 100 | //read configuration from default file 101 | boolean readConfig(); 102 | //write configuration to file 103 | boolean writeConfig(const char * filename); 104 | //write configuration to default file 105 | boolean writeConfig(); 106 | //delete configuration file 107 | boolean deleteConfig(const char * filename); 108 | //delete default configutation file 109 | boolean deleteConfig(); 110 | //get a parameter value by its name 111 | const String getString(const char * name); 112 | const char * getValue(const char * name); 113 | int getInt(const char * name); 114 | float getFloat(const char * name); 115 | boolean getBool(const char * name); 116 | //get the accesspoint name 117 | const char * getApName(); 118 | //get the number of parameters 119 | uint8_t getCount(); 120 | //get the name of a parameter 121 | String getName(uint8_t index); 122 | //Get results as a JSON string 123 | String getResults(); 124 | //Ser values from a JSON string 125 | void setValues(String json); 126 | //set the value for a parameter 127 | void setValue(const char*name,String value); 128 | //set the label for a parameter 129 | void setLabel(const char * name, const char* label); 130 | //remove all options 131 | void clearOptions(uint8_t index); 132 | void clearOptions(const char * name); 133 | //add a new option 134 | void addOption(uint8_t index, String option); 135 | void addOption(uint8_t index, String option, String label); 136 | //modify an option 137 | void setOption(uint8_t index, uint8_t option_index, String option, String label); 138 | void setOption(char * name, uint8_t option_index, String option, String label); 139 | //get the options count 140 | uint8_t getOptionCount(uint8_t index); 141 | uint8_t getOptionCount(char * name); 142 | //set form type to doen cancel 143 | void setButtons(uint8_t buttons); 144 | //register onSave callback 145 | void registerOnSave(void (*callback)(String results)); 146 | //register onSave callback 147 | void registerOnDone(void (*callback)(String results)); 148 | //register onSave callback 149 | void registerOnCancel(void (*callback)()); 150 | //register onSave callback 151 | void registerOnDelete(void (*callback)(String name)); 152 | 153 | //values for the parameter 154 | String values[MAXVALUES]; 155 | private: 156 | char _buf[1000]; 157 | uint8_t _count; 158 | String _apName; 159 | uint8_t _buttons = BTN_CONFIG; 160 | DESCRIPTION _description[MAXVALUES]; 161 | void (*_onSave)(String results) = NULL; 162 | void (*_onDone)(String results) = NULL; 163 | void (*_onCancel)() = NULL; 164 | void (*_onDelete)(String name) = NULL; 165 | }; 166 | 167 | #endif 168 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:nodemcu-32s] 12 | platform = espressif32@3.5.0 13 | board = nodemcu-32s 14 | framework = arduino 15 | monitor_speed = 115200 16 | upload_speed = 921600 17 | lib_deps = 18 | bxparks/AceButton@1.9.2 19 | jandelgado/JLed@4.11.0 20 | bblanchon/ArduinoJson@6.19.4 21 | FS 22 | knolleary/PubSubClient@2.8 23 | arduino-libraries/NTPClient@3.1.0 24 | me-no-dev/ESP Async WebServer@1.2.3 25 | gerlech/WebConfig@^1.4.1 26 | adafruit/Adafruit GFX Library@^1.11.3 27 | adafruit/Adafruit BusIO@^1.13.2 28 | thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays@^4.3.0 29 | LittleFS_esp32 30 | -------------------------------------------------------------------------------- /png2bin.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Blueforcer/SmartPusher/c92c789723b336f6819856090d66a339cb49cd81/png2bin.zip -------------------------------------------------------------------------------- /src/ButtonManager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | bool ResetLights = false; 6 | unsigned long prevMillis = 0; 7 | int demoState = 0; 8 | 9 | // The getter for the instantiated singleton instance 10 | ButtonManager_ &ButtonManager_::getInstance() 11 | { 12 | static ButtonManager_ instance; 13 | return instance; 14 | } 15 | 16 | // Initialize the global shared instance 17 | ButtonManager_ &ButtonManager = ButtonManager.getInstance(); 18 | 19 | // The "static" event handler simply calls the non-static event handler on the 20 | // shared singleton instance 21 | void staticHandleEvent(AceButton *button, uint8_t eventType, uint8_t buttonState) 22 | { 23 | ButtonManager.handleEvent(button, eventType, buttonState); 24 | } 25 | 26 | void ButtonManager_::setup() 27 | { 28 | for (int i = 0; i < 8; i++) 29 | { 30 | // Initialize the AceButton objects 31 | buttons.push_back(new AceButton(buttonPins[i])); 32 | // Set the input mode of the button pin 33 | pinMode(buttonPins[i], INPUT_PULLUP); 34 | } 35 | 36 | ButtonConfig *buttonConfig = ButtonConfig::getSystemButtonConfig(); 37 | buttonConfig->setEventHandler(staticHandleEvent); 38 | buttonConfig->setFeature(ButtonConfig::kFeatureClick); 39 | buttonConfig->setFeature(ButtonConfig::kFeatureDoubleClick); 40 | buttonConfig->setFeature(ButtonConfig::kFeatureLongPress); 41 | buttonConfig->setFeature(ButtonConfig::kFeatureRepeatPress); 42 | buttonConfig->setFeature( 43 | ButtonConfig::kFeatureSuppressClickBeforeDoubleClick); 44 | buttonConfig->setClickDelay(300); 45 | buttonConfig->setDoubleClickDelay(600); 46 | setStates(); 47 | } 48 | 49 | void ButtonManager_::tick() 50 | { 51 | for (int i = 0; i < 8; i++) 52 | { 53 | leds[i].Update(); 54 | } 55 | 56 | // Check if the menu buttons are being pressed. If so, show menu 57 | checkButtons(); 58 | } 59 | 60 | AceButton *ButtonManager_::getButton(uint8_t index) 61 | { 62 | return buttons[index]; 63 | } 64 | 65 | void ButtonManager_::setBrightness(uint8_t val) 66 | { 67 | for (int i = 0; i < 8; i++) 68 | { 69 | leds[i].MaxBrightness(val); 70 | } 71 | setStates(); 72 | } 73 | 74 | void ButtonManager_::turnAllOff() 75 | { 76 | for (int i = 0; i < 8; i++) 77 | { 78 | leds[i].Off(); 79 | } 80 | } 81 | 82 | void ButtonManager_::checkButtons() 83 | { 84 | for (int i = 0; i < 8; i++) 85 | getButton(i)->check(); 86 | } 87 | 88 | uint8_t ButtonManager_::getButtonIndex(uint8_t pin) 89 | { 90 | for (uint8_t i = 0; i < 8; i++) 91 | { 92 | if (buttonPins[i] == pin) 93 | return i; 94 | } 95 | return -1; 96 | } 97 | 98 | void ButtonManager_::handleEvent(AceButton *button, uint8_t eventType, uint8_t buttonState) 99 | { 100 | uint8_t btn = getButtonIndex(button->getPin()); 101 | switch (eventType) 102 | { 103 | case AceButton::kEventClicked: 104 | return handleSingleClick(btn); 105 | case AceButton::kEventLongPressed: 106 | return handleLongClick(btn); 107 | case AceButton::kEventDoubleClicked: 108 | return handleDoubleClick(btn); 109 | case AceButton::kEventPressed: 110 | return handlePressed(btn); 111 | case AceButton::kEventReleased: 112 | return handleReleased(btn); 113 | } 114 | } 115 | 116 | void ButtonManager_::ShowAnimation(uint8_t type, uint8_t btn) 117 | { 118 | int ledtype = SystemManager.getInt("leds"); 119 | if (ledtype != 4) 120 | return; 121 | int count = 1; 122 | switch (type) 123 | { 124 | case 1: 125 | leds[btn].FadeOn(50).FadeOff(800).Repeat(type); 126 | 127 | for (uint8_t i = btn + 1; i < 8; i++) 128 | { 129 | leds[i].DelayBefore(50 * ++count).FadeOn(400).FadeOff(400).Repeat(type); 130 | } 131 | count = 1; 132 | for (int x = btn - 1; x >= 0; x--) 133 | { 134 | leds[x].DelayBefore(50 * ++count).FadeOn(400).FadeOff(400).Repeat(type); 135 | } 136 | break; 137 | case 2: 138 | for (uint8_t i = 0; i < 8; i++) 139 | { 140 | leds[i].Blink(100, 100).Repeat(2); 141 | } 142 | break; 143 | case 3: 144 | for (uint8_t i = 0; i < 8; i++) 145 | { 146 | leds[i].Breathe(2000).Repeat(1); 147 | } 148 | break; 149 | case 4: 150 | for (uint8_t i = 0; i < 8; i++) 151 | { 152 | leds[i].FadeOn(200).Repeat(1); 153 | } 154 | break; 155 | case 5: 156 | for (uint8_t i = 0; i < 8; i++) 157 | { 158 | leds[i].FadeOff(200).Repeat(1); 159 | } 160 | break; 161 | } 162 | prevMillis = 0; 163 | } 164 | 165 | boolean getPushSetting(uint8_t btn) 166 | { 167 | switch (btn) 168 | { 169 | case 0: 170 | return SystemManager.getBool("btn1push"); 171 | break; 172 | case 1: 173 | return SystemManager.getBool("btn2push"); 174 | break; 175 | case 2: 176 | return SystemManager.getBool("btn3push"); 177 | break; 178 | case 3: 179 | return SystemManager.getBool("btn4push"); 180 | break; 181 | case 4: 182 | return SystemManager.getBool("btn5push"); 183 | break; 184 | case 5: 185 | return SystemManager.getBool("btn6push"); 186 | break; 187 | case 6: 188 | return SystemManager.getBool("btn7push"); 189 | break; 190 | case 7: 191 | return SystemManager.getBool("btn8push"); 192 | break; 193 | default: 194 | return false; 195 | break; 196 | } 197 | } 198 | 199 | void ButtonManager_::handleSingleClick(uint8_t btn) 200 | { 201 | 202 | if (!getPushSetting(btn)) 203 | { 204 | SystemManager.ShowButtonScreen(btn, "Click"); 205 | ShowAnimation(1, btn); 206 | SendState(1, btn); 207 | } 208 | } 209 | 210 | void ButtonManager_::handleDoubleClick(uint8_t btn) 211 | { 212 | if (!getPushSetting(btn)) 213 | { 214 | SystemManager.ShowButtonScreen(btn, "Double"); 215 | ShowAnimation(2, btn); 216 | SendState(2, btn); 217 | } 218 | } 219 | 220 | void ButtonManager_::handleLongClick(uint8_t btn) 221 | { 222 | 223 | if (!getPushSetting(btn)) 224 | { 225 | SystemManager.ShowButtonScreen(btn, "Long"); 226 | ShowAnimation(3, btn); 227 | SendState(3, btn); 228 | } 229 | } 230 | 231 | void ButtonManager_::handlePressed(uint8_t btn) 232 | { 233 | if (getPushSetting(btn)) 234 | { 235 | SystemManager.ShowButtonScreen(btn, "Down"); 236 | ShowAnimation(4, btn); 237 | SendState(4, btn); 238 | } 239 | } 240 | 241 | void ButtonManager_::handleReleased(uint8_t btn) 242 | { 243 | if (getPushSetting(btn)) 244 | { 245 | SystemManager.ShowButtonScreen(btn, "Up"); 246 | ShowAnimation(5, btn); 247 | SendState(5, btn); 248 | } 249 | } 250 | 251 | void ButtonManager_::SendState(int type, uint8_t btn) 252 | { 253 | switch (type) 254 | { 255 | case 1: 256 | MqttManager.publish(("button" + String(btn + 1) + "/click").c_str(), "true"); 257 | break; 258 | case 2: 259 | MqttManager.publish(("button" + String(btn + 1) + "/double_click").c_str(), "true"); 260 | break; 261 | case 3: 262 | MqttManager.publish(("button" + String(btn + 1) + "/long_click").c_str(), "true"); 263 | break; 264 | case 4: 265 | MqttManager.publish(("button" + String(btn + 1) + "/push").c_str(), "true"); 266 | break; 267 | case 5: 268 | MqttManager.publish(("button" + String(btn + 1) + "/push").c_str(), "false"); 269 | break; 270 | default: 271 | break; 272 | } 273 | 274 | if (type != 4 || type != 5) 275 | { 276 | delay(50); 277 | switch (type) 278 | { 279 | case 1: 280 | MqttManager.publish(("button" + String(btn + 1) + "/click").c_str(), "false"); 281 | break; 282 | case 2: 283 | MqttManager.publish(("button" + String(btn + 1) + "/double_click").c_str(), "false"); 284 | break; 285 | case 3: 286 | MqttManager.publish(("button" + String(btn + 1) + "/long_click").c_str(), "false"); 287 | break; 288 | default: 289 | break; 290 | } 291 | } 292 | } 293 | 294 | bool ButtonManager_::getButtonState(uint8_t btn) 295 | { 296 | return states[btn]; 297 | } 298 | 299 | void ButtonManager_::setButtonState(uint8_t btn, uint8_t state) 300 | { 301 | states[btn] = state; 302 | setStates(); 303 | } 304 | 305 | void ButtonManager_::setButtonLight(uint8_t btn, uint8_t mode) 306 | { 307 | switch (mode) 308 | { 309 | case 0: 310 | leds[btn].Off(1000); 311 | break; 312 | case 1: 313 | leds[btn].On(1000); 314 | break; 315 | case 2: 316 | leds[btn].DelayBefore(btn * 200).Breathe(2000).Forever(); 317 | break; 318 | case 3: 319 | switch (states[btn]) 320 | { 321 | case 0: 322 | leds[btn].Off(1000); 323 | break; 324 | case 1: 325 | leds[btn].On(1000); 326 | break; 327 | case 2: 328 | leds[btn].Breathe(1000).Forever(); 329 | break; 330 | default: 331 | break; 332 | } 333 | break; 334 | default: 335 | leds[btn].Off(1000); 336 | } 337 | } 338 | 339 | void ButtonManager_::setStates() 340 | { 341 | int ledtype = SystemManager.getInt("leds"); 342 | if (ledtype != 4) 343 | { 344 | setButtonLight(0, ledtype); 345 | setButtonLight(1, ledtype); 346 | setButtonLight(2, ledtype); 347 | setButtonLight(3, ledtype); 348 | setButtonLight(4, ledtype); 349 | setButtonLight(5, ledtype); 350 | setButtonLight(6, ledtype); 351 | setButtonLight(7, ledtype); 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /src/ButtonManager.h: -------------------------------------------------------------------------------- 1 | #ifndef ButtonManager_h 2 | #define ButtonManager_h 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace ace_button; 11 | using namespace std; 12 | 13 | class ButtonManager_ 14 | { 15 | private: 16 | // The pins the buttons are connected to 17 | const uint8_t buttonPins[8] = {32, 25, 27, 12, 19, 5, 16, 0}; 18 | 19 | // Different states and modes of the buttons 20 | uint8_t states[8] = {0, 0, 0, 0, 0, 0, 0, 0}; 21 | bool longPressed[8] = {false, false, false, false, false, false, false, false}; 22 | bool modes[8] = {0, 0, 0, 0, 0, 0, 0, 0}; 23 | 24 | // The button references themselves 25 | vector buttons; 26 | 27 | void SendState(int type, uint8_t btn); 28 | uint8_t getButtonIndex(uint8_t); 29 | 30 | // This method is the class-internal event handler with access to the instance state 31 | void handleEvent(AceButton *, uint8_t, uint8_t); 32 | void handleSingleClick(uint8_t); 33 | void handleDoubleClick(uint8_t); 34 | void handleLongClick(uint8_t); 35 | void handlePressed(uint8_t); 36 | void handleReleased(uint8_t); 37 | 38 | // This references the global/static event handler and marks it as a friend of this class, 39 | // which allows it to call the class-internal event handler, even though it is marked as private 40 | friend void staticHandleEvent(AceButton *, uint8_t, uint8_t); 41 | 42 | ButtonManager_() = default; 43 | 44 | public: 45 | static ButtonManager_ &getInstance(); 46 | 47 | JLed leds[8] = { 48 | JLed(33).Off().LowActive(), 49 | JLed(26).Off().LowActive(), 50 | JLed(14).Off().LowActive(), 51 | JLed(13).Off().LowActive(), 52 | JLed(23).Off().LowActive(), 53 | JLed(18).Off().LowActive(), 54 | JLed(17).Off().LowActive(), 55 | JLed(4).Off().LowActive()}; 56 | 57 | 58 | void setup(); 59 | void tick(); 60 | void setStates(); 61 | void checkButtons(); 62 | void LeaveMenuState(); 63 | void turnAllOff(); 64 | void setBrightness(uint8_t btn); 65 | void ShowAnimation(uint8_t type, uint8_t btn); 66 | bool getButtonState(uint8_t); 67 | void setButtonState(uint8_t, uint8_t); 68 | void setButtonLight(uint8_t btn, uint8_t mode); 69 | 70 | AceButton *getButton(uint8_t); 71 | }; 72 | 73 | extern ButtonManager_ &ButtonManager; 74 | 75 | #endif -------------------------------------------------------------------------------- /src/MqttManager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | //#include "TinyMqtt.h" 7 | 8 | // MqttBroker broker(PORT); 9 | WiFiClient espClient; 10 | PubSubClient client(espClient); 11 | 12 | byte *buffer; 13 | boolean Rflag = false; 14 | int r_len; 15 | 16 | // The getter for the instantiated singleton instance 17 | MqttManager_ &MqttManager_::getInstance() 18 | { 19 | static MqttManager_ instance; 20 | return instance; 21 | } 22 | 23 | // Initialize the global shared instance 24 | MqttManager_ &MqttManager = MqttManager.getInstance(); 25 | 26 | void callback(char *topic, byte *payload, unsigned int length) 27 | { 28 | payload[length] = '\0'; 29 | String strTopic = String(topic); 30 | String strPayload = String((char *)payload); 31 | 32 | if (strTopic == SystemManager.getValue("mqttprefix") + String("/brightness")) 33 | { 34 | SystemManager.setBrightness(atoi(strPayload.c_str())); 35 | } 36 | if (strTopic == SystemManager.getValue("mqttprefix") + String("/button1/state")) 37 | { 38 | ButtonManager.setButtonState(0, atoi(strPayload.c_str())); 39 | } 40 | if (strTopic == SystemManager.getValue("mqttprefix") + String("/button2/state")) 41 | { 42 | ButtonManager.setButtonState(1, atoi(strPayload.c_str())); 43 | } 44 | if (strTopic == SystemManager.getValue("mqttprefix") + String("/button3/state")) 45 | { 46 | ButtonManager.setButtonState(2, atoi(strPayload.c_str())); 47 | } 48 | if (strTopic == SystemManager.getValue("mqttprefix") + String("/button4/state")) 49 | { 50 | ButtonManager.setButtonState(3, atoi(strPayload.c_str())); 51 | } 52 | if (strTopic == SystemManager.getValue("mqttprefix") + String("/button5/state")) 53 | { 54 | ButtonManager.setButtonState(4, atoi(strPayload.c_str())); 55 | } 56 | if (strTopic == SystemManager.getValue("mqttprefix") + String("/button6/state")) 57 | { 58 | ButtonManager.setButtonState(5, atoi(strPayload.c_str())); 59 | } 60 | if (strTopic == SystemManager.getValue("mqttprefix") + String("/button7/state")) 61 | { 62 | ButtonManager.setButtonState(6, atoi(strPayload.c_str())); 63 | } 64 | if (strTopic == SystemManager.getValue("mqttprefix") + String("/button8/state")) 65 | { 66 | ButtonManager.setButtonState(7, atoi(strPayload.c_str())); 67 | } 68 | if (strTopic == SystemManager.getValue("mqttprefix") + String("/message")) 69 | { 70 | SystemManager.ShowMessage(strPayload); 71 | } 72 | if (strTopic == SystemManager.getValue("mqttprefix") + String("/image")) 73 | { 74 | SystemManager.ShowImage(strPayload); 75 | } 76 | } 77 | 78 | long lastReconnectAttempt = 0; 79 | 80 | boolean reconnect() 81 | { 82 | if (client.connect(SystemManager.getValue("mqttprefix"), SystemManager.getValue("mqttuser"), SystemManager.getValue("mqttpwd"))) 83 | { 84 | client.subscribe((SystemManager.getValue("mqttprefix") + String("/brightness")).c_str()); 85 | client.subscribe((SystemManager.getValue("mqttprefix") + String("/button1/state")).c_str()); 86 | client.subscribe((SystemManager.getValue("mqttprefix") + String("/button2/state")).c_str()); 87 | client.subscribe((SystemManager.getValue("mqttprefix") + String("/button3/state")).c_str()); 88 | client.subscribe((SystemManager.getValue("mqttprefix") + String("/button4/state")).c_str()); 89 | client.subscribe((SystemManager.getValue("mqttprefix") + String("/button5/state")).c_str()); 90 | client.subscribe((SystemManager.getValue("mqttprefix") + String("/button6/state")).c_str()); 91 | client.subscribe((SystemManager.getValue("mqttprefix") + String("/button7/state")).c_str()); 92 | client.subscribe((SystemManager.getValue("mqttprefix") + String("/button8/state")).c_str()); 93 | client.subscribe((SystemManager.getValue("mqttprefix") + String("/message")).c_str()); 94 | client.subscribe((SystemManager.getValue("mqttprefix") + String("/image")).c_str()); 95 | 96 | for (int i = 1; i < 9; i++) 97 | { 98 | MqttManager.publish(("button" + String(i) + "/click").c_str(), "false"); 99 | MqttManager.publish(("button" + String(i) + "/double_click").c_str(), "false"); 100 | MqttManager.publish(("button" + String(i) + "/long_click").c_str(), "false"); 101 | MqttManager.publish(("button" + String(i) + "/push").c_str(), "false"); 102 | } 103 | 104 | Serial.println("MQTT Connected"); 105 | } 106 | 107 | return client.connected(); 108 | } 109 | 110 | void MqttManager_::setup() 111 | { 112 | uint16_t port = SystemManager.getInt("mqttport"); 113 | client.setServer(SystemManager.getValue("mqttbroker"), port); 114 | client.setCallback(callback); 115 | lastReconnectAttempt = 0; 116 | } 117 | 118 | void MqttManager_::tick() 119 | { 120 | // if (menuInternalBroker.isActive()) broker.loop(); 121 | if (WiFi.isConnected()) 122 | { 123 | if (!client.connected()) 124 | { 125 | long now = millis(); 126 | if (now - lastReconnectAttempt > 5000) 127 | { 128 | lastReconnectAttempt = now; 129 | Serial.println("Attempt to connect to MQTT"); 130 | // Attempt to reconnect 131 | if (reconnect()) 132 | { 133 | lastReconnectAttempt = 0; 134 | } 135 | } 136 | } 137 | else 138 | { 139 | // Client connected 140 | client.loop(); 141 | } 142 | } 143 | } 144 | 145 | void MqttManager_::publish(const char *topic, const char *payload) 146 | { 147 | char result[100]; 148 | strcpy(result, SystemManager.getValue("mqttprefix")); 149 | strcat(result, "/"); 150 | strcat(result, topic); 151 | client.publish(result, payload); 152 | } -------------------------------------------------------------------------------- /src/MqttManager.h: -------------------------------------------------------------------------------- 1 | #ifndef MqttManager_h 2 | #define MqttManager_h 3 | 4 | #include 5 | #include 6 | 7 | class MqttManager_ 8 | { 9 | private: 10 | MqttManager_() = default; 11 | 12 | public: 13 | static MqttManager_ &getInstance(); 14 | 15 | void setup(); 16 | void tick(); 17 | void publish(const char *topic,const char *payload); 18 | }; 19 | 20 | extern MqttManager_ &MqttManager; 21 | 22 | #endif -------------------------------------------------------------------------------- /src/SystemManager.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "SSD1306.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "font.h" 11 | #include "images.h" 12 | #include "SPI.h" 13 | #include 14 | 15 | #define DISPLAY_WIDTH 128 // OLED display width, in pixels 16 | #define DISPLAY_HEIGHT 64 // OLED display height, in pixels 17 | const char *VERSION = "1.92"; 18 | 19 | // U8G2_SSD1306_1 028X64_NONAME_F_SW_I2C gfx(U8G2_R0, /* clock=*/SCL, /* data=*/SDA, /* reset=*/U8X8_PIN_NONE); 20 | SSD1306 gfx(0x3c, SDA, SCL); 21 | 22 | const int daylightOffset_sec = 3600; 23 | tm timeinfo; 24 | 25 | uint8_t screen = 0; 26 | boolean connected = false; 27 | 28 | unsigned long previousMillis = 0; 29 | const long CLOCK_INTERVAL = 1000; 30 | const long PICTURE_INTERVAL = 2000; 31 | const long CHECK_WIFI_TIME = 10000; 32 | unsigned long PREVIOUS_WIFI_CHECK = 0; 33 | unsigned long PREVIOUS_WIFI_MILLIS = 0; 34 | const char *Pushtype; 35 | String Message; 36 | String Image; 37 | 38 | uint8_t BtnNr; 39 | boolean TypeShown; 40 | boolean MessageShown; 41 | boolean ImageShown; 42 | String weekDay; 43 | String fYear; 44 | String fDate; 45 | String fTime; 46 | 47 | File fsUploadFile; 48 | String temp = ""; 49 | 50 | const String weekDays[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 51 | bool colon_switch = true; 52 | const char *updateIndex = "
"; 53 | String params = "[" 54 | "{" 55 | "'name':'ssid'," 56 | "'label':'SSID'," 57 | "'type':" + 58 | String(INPUTTEXT) + "," 59 | "'default':''" 60 | "}," 61 | "{" 62 | "'name':'pwd'," 63 | "'label':'Password'," 64 | "'type':" + 65 | String(INPUTPASSWORD) + "," 66 | "'default':''" 67 | "}," 68 | "{" 69 | "'name':'mqttbroker'," 70 | "'label':'MQTT Broker'," 71 | "'type':" + 72 | String(INPUTTEXT) + "," 73 | "'default':''" 74 | "}," 75 | "{" 76 | "'name':'mqttprefix'," 77 | "'label':'MQTT Prefix'," 78 | "'type':" + 79 | String(INPUTTEXT) + "," 80 | "'default':'SmartPusher'" 81 | "}," 82 | "{" 83 | "'name':'mqttport'," 84 | "'label':'MQTT Port'," 85 | "'min':1,'max':9999," 86 | "'type':" + 87 | String(INPUTNUMBER) + "," 88 | "'default':'1883'" 89 | "}," 90 | "{" 91 | "'name':'mqttuser'," 92 | "'label':'MQTT Username'," 93 | "'type':" + 94 | String(INPUTTEXT) + "," 95 | "'default':''" 96 | "}," 97 | "{" 98 | "'name':'mqttpwd'," 99 | "'label':'MQTT Password'," 100 | "'type':" + 101 | String(INPUTPASSWORD) + "," 102 | "'default':''" 103 | "}," 104 | 105 | "{" 106 | "'name':'ntp'," 107 | "'label':'NTP Server'," 108 | "'type':" + 109 | String(INPUTTEXT) + "," 110 | "'default':'de.pool.ntp.org'" 111 | "}," 112 | "{" 113 | "'name':'tz'," 114 | "'label':'TZ INFO'," 115 | "'type':" + 116 | String(INPUTTEXT) + "," 117 | "'default':'CET-1CEST,M3.5.0/02,M10.5.0/03'" 118 | "}," 119 | "{" 120 | "'name':'colonblink'," 121 | "'label':'Colon Blink'," 122 | "'type':" + 123 | String(INPUTCHECKBOX) + "," 124 | "'default':'1'" 125 | "}," 126 | "{" 127 | "'name':'btn1push'," 128 | "'label':'Button 1 Pushmode'," 129 | "'type':" + 130 | String(INPUTCHECKBOX) + "," 131 | "'default':'0'" 132 | "}," 133 | "{" 134 | "'name':'btn2push'," 135 | "'label':'Button 2 Pushmode'," 136 | "'type':" + 137 | String(INPUTCHECKBOX) + "," 138 | "'default':'0'" 139 | "}," 140 | "{" 141 | "'name':'btn3push'," 142 | "'label':'Button 3 Pushmode'," 143 | "'type':" + 144 | String(INPUTCHECKBOX) + "," 145 | "'default':'0'" 146 | "}," 147 | "{" 148 | "'name':'btn4push'," 149 | "'label':'Button 4 Pushmode'," 150 | "'type':" + 151 | String(INPUTCHECKBOX) + "," 152 | "'default':'0'" 153 | "}," 154 | "{" 155 | "'name':'btn5push'," 156 | "'label':'Button 5 Pushmode'," 157 | "'type':" + 158 | String(INPUTCHECKBOX) + "," 159 | "'default':'0'" 160 | "}," 161 | "{" 162 | "'name':'btn6push'," 163 | "'label':'Button 6 Pushmode'," 164 | "'type':" + 165 | String(INPUTCHECKBOX) + "," 166 | "'default':'0'" 167 | "}," 168 | "{" 169 | "'name':'btn7push'," 170 | "'label':'Button 7 Pushmode'," 171 | "'type':" + 172 | String(INPUTCHECKBOX) + "," 173 | "'default':'0'" 174 | "}," 175 | "{" 176 | "'name':'btn8push'," 177 | "'label':'Button 8 Pushmode'," 178 | "'type':" + 179 | String(INPUTCHECKBOX) + "," 180 | "'default':'0'" 181 | "},{" 182 | "'name':'leds'," 183 | "'label':'LED Mode'," 184 | "'type':" + 185 | String(INPUTSELECT) + "," 186 | "'options':[" 187 | "{'v':'0','l':'Off'}," 188 | "{'v':'1','l':'On'}," 189 | "{'v':'2','l':'Fade'}," 190 | "{'v':'3','l':'Extern'}," 191 | "{'v':'4','l':'OnPush'}]," 192 | "'default':'1'" 193 | "}" 194 | "]"; 195 | 196 | WebServer server; 197 | WebConfig conf; 198 | 199 | void WiFiStationConnected(WiFiEvent_t event, WiFiEventInfo_t info) 200 | { 201 | Serial.println("Connected to AP successfully!"); 202 | } 203 | 204 | void WiFiGotIP(WiFiEvent_t event, WiFiEventInfo_t info) 205 | { 206 | Serial.println("WiFi connected"); 207 | Serial.println("IP address: "); 208 | Serial.println(WiFi.localIP()); 209 | } 210 | 211 | void WiFiStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) 212 | { 213 | Serial.println("Disconnected from WiFi access point"); 214 | Serial.print("WiFi lost connection. Reason: "); 215 | Serial.println("Trying to Reconnect"); 216 | WiFi.reconnect(); 217 | } 218 | 219 | boolean initWiFi() 220 | { 221 | 222 | WiFi.mode(WIFI_STA); 223 | WiFi.onEvent(WiFiStationConnected, WiFiEvent_t::SYSTEM_EVENT_STA_CONNECTED); 224 | WiFi.onEvent(WiFiGotIP, WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP); 225 | WiFi.onEvent(WiFiStationDisconnected, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); 226 | Serial.print("Verbindung zu "); 227 | Serial.print(conf.values[0]); 228 | Serial.println(" herstellen"); 229 | gfx.setFont(ArialMT_Plain_16); 230 | gfx.drawString(10, 5, "Connecting"); 231 | gfx.drawString(10, 30, "to WiFi..."); 232 | gfx.display(); 233 | if (conf.values[0] != "") 234 | { 235 | WiFi.begin(conf.values[0].c_str(), conf.values[1].c_str()); 236 | uint8_t cnt = 0; 237 | while ((WiFi.status() != WL_CONNECTED) && (cnt < 20)) 238 | { 239 | delay(500); 240 | Serial.print("."); 241 | cnt++; 242 | } 243 | Serial.println(); 244 | if (WiFi.status() == WL_CONNECTED) 245 | { 246 | 247 | gfx.setFont(ArialMT_Plain_16); 248 | Serial.print("IP-Adresse = "); 249 | Serial.println(WiFi.localIP()); 250 | gfx.clear(); 251 | gfx.drawString(20, 10, "Connected!"); 252 | gfx.drawString((DISPLAY_WIDTH - gfx.getStringWidth(WiFi.localIP().toString())) / 2, 40, WiFi.localIP().toString()); 253 | gfx.display(); 254 | connected = true; 255 | delay(3000); 256 | } 257 | } 258 | if (!connected) 259 | { 260 | gfx.setFont(ArialMT_Plain_16); 261 | WiFi.mode(WIFI_AP); 262 | WiFi.softAP("SmartPusher", "", 1); 263 | gfx.clear(); 264 | gfx.drawString(25, 15, "AP MODE"); 265 | gfx.drawString(20, 35, "192.168.4.1"); 266 | gfx.display(); 267 | } 268 | return connected; 269 | } 270 | 271 | // The getter for the instantiated singleton instance 272 | SystemManager_ &SystemManager_::getInstance() 273 | { 274 | static SystemManager_ instance; 275 | return instance; 276 | } 277 | 278 | // Initialize the global shared instance 279 | SystemManager_ &SystemManager = SystemManager.getInstance(); 280 | 281 | void SettingsSaved(String result) 282 | { 283 | ButtonManager.setStates(); 284 | return; 285 | } 286 | 287 | void update_started() 288 | { 289 | Serial.println("CALLBACK: HTTP update process started"); 290 | } 291 | 292 | void update_finished() 293 | { 294 | Serial.println("CALLBACK: HTTP update process finished"); 295 | } 296 | 297 | void update_progress(int cur, int total) 298 | { 299 | static int last_percent = 0; 300 | Serial.printf("CALLBACK: HTTP update process at %d of %d bytes...\n", cur, total); 301 | 302 | int percent = (100 * cur) / total; 303 | Serial.println(percent); 304 | 305 | gfx.display(); 306 | if (percent != last_percent) 307 | { 308 | uint8_t light = percent / 12.5; 309 | ButtonManager.setButtonLight(light, 1); 310 | ButtonManager.tick(); 311 | gfx.clear(); 312 | gfx.drawString(DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 2, String(percent) + "%"); 313 | last_percent = percent; 314 | gfx.display(); 315 | } 316 | } 317 | 318 | #ifndef Web 319 | 320 | String formatBytes(size_t bytes) 321 | { // lesbare Anzeige der Speichergrößen 322 | if (bytes < 1024) 323 | { 324 | return String(bytes) + " Byte"; 325 | } 326 | else if (bytes < (1024 * 1024)) 327 | { 328 | return String(bytes / 1024.0) + " KB"; 329 | } 330 | else if (bytes < (1024 * 1024 * 1024)) 331 | { 332 | return String(bytes / 1024.0 / 1024.0) + " MB"; 333 | } 334 | } 335 | 336 | String getContentType(String filename) 337 | { // convert the file extension to the MIME type 338 | if (filename.endsWith(".htm")) 339 | return "text/html"; 340 | else if (filename.endsWith(".css")) 341 | return "text/css"; 342 | else if (filename.endsWith(".js")) 343 | return "application/javascript"; 344 | else if (filename.endsWith(".ico")) 345 | return "image/x-icon"; 346 | else if (filename.endsWith(".gz")) 347 | return "application/x-gzip"; 348 | else if (filename.endsWith(".bmp")) 349 | return "image/bmp"; 350 | else if (filename.endsWith(".tif")) 351 | return "image/tiff"; 352 | else if (filename.endsWith(".pbm")) 353 | return "image/x-portable-bitmap"; 354 | else if (filename.endsWith(".jpg")) 355 | return "image/jpeg"; 356 | else if (filename.endsWith(".gif")) 357 | return "image/gif"; 358 | else if (filename.endsWith(".png")) 359 | return "image/png"; 360 | else if (filename.endsWith(".svg")) 361 | return "image/svg+xml"; 362 | else if (filename.endsWith(".html")) 363 | return "text/html"; 364 | else if (filename.endsWith(".wav")) 365 | return "audio/x-wav"; 366 | else if (filename.endsWith(".zip")) 367 | return "application/zip"; 368 | else if (filename.endsWith(".rgb")) 369 | return "image/x-rg"; 370 | else if (filename.endsWith(".bin")) 371 | return "application/octet-stream"; 372 | // Complete List on https://wiki.selfhtml.org/wiki/MIME-Type/Übersicht 373 | return "text/plain"; 374 | } 375 | 376 | bool handleFileRead(String path) 377 | { // send the right file to the client (if it exists) 378 | Serial.println("handleFileRead: " + path); 379 | if (path.endsWith("/")) 380 | path += "index.html"; // If a folder is requested, send the index file 381 | String contentType = getContentType(path); // Get the MIME type 382 | String pathWithGz = path + ".gz"; 383 | if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) 384 | { // If the file exists, either as a compressed archive, or normal 385 | if (SPIFFS.exists(pathWithGz)) // If there's a compressed version available 386 | path += ".gz"; // Use the compressed verion 387 | File file = SPIFFS.open(path, "r"); // Open the file 388 | size_t sent = server.streamFile(file, contentType); // Send it to the client 389 | file.close(); // Close the file again 390 | return true; 391 | } 392 | return false; 393 | } 394 | 395 | void handleNotFound() 396 | { 397 | if (!handleFileRead(server.uri())) 398 | { 399 | temp = ""; 400 | // HTML Header 401 | server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); 402 | server.sendHeader("Pragma", "no-cache"); 403 | server.sendHeader("Expires", "-1"); 404 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 405 | // HTML Content 406 | temp += ""; 407 | temp += ""; 409 | temp += "File not found"; 410 | temp += "

404 File Not Found


"; 411 | temp += "

Debug Information:


"; 412 | temp += ""; 413 | temp += "URI: "; 414 | temp += server.uri(); 415 | temp += "\nMethod: "; 416 | temp += (server.method() == HTTP_GET) ? "GET" : "POST"; 417 | temp += "
Arguments: "; 418 | temp += server.args(); 419 | temp += "\n"; 420 | for (uint8_t i = 0; i < server.args(); i++) 421 | { 422 | temp += " " + server.argName(i) + ": " + server.arg(i) + "\n"; 423 | } 424 | temp += "
Server Hostheader: " + server.hostHeader(); 425 | for (uint8_t i = 0; i < server.headers(); i++) 426 | { 427 | temp += " " + server.headerName(i) + ": " + server.header(i) + "\n
"; 428 | } 429 | temp += "

"; 430 | temp += "

You may want to browse to:

"; 431 | temp += "Main Page
"; 432 | temp += "WIFI Settings
"; 433 | temp += "Filemanager
"; 434 | temp += "


"; 435 | temp += ""; 436 | temp += ""; 437 | server.send(404, "", temp); 438 | server.client().stop(); // Stop is needed because we sent no content length 439 | temp = ""; 440 | } 441 | } 442 | 443 | void handleRoot() 444 | { 445 | conf.handleFormRequest(&server); 446 | if (server.hasArg("SAVE")) 447 | { 448 | 449 | } 450 | } 451 | 452 | void handleDisplayFS() 453 | { // HTML Filesystem 454 | // Page: /filesystem 455 | temp = ""; 456 | // HTML Header 457 | server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); 458 | server.sendHeader("Pragma", "no-cache"); 459 | server.sendHeader("Expires", "-1"); 460 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 461 | // HTML Content 462 | server.send(200, "text/html", temp); 463 | temp += ""; 464 | server.sendContent(temp); 465 | temp = ""; 466 | temp += "File System Manager"; 470 | temp += "

File Uploader

"; 471 | server.sendContent(temp); 472 | temp = ""; 473 | if (server.args() > 0) // Parameter wurden ubergeben 474 | { 475 | if (server.hasArg("delete")) 476 | { 477 | String FToDel = server.arg("delete"); 478 | if (SPIFFS.exists(FToDel)) 479 | { 480 | SPIFFS.remove(FToDel); 481 | temp += "File " + FToDel + " successfully deleted."; 482 | } 483 | else 484 | { 485 | temp += "File " + FToDel + " cannot be deleted."; 486 | } 487 | server.sendContent(temp); 488 | temp = ""; 489 | } 490 | if (server.hasArg("format") and server.arg("on")) 491 | { 492 | SPIFFS.format(); 493 | temp += "SPI File System successfully formatted."; 494 | server.sendContent(temp); 495 | temp = ""; 496 | } // server.client().stop(); // Stop is needed because we sent no content length 497 | } 498 | 499 | temp += formatBytes(SPIFFS.usedBytes() * 1.05) + " of " + formatBytes(SPIFFS.totalBytes()) + " used.
"; 500 | temp += formatBytes((SPIFFS.totalBytes() - (SPIFFS.usedBytes() * 1.05))) + " free.
"; 501 | temp += "
"; 502 | server.sendContent(temp); 503 | temp = ""; 504 | // Check for Site Parameters 505 | temp += "

Available Files on SPIFFS:

"; 506 | server.sendContent(temp); 507 | temp = ""; 508 | File root = SPIFFS.open("/"); 509 | File file = root.openNextFile(); 510 | while (file) 511 | { 512 | temp += ""; 514 | temp += ""; 515 | temp += ""; 516 | file = root.openNextFile(); 517 | } 518 | 519 | temp += ""; 520 | temp += "
FilenameSizeAction
" + String(file.name()) + "
"; 513 | temp += "
" + formatBytes(file.size()) + " Delete

"; 521 | temp += "

Choose File:

"; 522 | temp += "
\r\n
"; 523 | temp += "
"; 524 | server.sendContent(temp); 525 | temp = ""; 526 | temp += "
"; 527 | temp += " Back



"; 528 | server.sendContent(temp); 529 | temp = ""; 530 | temp += ""; 531 | // server.send ( 200, "", temp ); 532 | server.sendContent(temp); 533 | server.client().stop(); // Stop is needed because we sent no content length 534 | temp = ""; 535 | } 536 | 537 | void handleFileUpload() 538 | { // Dateien ins SPIFFS schreiben 539 | Serial.println("FileUpload Name:"); 540 | if (server.uri() != "/upload") 541 | return; 542 | HTTPUpload &upload = server.upload(); 543 | if (upload.status == UPLOAD_FILE_START) 544 | { 545 | String filename = upload.filename; 546 | if (upload.filename.length() > 30) 547 | { 548 | upload.filename = upload.filename.substring(upload.filename.length() - 30, upload.filename.length()); // Dateinamen auf 30 Zeichen kürzen 549 | } 550 | Serial.println("FileUpload Name: " + upload.filename); 551 | if (!filename.startsWith("/")) 552 | filename = "/" + filename; 553 | // fsUploadFile = SPIFFS.open(filename, "w"); 554 | fsUploadFile = SPIFFS.open("/" + server.urlDecode(upload.filename), "w"); 555 | filename = String(); 556 | } 557 | else if (upload.status == UPLOAD_FILE_WRITE) 558 | { 559 | // Serial.print("handleFileUpload Data: "); Serial.println(upload.currentSize); 560 | if (fsUploadFile) 561 | fsUploadFile.write(upload.buf, upload.currentSize); 562 | } 563 | else if (upload.status == UPLOAD_FILE_END) 564 | { 565 | if (fsUploadFile) 566 | fsUploadFile.close(); 567 | 568 | // Serial.print("handleFileUpload Size: "); Serial.println(upload.totalSize); 569 | // server.sendContent(Header); 570 | handleDisplayFS(); 571 | } 572 | } 573 | #endif // end Webregion 574 | 575 | void SystemManager_::setup() 576 | { 577 | delay(2000); 578 | gfx.init(); 579 | gfx.flipScreenVertically(); 580 | gfx.clear(); // clear the internal memory 581 | gfx.drawXbm(0, 0, 128, 64, logo); 582 | 583 | gfx.display(); 584 | 585 | delay(2000); 586 | gfx.clear(); 587 | gfx.setFont(ArialMT_Plain_24); 588 | gfx.drawString(45, 20, "v" + String(VERSION)); 589 | gfx.display(); 590 | delay(800); 591 | conf.registerOnSave(SettingsSaved); 592 | 593 | conf.setDescription(params); 594 | conf.readConfig(); 595 | gfx.clear(); 596 | initWiFi(); 597 | Update.onProgress(update_progress); 598 | server.onNotFound(handleNotFound); 599 | server.on("/", handleRoot); 600 | server.on( 601 | "/upload", HTTP_POST, []() 602 | { server.send(200, "text/plain", ""); }, 603 | handleFileUpload); 604 | server.on("/files", HTTP_GET, handleDisplayFS); 605 | server.on("/update", HTTP_GET, []() 606 | { 607 | server.sendHeader("Connection", "close"); 608 | 609 | server.send(200, "text/html", updateIndex); }); 610 | server.on( 611 | "/doupdate", HTTP_POST, []() 612 | { 613 | server.sendHeader("Connection", "close"); 614 | server.send(200, "text/plain", (Update.hasError()) ? "NOK" : "OK"); 615 | 616 | delay(1000); 617 | ESP.restart(); }, 618 | []() 619 | { 620 | HTTPUpload &upload = server.upload(); 621 | if (upload.status == UPLOAD_FILE_START) 622 | { 623 | gfx.setFont(ArialMT_Plain_24); 624 | ButtonManager.turnAllOff(); 625 | ButtonManager.tick(); 626 | Serial.setDebugOutput(true); 627 | Serial.printf("Update: %s\n", upload.filename.c_str()); 628 | uint32_t maxSketchSpace = (1048576 - 0x1000) & 0xFFFFF000; 629 | gfx.clear(); 630 | gfx.drawString(15, 25, "UPDATE"); 631 | 632 | gfx.display(); 633 | 634 | if (!Update.begin(maxSketchSpace)) 635 | { // start with max available size 636 | Update.printError(Serial); 637 | } 638 | } 639 | else if (upload.status == UPLOAD_FILE_WRITE) 640 | { 641 | 642 | if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) 643 | { 644 | Update.printError(Serial); 645 | } 646 | } 647 | else if (upload.status == UPLOAD_FILE_END) 648 | { 649 | if (Update.end(true)) 650 | { // true to set the size to the current progress 651 | Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); 652 | } 653 | else 654 | { 655 | Update.printError(Serial); 656 | } 657 | Serial.setDebugOutput(false); 658 | } 659 | yield(); 660 | }); 661 | server.begin(80); 662 | 663 | if (connected) 664 | { 665 | char dns[30]; 666 | sprintf(dns, "%s.local", conf.getString("mqttprefix")); 667 | if (MDNS.begin(dns)) 668 | { 669 | Serial.println("MDNS responder gestartet"); 670 | } 671 | 672 | configTzTime(conf.getString("tz").c_str(), conf.getString("ntp").c_str()); 673 | getLocalTime(&timeinfo); 674 | } 675 | } 676 | 677 | void SystemManager_::tick() 678 | { 679 | 680 | server.handleClient(); 681 | if (connected) 682 | { 683 | switch (screen) 684 | { 685 | case 0: 686 | renderClockScreen(); 687 | break; 688 | case 1: 689 | renderButtonScreen(); 690 | break; 691 | case 2: 692 | renderMessageScreen(); 693 | break; 694 | case 3: 695 | renderImageScreen(); 696 | break; 697 | default: 698 | break; 699 | } 700 | } 701 | else 702 | { 703 | delay(50); 704 | } 705 | } 706 | 707 | void SystemManager_::drawtext(uint8_t x, uint8_t y, String text) 708 | { 709 | } 710 | 711 | void SystemManager_::show() 712 | { 713 | gfx.display(); 714 | } 715 | 716 | void SystemManager_::clear() 717 | { 718 | gfx.clear(); 719 | } 720 | 721 | void SystemManager_::setBrightness(uint8_t val) 722 | { 723 | gfx.setContrast(val); 724 | if (val == 0) 725 | { 726 | gfx.displayOff(); 727 | } 728 | else 729 | { 730 | gfx.displayOn(); 731 | }; 732 | 733 | ButtonManager.setBrightness(val); 734 | } 735 | 736 | const char *SystemManager_::getValue(const char *tag) 737 | { 738 | return conf.getValue(tag); 739 | } 740 | 741 | boolean SystemManager_::getBool(const char *tag) 742 | { 743 | return conf.getBool(tag); 744 | } 745 | 746 | String SystemManager_::getString(const char *tag) 747 | { 748 | return conf.getString(tag); 749 | } 750 | 751 | int SystemManager_::getInt(const char *tag) 752 | { 753 | return conf.getInt(tag); 754 | } 755 | 756 | void SystemManager_::ShowButtonScreen(uint8_t btn, const char *type) 757 | { 758 | previousMillis = millis(); 759 | Pushtype = type; 760 | BtnNr = btn; 761 | screen = 1; 762 | } 763 | 764 | void SystemManager_::ShowMessage(String msg) 765 | { 766 | Message = msg; 767 | previousMillis = millis(); 768 | screen = 2; 769 | } 770 | 771 | void SystemManager_::ShowImage(String img) 772 | { 773 | Image = img; 774 | previousMillis = millis(); 775 | screen = 3; 776 | } 777 | 778 | void SystemManager_::renderMessageScreen() 779 | { 780 | static uint16_t start_at = 0; 781 | gfx.clear(); 782 | gfx.setFont(ArialMT_Plain_24); 783 | uint16_t firstline = gfx.drawStringMaxWidth(0, 0, 128, Message.substring(start_at)); 784 | gfx.display(); 785 | 786 | unsigned long currentMillis = millis(); 787 | if (currentMillis - previousMillis >= CLOCK_INTERVAL) 788 | { 789 | previousMillis = currentMillis; 790 | if (firstline != 0) 791 | { 792 | start_at += firstline; 793 | } 794 | else 795 | { 796 | if (MessageShown) 797 | { 798 | start_at = 0; 799 | screen = 0; 800 | MessageShown = false; 801 | return; 802 | } 803 | MessageShown = true; 804 | } 805 | } 806 | } 807 | 808 | void SystemManager_::renderButtonScreen() 809 | { 810 | if (!TypeShown) 811 | { 812 | gfx.clear(); 813 | gfx.setFont(Roboto_Black_36); 814 | gfx.drawString((DISPLAY_WIDTH - gfx.getStringWidth(Pushtype)) / 2, 15, Pushtype); 815 | gfx.display(); 816 | TypeShown = true; 817 | } 818 | unsigned long currentMillis = millis(); 819 | if (currentMillis - previousMillis >= CLOCK_INTERVAL) 820 | { 821 | previousMillis = currentMillis; 822 | screen = 0; 823 | TypeShown = false; 824 | } 825 | } 826 | 827 | void SystemManager_::renderImageScreen() 828 | { 829 | gfx.clear(); 830 | uint8_t h; 831 | uint8_t w; 832 | uint8_t xpos; 833 | uint8_t b; 834 | if (SPIFFS.exists("/" + Image + ".bin")) 835 | { 836 | File myFile = SPIFFS.open("/" + Image + ".bin", "r"); 837 | if (myFile) 838 | { 839 | w = myFile.read(); // read the dimension of the bitmap 840 | h = myFile.read(); 841 | for (size_t y = 0; y < h; y++) 842 | { 843 | xpos = 0; 844 | for (size_t i = 0; i < (w / 8); i++) 845 | { 846 | { 847 | b = myFile.read(); 848 | for (uint8_t bt = 0; bt < 8; bt++) 849 | { 850 | if (bitRead(b, bt)) 851 | { // check one pixel 852 | gfx.setPixelColor(xpos, y, WHITE); 853 | } 854 | else 855 | { 856 | gfx.setPixelColor(xpos, y, BLACK); 857 | } 858 | xpos++; 859 | } 860 | } 861 | } 862 | } 863 | 864 | myFile.close(); // all done, close the file 865 | gfx.display(); 866 | } 867 | } 868 | else 869 | { 870 | gfx.setFont(ArialMT_Plain_16); 871 | gfx.drawString(14, 25, "NOT FOUND!"); 872 | gfx.display(); 873 | } 874 | 875 | unsigned long currentMillis = millis(); 876 | if (currentMillis - previousMillis >= PICTURE_INTERVAL) 877 | { 878 | previousMillis = currentMillis; 879 | screen = 0; 880 | ImageShown = false; 881 | } 882 | } 883 | 884 | void SystemManager_::renderClockScreen() 885 | { 886 | unsigned long currentMillis = millis(); 887 | if (currentMillis - previousMillis >= CLOCK_INTERVAL) 888 | { 889 | getLocalTime(&timeinfo); 890 | gfx.clear(); 891 | 892 | previousMillis = currentMillis; 893 | weekDay = weekDays[timeinfo.tm_wday]; 894 | 895 | if (conf.getBool("colonblink")) 896 | { 897 | colon_switch = !colon_switch; 898 | } 899 | else 900 | { 901 | colon_switch = true; 902 | } 903 | 904 | fYear = String(1900 + timeinfo.tm_year); 905 | fDate = (timeinfo.tm_mday < 10 ? "0" : "") + String(timeinfo.tm_mday) + "/" + (timeinfo.tm_mon + 1 < 10 ? "0" : "") + String(timeinfo.tm_mon + 1); 906 | fTime = (timeinfo.tm_hour < 10 ? "0" : "") + String(timeinfo.tm_hour) + (colon_switch ? ":" : " ") + (timeinfo.tm_min < 10 ? "0" : "") + String(timeinfo.tm_min); 907 | gfx.setFont(ArialMT_Plain_16); 908 | gfx.drawString(0, 0, fDate); 909 | gfx.setFont(ArialMT_Plain_16); 910 | gfx.drawString(95, 0, weekDay); 911 | gfx.setFont(DSEG14_Modern_Mini_Regular_30); 912 | gfx.drawString((DISPLAY_WIDTH - gfx.getStringWidth(fTime)) / 2, 25, fTime); 913 | gfx.display(); 914 | } 915 | } 916 | -------------------------------------------------------------------------------- /src/SystemManager.h: -------------------------------------------------------------------------------- 1 | #ifndef SystemManager_h 2 | #define SystemManager_h 3 | 4 | #include 5 | 6 | class SystemManager_ 7 | { 8 | private: 9 | SystemManager_() = default; 10 | 11 | public: 12 | static SystemManager_ &getInstance(); 13 | void setup(); 14 | void tick(); 15 | void show(); 16 | void clear(); 17 | void renderMessageScreen(); 18 | void renderImageScreen(); 19 | void renderButtonScreen(); 20 | void renderClockScreen(); 21 | void drawtext(uint8_t x, uint8_t y, String text); 22 | void ShowButtonScreen(uint8_t btn, const char *type); 23 | void setBrightness(uint8_t val); 24 | void ShowMessage(String msg); 25 | void ShowImage(String msg); 26 | const char *getValue(const char *name); 27 | boolean getBool(const char *name); 28 | int getInt(const char *name); 29 | String getString(const char *name); 30 | }; 31 | 32 | extern SystemManager_ &SystemManager; 33 | 34 | #endif -------------------------------------------------------------------------------- /src/images.h: -------------------------------------------------------------------------------- 1 | // 'Smart Pusher (1)', 128x64px 2 | const unsigned char logo [] PROGMEM = { 3 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 4 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 5 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 6 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 7 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 8 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 9 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 10 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 11 | 0x00, 0xF0, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 12 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xF2, 0x03, 0x00, 0x00, 0x00, 0x00, 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x80, 0x07, 14 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 15 | 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x1C, 18 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 19 | 0x80, 0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20 | 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 21 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x18, 22 | 0x00, 0xE0, 0x7F, 0xFC, 0xFF, 0xF9, 0x3F, 0xFF, 0xE7, 0xFF, 0x00, 0x00, 23 | 0x80, 0x01, 0x00, 0x38, 0x00, 0xF0, 0xFF, 0xFE, 0xFF, 0xF9, 0x3F, 0xFF, 24 | 0xF7, 0xFF, 0x00, 0x00, 0x80, 0x01, 0x1F, 0x38, 0x00, 0xF0, 0xFF, 0xFE, 25 | 0xFF, 0xF9, 0xBF, 0xFF, 0xE7, 0xFF, 0x00, 0x00, 0x80, 0x81, 0x3F, 0x38, 26 | 0x00, 0x70, 0x00, 0x0E, 0xC7, 0x3D, 0x30, 0x03, 0x07, 0x06, 0x00, 0x00, 27 | 0x80, 0xC3, 0x39, 0x38, 0x00, 0xE0, 0x65, 0x0E, 0xC7, 0x39, 0x38, 0x07, 28 | 0x07, 0x0E, 0x00, 0x00, 0x80, 0xC3, 0x71, 0x18, 0x00, 0xF0, 0xFF, 0x0C, 29 | 0xC7, 0x1D, 0xB8, 0x03, 0x07, 0x0E, 0x00, 0x00, 0x00, 0xC3, 0x30, 0x1C, 30 | 0x00, 0xF0, 0xFF, 0x0C, 0xC7, 0xB9, 0xBA, 0x97, 0x07, 0x0E, 0x00, 0x00, 31 | 0x00, 0xC7, 0x31, 0x0C, 0x00, 0x40, 0xE0, 0x0E, 0xC7, 0xFD, 0x3F, 0xFF, 32 | 0x07, 0x0E, 0x00, 0x00, 0x00, 0xCE, 0x30, 0x0E, 0x00, 0x00, 0xE4, 0x1E, 33 | 0x83, 0xF9, 0xBF, 0xFF, 0x07, 0x0E, 0x00, 0x00, 0x00, 0xCE, 0x70, 0x07, 34 | 0x00, 0xF0, 0xFF, 0x0E, 0xC7, 0xBD, 0x3A, 0xE7, 0x03, 0x06, 0x00, 0x00, 35 | 0x00, 0xC8, 0x71, 0x02, 0x00, 0xF0, 0xFF, 0x0E, 0xC3, 0x1D, 0xB8, 0x83, 36 | 0x03, 0x0E, 0x00, 0x00, 0x00, 0xC0, 0x30, 0x00, 0x00, 0xA0, 0x9A, 0x04, 37 | 0x83, 0x18, 0x20, 0x81, 0x02, 0x06, 0x00, 0x00, 0x00, 0xC0, 0x31, 0x00, 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 39 | 0x00, 0xC0, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 41 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x70, 0x00, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 43 | 0x00, 0xC0, 0xF1, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 44 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xF0, 0xFF, 0x00, 0x00, 0x00, 0x00, 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC3, 0x70, 0xFE, 46 | 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 47 | 0xE0, 0xC7, 0x30, 0x9C, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 48 | 0x00, 0x00, 0x00, 0x00, 0xE0, 0xDE, 0x31, 0x8C, 0x3F, 0x00, 0x00, 0x00, 49 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xDC, 0x30, 0x0E, 50 | 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 51 | 0xE0, 0xF8, 0x38, 0x8C, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 52 | 0x00, 0x00, 0x00, 0x00, 0xC0, 0xF1, 0x10, 0x00, 0x70, 0x80, 0x11, 0x04, 53 | 0x00, 0x18, 0x10, 0x00, 0x84, 0x41, 0x44, 0x00, 0xC0, 0xF1, 0x00, 0x00, 54 | 0x30, 0xF0, 0xFF, 0x0C, 0xDC, 0xFF, 0x3B, 0x70, 0xFF, 0xE7, 0xFF, 0x01, 55 | 0x80, 0xE1, 0x00, 0x00, 0x70, 0xF0, 0xFF, 0x0E, 0xDC, 0xFF, 0x3B, 0x70, 56 | 0xFF, 0xEF, 0xFF, 0x00, 0x80, 0xE3, 0x00, 0x00, 0x30, 0xF0, 0xEA, 0x0E, 57 | 0xDC, 0x4B, 0x38, 0x30, 0x9F, 0xE2, 0xE5, 0x01, 0x80, 0x43, 0x00, 0x00, 58 | 0x70, 0x60, 0xE0, 0x0E, 0xDC, 0x01, 0x30, 0x70, 0x07, 0xE0, 0xC0, 0x01, 59 | 0x00, 0x03, 0x00, 0x00, 0x70, 0x70, 0xE0, 0x0E, 0xDC, 0xFF, 0xFB, 0x7F, 60 | 0xFF, 0xEF, 0xC0, 0x00, 0x00, 0x07, 0x00, 0x00, 0x30, 0x70, 0xE0, 0x0C, 61 | 0xDC, 0xFF, 0xFB, 0x7F, 0xFF, 0xE7, 0xE0, 0x01, 0x00, 0x06, 0x00, 0x00, 62 | 0x70, 0xE0, 0xFF, 0x0C, 0x9C, 0xFB, 0xF3, 0x7F, 0xFF, 0xEF, 0xFF, 0x01, 63 | 0x00, 0x0E, 0x00, 0x00, 0x30, 0xF0, 0xFF, 0x0E, 0x0C, 0x80, 0x3B, 0x70, 64 | 0x07, 0xE0, 0xFF, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x30, 0xF0, 0xFF, 0xFE, 65 | 0xDF, 0xDF, 0x3B, 0x30, 0xBF, 0xE7, 0xFF, 0x00, 0x00, 0x1C, 0x00, 0x00, 66 | 0x30, 0x70, 0x00, 0xFE, 0xDF, 0xFF, 0x39, 0x70, 0xFF, 0xE7, 0x70, 0x00, 67 | 0x00, 0x1C, 0x00, 0x00, 0x38, 0x70, 0x00, 0xFE, 0xDF, 0xFF, 0x3B, 0x70, 68 | 0xFF, 0xE7, 0xE0, 0x00, 0x00, 0x18, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 69 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 70 | 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 71 | 0x00, 0x30, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 72 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 74 | 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 75 | 0x00, 0xC0, 0x01, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 76 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x07, 0xC0, 0x03, 0x00, 0x00, 0x00, 77 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFB, 78 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 79 | 0x00, 0x00, 0xFE, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 80 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x07, 0x00, 0x00, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 84 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 85 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 86 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 87 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 88 | 0x00, 0x00, 0x00, 0x00 89 | }; 90 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SmartPusher 3 | 4 | Copyright (c) 2022, Blueforcer 5 | admin@blueforcer.de 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this list 12 | of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright notice, this 15 | list of conditions and the following disclaimer in the documentation and/or other 16 | materials provided with the distribution. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 19 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 20 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 21 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 23 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 25 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 28 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 30 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | 36 | void setup() 37 | { 38 | delay(1000); 39 | Serial.begin(115200); 40 | SystemManager.setup(); 41 | ButtonManager.setup(); 42 | MqttManager.setup(); 43 | } 44 | 45 | void loop() 46 | { 47 | ButtonManager.tick(); 48 | SystemManager.tick(); 49 | MqttManager.tick(); 50 | } --------------------------------------------------------------------------------