├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs └── VARIABLES.md ├── examples └── Simple │ └── Simple.ino ├── img ├── Bleeper.png ├── Bleeper.svg ├── diagram.ai ├── diagram.png ├── smartphone.png └── web.png ├── keywords.txt ├── lib └── readme.txt ├── library.json ├── library.properties ├── platformio.ini └── src ├── Bleeper.h ├── Bleeper ├── Bleeper+Configuration.cpp ├── Bleeper+ConfigurationInterface.cpp ├── Bleeper+Connection.cpp ├── Bleeper+Storage.cpp ├── BleeperClass.cpp ├── BleeperClass.h └── Initable.h ├── Configuration ├── BaseConfiguration.h ├── ConfigMap.cpp ├── ConfigMap.h ├── Configuration.h ├── ConfigurationDictionary.cpp ├── ConfigurationDictionary.h ├── ConfigurationMacros.h ├── RootConfiguration.h ├── StringConvertibleVariable.cpp ├── StringConvertibleVariable.h └── Variable.h ├── ConfigurationInterface ├── ConfigurationInterface.h └── WebServer │ ├── BleeperWebServer.cpp │ ├── BleeperWebServer.h │ ├── ESP32 │ ├── ESP32DefaultWebServer.cpp │ ├── ESP32DefaultWebServer.h │ ├── HTTPRequest.cpp │ └── HTTPRequest.h │ ├── ESP8266 │ ├── ESP8266DefaultWebServer.cpp │ └── ESP8266DefaultWebServer.h │ └── WebPage.h ├── Connectivity ├── AP.cpp ├── AP.h ├── Connection.cpp ├── Connection.h ├── Wifi.cpp └── Wifi.h ├── Helpers ├── Logger.cpp ├── Logger.h ├── macros.h ├── utils.cpp └── utils.h ├── Observers ├── ConfigurationObserver.cpp ├── ConfigurationObserver.h └── Observable.h └── Storage ├── EEPROMHelper.h ├── SPIFFSStorage.h ├── Storage.h └── VariablesMapStorage.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | .pioenvs 34 | .piolibdeps 35 | .clang_complete 36 | .gcc-flags.json 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < http://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < http://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < http://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choice one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # 39 | # script: 40 | # - platformio run 41 | 42 | 43 | # 44 | # Template #2: The project is intended to by used as a library with examples 45 | # 46 | 47 | # language: python 48 | # python: 49 | # - "2.7" 50 | # 51 | # sudo: false 52 | # cache: 53 | # directories: 54 | # - "~/.platformio" 55 | # 56 | # env: 57 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 58 | # - PLATFORMIO_CI_SRC=examples/file.ino 59 | # - PLATFORMIO_CI_SRC=path/to/test/directory 60 | # 61 | # install: 62 | # - pip install -U platformio 63 | # 64 | # script: 65 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Diego Ernst 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | Bleeper is a library to manage your firmware configurations written in C++ for [ESP8266](https://github.com/esp8266/Arduino) and [ESP32](https://github.com/espressif/arduino-esp32) Arduino Platforms. 6 | 7 | - [Features](#features) 8 | - [Why Bleeper](#why-bleeper) 9 | - [Usage](#usage) 10 | - [Installation](#installation) 11 | - [Arduino IDE](#arduino-ide) 12 | - [PlatformIO IDE for Atom](#platformio-ide) 13 | - [Future Work](#future-work) 14 | 15 | ## Features 16 | 17 | - [x] Fully customizable hierarchical configuration structure 18 | - [x] Generic property types 19 | - [x] Automatic storage with property granularity (EEPROM & SPIFFS) 20 | - [x] Wifi & AP connections 21 | - [x] Configuration interfaces (web panel by default) 22 | - [x] Observe any configuration property change through the observer API 23 | 24 | ## Why Bleeper 25 | 26 | In the scenario of prototyping with hardware devices you will likely end up with a bunch of configuration settings like pin numbers, sensor thresholds, device ids, connection credentials, port numbers, and so many others. 27 | As a good programmer you decide to define these configurations in a "configuration file" with a lot of `#define` macros or constants. 28 | 29 | But... what if you want to change those values? Downloading the firmware each time is tedious. Think about the cases where you have multiple devices to configure or even worst you don't have close physical access to them. 30 | 31 | At the end you realize that you actually need some sort of "Configuration Manager" with high level features like exposing those variables on a web panel (or another type of interface), persisting some of them (usually your Wifi credentials) and so on. 32 | 33 | ## Usage 34 | 35 | Suppose that you have this configuration: 36 | 37 |

38 | 39 |

40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | The above tree structure speaks by its own, what's worth to mention here is that we want `wifi.ssid` and `wifi.passwrod` to be persistent using the Bleeper storage. 48 | 49 | The C++ code will look like this: 50 | 51 | ```cpp 52 | #include "Bleeper.h" 53 | 54 | class Config: public RootConfiguration { 55 | public: 56 | stringVar(name, "Default Device Name"); 57 | subconfig(WifiConfig, wifi); 58 | subconfig(LedsConfig, leds); 59 | }; 60 | 61 | class WifiConfig: public Configuration { 62 | public: 63 | persistentStringVar(ssid, "MySSID"); 64 | persistentStringVar(password, "MyPassword"); 65 | }; 66 | 67 | class LedsConfig: public Configuration { 68 | public: 69 | floatVar(calibration, 1.5); 70 | subconfig(SPIConfig, spi); 71 | }; 72 | 73 | class SPIConfig: public Configuration { 74 | public: 75 | intVar(speed, 1000000); 76 | }; 77 | ``` 78 | 79 | Basically, per each configuration node you have to implement a subclass of `Configuration` and a `RootConfiguration` subclass for the especial root node (i.e the top level entry). 80 | 81 | > For a full documentation on how properties are defined read [here](docs/VARIABLES.md). 82 | 83 | Once we completed our configuration structure we can use it like this: 84 | 85 | ```cpp 86 | // Your Config instance 87 | Config C; 88 | 89 | void loop() { 90 | // access to your spi speed config 91 | int speed = C.leds.spi.speed 92 | } 93 | 94 | ``` 95 | 96 | > Note that all variables are type-safe. You are not accessing to a generic wrapper and casting its type. 97 | 98 | The final step is to setup the Bleeper singleton instance with your `RootConfiguration` instance and specify your connections, interfaces, storage and observers. 99 | 100 | ```cpp 101 | #include "Bleeper.h" 102 | 103 | class CalibrationObserver: public ConfigurationObserver { 104 | public: 105 | void onConfigurationChanged(const ConfigurationPropertyChange value) { 106 | Serial.println("Configuration " + value.key + " changed from " + value.oldValue + " to " + value.newValue); 107 | } 108 | }; 109 | 110 | Config C; 111 | 112 | void setup() { 113 | 114 | Bleeper 115 | .verbose(115200) 116 | .configuration 117 | .set(&C) 118 | .addObserver(new CalibrationObserver(), {&C.leds.calibration}) 119 | .done() 120 | .configurationInterface 121 | .addDefaultWebServer() 122 | .done() 123 | .connection 124 | .setSingleConnectionFromPriorityList({ 125 | new Wifi(&C.wifi.ssid, &C.wifi.password), 126 | new AP() // fallback 127 | }) 128 | .done() 129 | .storage 130 | .setDefault() // EEPROM 131 | // .set(new SPIFFSStorage()) // SPIFFS 132 | .done() 133 | .init(); 134 | } 135 | 136 | void loop() { 137 | 138 | Bleeper.handle(); 139 | 140 | } 141 | ``` 142 | 143 | Basically Bleeper exposes four entry points: 144 | 145 | 1. **Bleeper.configuration** 146 | 147 | Lets you set your `RootConfiguration` instance and add observers to changes on it. In this example we are setting the `CalibrationObserver` instance that will be only notified about changes on the `C.leds.calibration` property. 148 | 149 | 2. **Bleeper.configurationInterface** 150 | 151 | Here you can add as many `ConfigurationInterface` instances as you want. Bleeper provides a default web panel when calling `addDefaultWebServer`. 152 | 153 | 2. **Bleeper.connection** 154 | 155 | Under connection we can call `setMultipleConnections` or `setSingleConnectionFromPriorityList` (in which only one connection will be active) and provide a list of `Connection` instances. By default the `Wifi` class will observe changes on the provided credentials and retry the connection accordingly. 156 | 157 | 2. **Bleeper.storage** 158 | 159 | Lets you specify the `Storage` instance to use when saving your persistent variables. Calling `setDefault` will use the default EEPROM storage automatically. You can also use `SPIFFSStorage` or create your own instead. 160 | 161 | ## Installation 162 | 163 | ### Arduino IDE 164 | 165 | Go to Sketch > Include Library > Manage Libraries... 166 | Search for `Bleeper` and click on `Install` 167 | 168 | ### PlatformIO IDE 169 | 170 | Go to your `platformio.ini` file and add the following lines: 171 | ``` 172 | lib_deps = Bleeper 173 | lib_ldf_mode = deep 174 | ``` 175 | 176 | ## Future Work 177 | 178 | - Add support for other boards. 179 | - Improve documentation & examples. 180 | - Add CI server & tests. 181 | 182 | ## Author 183 | 184 | * [Diego Ernst](https://github.com/dernster) 185 | -------------------------------------------------------------------------------- /docs/VARIABLES.md: -------------------------------------------------------------------------------- 1 | # Variables 2 | 3 | Variables within a `Configuration` class or `RootConfiguration` class will be tracked by `Bleeper` only if they are declared using specific macros. 4 | 5 | ## Common types 6 | 7 | - `intVar`, `floatVar` & `stringVar` 8 | They all receive two parameters, the first one is the name of the variable and the second one its default value. Default values must be of types `int`, `float` and `String` respectively. 9 | 10 | - `persistentIntVar`, `persistentFloatVar` & `persistentStringVar` 11 | The same as before but they will be persisted on the configured `Bleeper` storage. 12 | 13 | ```Cpp 14 | class ExampleConfig: public Configuration { 15 | public: 16 | persistentStringVar(boardName, "Board 1"); 17 | floatVar(threshold, 0.7); 18 | }; 19 | ``` 20 | 21 | ## Custom types 22 | 23 | `Bleeper` allows you to store any type of variable. You only need to be able to serialize & deserialize it to & from a string. 24 | 25 | - `var(type, name, default, setBody, getBody)` 26 | - `type`: your custom type. 27 | - `name`: name of your variable. 28 | - `default`: default value. 29 | - `setBody`: scope enclosed by curly brackets in which you will have exposed a variable called `name` (your actual variable) and a constant called `nameString` which you will have to make your conversion from. 30 | - `getBody`: similar to `setBody` but you need to write the variable called `nameString` from your variable `name`. 31 | 32 | 33 | - `persistentVar(type, name, default, setBody, getBody)` 34 | The same as before but persistent. 35 | 36 | ```Cpp 37 | #include "Bleeper.h" 38 | 39 | class Point { 40 | public: 41 | int x, y; 42 | 43 | Point(int x, int y) { 44 | this->x = x; 45 | this->y = y; 46 | } 47 | 48 | Point(String string) { 49 | auto strings = splitString(string, ","); 50 | x = strings[0].toInt(); 51 | y = strings[1].toInt(); 52 | } 53 | 54 | String toString() { 55 | return String(x) + "," + String(y) 56 | } 57 | } 58 | 59 | class ExampleConfig: public Configuration { 60 | public: 61 | var(Point, point, Point(0, 0), 62 | { 63 | point = Point(pointString); 64 | }, 65 | { 66 | pointString = point.toString(); 67 | }); 68 | }; 69 | ``` 70 | -------------------------------------------------------------------------------- /examples/Simple/Simple.ino: -------------------------------------------------------------------------------- 1 | #include "Bleeper.h" 2 | 3 | class Network: public Configuration { 4 | public: 5 | stringVar(ip, "localhost"); 6 | intVar(port, 506); 7 | }; 8 | 9 | class WifiConfig: public Configuration { 10 | public: 11 | persistentStringVar(ssid, "ssid"); 12 | persistentStringVar(password, "pass"); 13 | subconfig(Network, network); 14 | }; 15 | 16 | class Config: public RootConfiguration { 17 | public: 18 | floatVar(floatValue, 0); 19 | 20 | var(unsigned int, time, 25, 21 | { 22 | time = timeString.toInt(); 23 | }, 24 | { 25 | timeString = String(time); 26 | }); 27 | 28 | subconfig(WifiConfig, wifi); 29 | } C; 30 | 31 | class MyObserver: public ConfigurationObserver { 32 | public: 33 | void onConfigurationChanged(const ConfigurationPropertyChange value) { 34 | Serial.println("Configuration " + value.key + " changed from " + value.oldValue + " to " + value.newValue); 35 | } 36 | }; 37 | 38 | void setup() { 39 | 40 | Bleeper 41 | .verbose(115200) 42 | .configuration 43 | .set(&C) 44 | .addObserver(new MyObserver(), {&C.wifi.network.port}) 45 | .done() 46 | .configurationInterface 47 | .addDefaultWebServer() 48 | .done() 49 | .connection 50 | .setSingleConnectionFromPriorityList({ 51 | new Wifi(&C.wifi.ssid, &C.wifi.password), 52 | new AP() 53 | }) 54 | .done() 55 | .storage 56 | .setDefault() // EEPROM 57 | // .set(new SPIFFSStorage()) // SPIFFS 58 | .done() 59 | .init(); 60 | } 61 | 62 | void loop() { 63 | 64 | Bleeper.handle(); 65 | 66 | } 67 | -------------------------------------------------------------------------------- /img/Bleeper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workilabs/Bleeper/81992601756e2a9f4b7416ef4783f0c211536fcb/img/Bleeper.png -------------------------------------------------------------------------------- /img/Bleeper.svg: -------------------------------------------------------------------------------- 1 | BleeperAsset 3Bleeper -------------------------------------------------------------------------------- /img/diagram.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workilabs/Bleeper/81992601756e2a9f4b7416ef4783f0c211536fcb/img/diagram.ai -------------------------------------------------------------------------------- /img/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workilabs/Bleeper/81992601756e2a9f4b7416ef4783f0c211536fcb/img/diagram.png -------------------------------------------------------------------------------- /img/smartphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workilabs/Bleeper/81992601756e2a9f4b7416ef4783f0c211536fcb/img/smartphone.png -------------------------------------------------------------------------------- /img/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workilabs/Bleeper/81992601756e2a9f4b7416ef4783f0c211536fcb/img/web.png -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ############################# 2 | # Automatically generated 3 | ############################# 4 | 5 | BleeperClass::ConfigurationInterfaceFunctions KEYWORD1 6 | add KEYWORD2 7 | addDefaultWebServer KEYWORD2 8 | BleeperClass::StorageFunctions KEYWORD1 9 | persist KEYWORD2 10 | set KEYWORD2 11 | setDefault KEYWORD2 12 | BleeperClass::UserProperties KEYWORD1 13 | BleeperClass::ConfigurationFunctions KEYWORD1 14 | addObserver KEYWORD2 15 | enableObservers KEYWORD2 16 | get KEYWORD2 17 | getAsDictionary KEYWORD2 18 | getVariables KEYWORD2 19 | set KEYWORD2 20 | setFromDictionary KEYWORD2 21 | BleeperClass::Chainable KEYWORD1 22 | done KEYWORD2 23 | BleeperClass KEYWORD1 24 | handle KEYWORD2 25 | init KEYWORD2 26 | shared KEYWORD2 27 | verbose KEYWORD2 28 | BleeperClass::ConnectionFunctions KEYWORD1 29 | set KEYWORD2 30 | setMultipleConnections KEYWORD2 31 | setSingleConnectionFromPriorityList KEYWORD2 32 | Initable KEYWORD1 33 | init KEYWORD2 34 | BaseConfiguration KEYWORD1 35 | addTo KEYWORD2 36 | fillConfigMap KEYWORD2 37 | getPrefix KEYWORD2 38 | withPrefix KEYWORD2 39 | ConfigMap KEYWORD1 40 | add KEYWORD2 41 | Configuration KEYWORD1 42 | ConfigurationDictionary KEYWORD1 43 | append KEYWORD2 44 | toStrings KEYWORD2 45 | RootConfiguration KEYWORD1 46 | getAsDictionary KEYWORD2 47 | getVariableForAddress KEYWORD2 48 | getVariables KEYWORD2 49 | init KEYWORD2 50 | isAddressValid KEYWORD2 51 | setFromDictionary KEYWORD2 52 | StringConvertibleVariable KEYWORD1 53 | getAsString KEYWORD2 54 | getFullKey KEYWORD2 55 | getKey KEYWORD2 56 | getMemoryAddress KEYWORD2 57 | isPersistentVariable KEYWORD2 58 | setFromString KEYWORD2 59 | setFullKey KEYWORD2 60 | Variable KEYWORD1 61 | addTo KEYWORD2 62 | getAsString KEYWORD2 63 | isPersistentVariable KEYWORD2 64 | setFromString KEYWORD2 65 | IntVariable KEYWORD1 66 | getAsString KEYWORD2 67 | setFromString KEYWORD2 68 | GenericVariable KEYWORD1 69 | SetterOfType KEYWORD2 70 | getAsString KEYWORD2 71 | setFromString KEYWORD2 72 | StringVariable KEYWORD1 73 | getAsString KEYWORD2 74 | setFromString KEYWORD2 75 | FloatVariable KEYWORD1 76 | getAsString KEYWORD2 77 | setFromString KEYWORD2 78 | ConfigurationInterface KEYWORD1 79 | handle KEYWORD2 80 | init KEYWORD2 81 | AP KEYWORD1 82 | connect KEYWORD2 83 | disconnect KEYWORD2 84 | handle KEYWORD2 85 | init KEYWORD2 86 | Connections KEYWORD1 87 | add KEYWORD2 88 | handle KEYWORD2 89 | init KEYWORD2 90 | OneOfMultipleConnection KEYWORD1 91 | connect KEYWORD2 92 | disconnect KEYWORD2 93 | disconnectFromAllExepct KEYWORD2 94 | handle KEYWORD2 95 | retry KEYWORD2 96 | Connection KEYWORD1 97 | connect KEYWORD2 98 | connectionDidComplete KEYWORD2 99 | connectionDidFail KEYWORD2 100 | disconnect KEYWORD2 101 | handle KEYWORD2 102 | init KEYWORD2 103 | retry KEYWORD2 104 | setIsExclusiveConnection KEYWORD2 105 | wantsToRetryConnection KEYWORD2 106 | MultipleConnection KEYWORD1 107 | connect KEYWORD2 108 | disconnect KEYWORD2 109 | handle KEYWORD2 110 | Wifi KEYWORD1 111 | connect KEYWORD2 112 | disconnect KEYWORD2 113 | handle KEYWORD2 114 | init KEYWORD2 115 | onConfigurationChanged KEYWORD2 116 | retry KEYWORD2 117 | wantsToRetryConnection KEYWORD2 118 | Logger KEYWORD1 119 | print KEYWORD2 120 | ConfigurationObserver KEYWORD1 121 | isInterestedIn KEYWORD2 122 | onConfigurationChanged KEYWORD2 123 | setFilter KEYWORD2 124 | ConfigurationPropertyChange KEYWORD1 125 | Observable KEYWORD1 126 | addObserver KEYWORD2 127 | EEPROMHelper KEYWORD1 128 | readStrings KEYWORD2 129 | writeStrings KEYWORD2 130 | Storage KEYWORD1 131 | load KEYWORD2 132 | persist KEYWORD2 133 | VariablesMapStorage KEYWORD1 134 | init KEYWORD2 135 | load KEYWORD2 136 | persist KEYWORD2 137 | DefaultWebServer KEYWORD1 138 | handle KEYWORD2 139 | init KEYWORD2 140 | WebServer KEYWORD1 141 | handle KEYWORD2 142 | init KEYWORD2 143 | 144 | ############################# 145 | # Manual settings 146 | ############################# 147 | 148 | Bleeper KEYWORD1 149 | var KEYWORD2 150 | floatVar KEYWORD2 151 | stringVar KEYWORD2 152 | intVar KEYWORD2 153 | persistentVar KEYWORD2 154 | persistentFloatVar KEYWORD2 155 | persistentStringVar KEYWORD2 156 | persistentIntVar KEYWORD2 157 | subconfig KEYWORD2 158 | -------------------------------------------------------------------------------- /lib/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for the project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link to executable file. 4 | 5 | The source code of each library should be placed in separate directory, like 6 | "lib/private_lib/[here are source files]". 7 | 8 | For example, see how can be organized `Foo` and `Bar` libraries: 9 | 10 | |--lib 11 | | |--Bar 12 | | | |--docs 13 | | | |--examples 14 | | | |--src 15 | | | |- Bar.c 16 | | | |- Bar.h 17 | | |--Foo 18 | | | |- Foo.c 19 | | | |- Foo.h 20 | | |- readme.txt --> THIS FILE 21 | |- platformio.ini 22 | |--src 23 | |- main.c 24 | 25 | Then in `src/main.c` you should use: 26 | 27 | #include 28 | #include 29 | 30 | // rest H/C/CPP code 31 | 32 | PlatformIO will find your libraries automatically, configure preprocessor's 33 | include paths and build them. 34 | 35 | More information about PlatformIO Library Dependency Finder 36 | - http://docs.platformio.org/page/librarymanager/ldf.html 37 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bleeper", 3 | "keywords": "Configuration, Storage, Web Panel, Connection, WiFi", 4 | "description": "A library to store generic configurations", 5 | "repository": 6 | { 7 | "type": "git", 8 | "url": "https://github.com/neman-io/Bleeper.git" 9 | }, 10 | "frameworks": "arduino", 11 | "platforms": ["espressif8266", "espressif32"], 12 | "version": "1.1.0" 13 | } 14 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Bleeper 2 | version=1.1.0 3 | author=Diego Ernst 4 | maintainer=Diego Ernst 5 | sentence=A library to store generic configurations. 6 | paragraph=Easily define your configuration hierarchy, the type of each property and weather or not it should be persisted. 7 | category=Other 8 | url=https://github.com/neman-io/Bleeper.git 9 | architectures=esp8266,esp32 10 | includes=Bleeper.h 11 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | # 2 | # Project Configuration File 3 | # 4 | # A detailed documentation with the EXAMPLES is located here: 5 | # http://docs.platformio.org/en/latest/projectconf.html 6 | # 7 | 8 | # A sign `#` at the beginning of the line indicates a comment 9 | # Comment lines are ignored. 10 | 11 | # Simple and base environment 12 | # [env:mybaseenv] 13 | # platform = %INSTALLED_PLATFORM_NAME_HERE% 14 | # framework = 15 | # board = 16 | # 17 | # Automatic targets - enable auto-uploading 18 | # targets = upload 19 | 20 | [env:esp8266dev] 21 | platform = espressif 22 | framework = arduino 23 | board = nodemcuv2 24 | 25 | [env:esp32dev] 26 | platform = espressif32 27 | framework = arduino 28 | board = esp32dev 29 | -------------------------------------------------------------------------------- /src/Bleeper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Helpers/utils.h" 4 | #include "Bleeper/BleeperClass.h" 5 | #include "Connectivity/Wifi.h" 6 | #include "Connectivity/AP.h" 7 | #include "Observers/ConfigurationObserver.h" 8 | #include "Configuration/ConfigurationMacros.h" 9 | #include "Configuration/Configuration.h" 10 | #include "Configuration/RootConfiguration.h" 11 | #include "Storage/SPIFFSStorage.h" 12 | -------------------------------------------------------------------------------- /src/Bleeper/Bleeper+Configuration.cpp: -------------------------------------------------------------------------------- 1 | #include "Bleeper/BleeperClass.h" 2 | #include "Observers/ConfigurationObserver.h" 3 | #include "Helpers/macros.h" 4 | 5 | #define FunctionsContainer BleeperClass::ConfigurationFunctions 6 | 7 | FunctionsContainer& FunctionsContainer::addObserver(ConfigurationObserver* observer, std::set filter) { 8 | guard(Bleeper.userProperties.rootConfig != NULL, return *this); 9 | observer->setFilter(filter); 10 | Bleeper.userProperties.rootConfig->addObserver(observer); 11 | return *this; 12 | } 13 | 14 | FunctionsContainer& FunctionsContainer::set(RootConfiguration* config) { 15 | Bleeper.userProperties.rootConfig = config; 16 | return *this; 17 | } 18 | 19 | void FunctionsContainer::setFromDictionary(const ConfigurationDictionary& dict) { 20 | Bleeper.userProperties.rootConfig->setFromDictionary(dict); 21 | } 22 | 23 | ConfigurationDictionary FunctionsContainer::getAsDictionary(bool onlyPersistent) { 24 | guard(Bleeper.userProperties.rootConfig, return ConfigurationDictionary()); 25 | return Bleeper.userProperties.rootConfig->getAsDictionary(onlyPersistent); 26 | } 27 | 28 | RootConfiguration* FunctionsContainer::get() { 29 | return Bleeper.userProperties.rootConfig; 30 | } 31 | 32 | void FunctionsContainer::enableObservers(bool enable) { 33 | ConfigurationObserver::isEnabled = enable; 34 | } 35 | 36 | const std::vector& FunctionsContainer::getVariables() { 37 | return Bleeper.userProperties.rootConfig->getVariables(); 38 | } 39 | -------------------------------------------------------------------------------- /src/Bleeper/Bleeper+ConfigurationInterface.cpp: -------------------------------------------------------------------------------- 1 | #ifdef ESP8266 2 | #include "ConfigurationInterface/WebServer/ESP8266/ESP8266DefaultWebServer.h" 3 | #elif ESP32 4 | #include "ConfigurationInterface/WebServer/ESP32/ESP32DefaultWebServer.h" 5 | #endif 6 | 7 | #include "Bleeper/BleeperClass.h" 8 | #include "Helpers/macros.h" 9 | 10 | #define FunctionsContainer BleeperClass::ConfigurationInterfaceFunctions 11 | 12 | FunctionsContainer& FunctionsContainer::add(ConfigurationInterface* i) { 13 | Bleeper.userProperties.interfaces.push_back(i); 14 | return *this; 15 | } 16 | 17 | FunctionsContainer& FunctionsContainer::addDefaultWebServer() { 18 | #ifdef ESP8266 19 | Bleeper.userProperties.interfaces.push_back(new ESP8266DefaultWebServer(80)); 20 | #elif ESP32 21 | Bleeper.userProperties.interfaces.push_back(new ESP32DefaultWebServer(80)); 22 | #endif 23 | return *this; 24 | } 25 | -------------------------------------------------------------------------------- /src/Bleeper/Bleeper+Connection.cpp: -------------------------------------------------------------------------------- 1 | #include "Bleeper/BleeperClass.h" 2 | #include "Helpers/macros.h" 3 | 4 | #define FunctionsContainer BleeperClass::ConnectionFunctions 5 | 6 | FunctionsContainer& FunctionsContainer::set(Connection* c) { 7 | Bleeper.userProperties.connection = c; 8 | return *this; 9 | } 10 | 11 | FunctionsContainer& FunctionsContainer::setMultipleConnections(std::vector connections) { 12 | Bleeper.userProperties.connection = new MultipleConnection(connections); 13 | return *this; 14 | } 15 | 16 | FunctionsContainer& FunctionsContainer::setSingleConnectionFromPriorityList(std::vector connections) { 17 | Bleeper.userProperties.connection = new OneOfMultipleConnection(connections); 18 | return *this; 19 | } 20 | 21 | Connection* FunctionsContainer::get() { 22 | return Bleeper.userProperties.connection; 23 | } 24 | -------------------------------------------------------------------------------- /src/Bleeper/Bleeper+Storage.cpp: -------------------------------------------------------------------------------- 1 | #include "Bleeper/BleeperClass.h" 2 | #include "Storage/VariablesMapStorage.h" 3 | #include "Helpers/macros.h" 4 | 5 | #define FunctionsContainer BleeperClass::StorageFunctions 6 | 7 | FunctionsContainer& FunctionsContainer::set(Storage* s) { 8 | Bleeper.userProperties.storage = s; 9 | return *this; 10 | } 11 | 12 | FunctionsContainer& FunctionsContainer::setDefault() { 13 | Bleeper.userProperties.storage = new VariablesMapStorage(); 14 | return *this; 15 | } 16 | 17 | FunctionsContainer& FunctionsContainer::persist() { 18 | guard(Bleeper.userProperties.storage, return *this); 19 | Bleeper.userProperties.storage->persist(); 20 | return *this; 21 | } 22 | -------------------------------------------------------------------------------- /src/Bleeper/BleeperClass.cpp: -------------------------------------------------------------------------------- 1 | #include "Bleeper/BleeperClass.h" 2 | #include "Helpers/Logger.h" 3 | #include "Helpers/macros.h" 4 | 5 | #ifdef ESP8266 6 | #include 7 | #elif ESP32 8 | #include 9 | #endif 10 | 11 | BleeperClass* BleeperClass::sharedInstance = NULL; 12 | 13 | BleeperClass::BleeperClass() { 14 | userProperties.storage = NULL; 15 | userProperties.rootConfig = NULL; 16 | userProperties.connection = NULL; 17 | } 18 | 19 | BleeperClass* BleeperClass::shared() { 20 | if (!sharedInstance) 21 | sharedInstance = new BleeperClass(); 22 | return sharedInstance; 23 | } 24 | 25 | void BleeperClass::handle() { 26 | for(auto interface: userProperties.interfaces) { 27 | interface->handle(); 28 | } 29 | 30 | if (userProperties.connection) { 31 | userProperties.connection->handle(); 32 | } 33 | 34 | } 35 | 36 | BleeperClass& BleeperClass::verbose() { 37 | Logger::verbose = true; 38 | return *this; 39 | } 40 | 41 | BleeperClass& BleeperClass::verbose(int baudRate) { 42 | Logger::verbose = true; 43 | Logger::baudRate = baudRate; 44 | return *this; 45 | } 46 | 47 | void BleeperClass::init() { 48 | init(true); 49 | } 50 | 51 | void BleeperClass::init(bool loadFromStorage) { 52 | guard(userProperties.rootConfig, return); 53 | 54 | configuration.enableObservers(false); 55 | 56 | userProperties.rootConfig->init(); 57 | 58 | if (userProperties.storage) { 59 | userProperties.storage->init(); 60 | if (loadFromStorage) 61 | userProperties.storage->load(); 62 | } 63 | 64 | for(auto interface: userProperties.interfaces) { 65 | interface->init(); 66 | } 67 | 68 | if (userProperties.connection) { 69 | WiFi.mode(WIFI_OFF); 70 | userProperties.connection->init(); 71 | userProperties.connection->connect(); 72 | } 73 | configuration.enableObservers(true); 74 | } 75 | -------------------------------------------------------------------------------- /src/Bleeper/BleeperClass.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Storage/Storage.h" 3 | #include "Configuration/RootConfiguration.h" 4 | #include "Connectivity/Connection.h" 5 | #include "Configuration/ConfigurationDictionary.h" 6 | #include "ConfigurationInterface/ConfigurationInterface.h" 7 | #include "Bleeper/Initable.h" 8 | 9 | #define Bleeper (*BleeperClass::shared()) 10 | 11 | class BleeperClass: public Initable { 12 | private: 13 | static BleeperClass* sharedInstance; 14 | BleeperClass(); 15 | 16 | struct UserProperties { 17 | Storage* storage; 18 | RootConfiguration* rootConfig; 19 | std::vector interfaces; 20 | Connection* connection; 21 | } userProperties; 22 | 23 | public: 24 | static BleeperClass* shared(); 25 | void handle(); 26 | void init(); 27 | void init(bool loadFromStorage); 28 | BleeperClass& verbose(); 29 | BleeperClass& verbose(int baudRate); 30 | 31 | class Chainable { 32 | public: 33 | BleeperClass& done() { 34 | return Bleeper; 35 | } 36 | }; 37 | 38 | class ConfigurationFunctions: public Chainable { 39 | public: 40 | ConfigurationFunctions& set(RootConfiguration*); 41 | ConfigurationFunctions& addObserver(ConfigurationObserver* observer, std::set filter = std::set()); 42 | RootConfiguration* get(); 43 | ConfigurationDictionary getAsDictionary(bool onlyPersistent = false); 44 | const std::vector& getVariables(); 45 | void enableObservers(bool); 46 | void setFromDictionary(const ConfigurationDictionary&); 47 | } configuration; 48 | 49 | class ConnectionFunctions: public Chainable { 50 | public: 51 | ConnectionFunctions& set(Connection*); 52 | Connection* get(); 53 | ConnectionFunctions& setMultipleConnections(std::vector connections); 54 | ConnectionFunctions& setSingleConnectionFromPriorityList(std::vector connections); 55 | } connection; 56 | 57 | class StorageFunctions: public Chainable { 58 | public: 59 | StorageFunctions& set(Storage*); 60 | StorageFunctions& setDefault(); 61 | StorageFunctions& persist(); 62 | } storage; 63 | 64 | class ConfigurationInterfaceFunctions: public Chainable { 65 | public: 66 | ConfigurationInterfaceFunctions& add(ConfigurationInterface*); 67 | ConfigurationInterfaceFunctions& addDefaultWebServer(); 68 | } configurationInterface; 69 | 70 | }; 71 | -------------------------------------------------------------------------------- /src/Bleeper/Initable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class Initable { 4 | public: 5 | virtual void init() = 0; 6 | }; 7 | -------------------------------------------------------------------------------- /src/Configuration/BaseConfiguration.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Configuration/ConfigurationDictionary.h" 3 | #include "Variable.h" 4 | #include "StringConvertibleVariable.h" 5 | #include "Configuration/ConfigMap.h" 6 | 7 | class BaseConfiguration { 8 | protected: 9 | String prefix; 10 | std::vector vars; 11 | std::vector subConfigs; 12 | 13 | template void add(Variable& var) { 14 | vars.push_back(&var); 15 | } 16 | 17 | void fillConfigMap(String cumulativePrefix, ConfigMap* map) { 18 | for(StringConvertibleVariable* variable: vars) { 19 | auto prefix = cumulativePrefix; 20 | auto varFullKey = (prefix != "") ? prefix + "." + variable->getKey() : variable->getKey(); 21 | variable->setFullKey(varFullKey); 22 | map->add(variable->getMemoryAddress(), variable); 23 | } 24 | for(BaseConfiguration* subConfig: subConfigs) { 25 | auto cumulative = (cumulativePrefix != "") ? cumulativePrefix + "." + subConfig->prefix: subConfig->prefix; 26 | subConfig->fillConfigMap(cumulative, map); 27 | } 28 | } 29 | 30 | public: 31 | BaseConfiguration() {} 32 | 33 | String getPrefix() { 34 | return prefix; 35 | } 36 | 37 | void fillConfigMap(ConfigMap* map) { 38 | fillConfigMap("", map); 39 | } 40 | 41 | template 42 | T* addTo(std::vector& subConfigs) { 43 | subConfigs.push_back(this); 44 | return (T*)this; 45 | } 46 | 47 | template 48 | T* withPrefix(String prefix) { 49 | this->prefix = prefix; 50 | return (T*)this; 51 | } 52 | 53 | }; 54 | -------------------------------------------------------------------------------- /src/Configuration/ConfigMap.cpp: -------------------------------------------------------------------------------- 1 | #include "ConfigMap.h" 2 | 3 | ConfigMap::ConfigMap() {} 4 | 5 | void ConfigMap::add(VariableAddress address, StringConvertibleVariable* variable) { 6 | addressMap[address] = variable; 7 | keyMap[variable->getFullKey()] = variable; 8 | variables.push_back(variable); 9 | } 10 | -------------------------------------------------------------------------------- /src/Configuration/ConfigMap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "StringConvertibleVariable.h" 3 | #include "Configuration/ConfigurationDictionary.h" 4 | #include 5 | #include 6 | 7 | typedef const void* VariableAddress; 8 | 9 | class ConfigMap { 10 | protected: 11 | std::map addressMap; 12 | std::map keyMap; 13 | std::vector variables; 14 | public: 15 | ConfigMap(); 16 | void add(VariableAddress address, StringConvertibleVariable* variable); 17 | }; 18 | -------------------------------------------------------------------------------- /src/Configuration/Configuration.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "BaseConfiguration.h" 3 | 4 | class Configuration: public BaseConfiguration {}; 5 | -------------------------------------------------------------------------------- /src/Configuration/ConfigurationDictionary.cpp: -------------------------------------------------------------------------------- 1 | #include "Configuration/ConfigurationDictionary.h" 2 | #include "Helpers/utils.h" 3 | #include "Helpers/macros.h" 4 | 5 | ConfigurationDictionary::ConfigurationDictionary() {}; 6 | 7 | ConfigurationDictionary::ConfigurationDictionary(const std::vector & strings) { 8 | guard(strings.size() > 0, return); 9 | for(auto p = 0; p < strings.size() - 1; p += 2) { 10 | (*this)[strings[p]] = strings[p + 1]; 11 | } 12 | } 13 | 14 | void ConfigurationDictionary::append(ConfigurationDictionary dict) { 15 | insert(dict.begin(), dict.end()); 16 | } 17 | 18 | std::vector ConfigurationDictionary::toStrings() { 19 | std::vector result; 20 | for(auto const& pair: *this) { 21 | result.push_back(pair.first); 22 | result.push_back(pair.second); 23 | } 24 | return result; 25 | } 26 | -------------------------------------------------------------------------------- /src/Configuration/ConfigurationDictionary.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | class ConfigurationDictionary: public std::map { 7 | public: 8 | ConfigurationDictionary(); 9 | ConfigurationDictionary(const std::vector & strings); 10 | void append(ConfigurationDictionary); 11 | std::vector toStrings(); 12 | }; 13 | -------------------------------------------------------------------------------- /src/Configuration/ConfigurationMacros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define UNIQUE_NAME(x) zbleeper ## _ ## x 4 | 5 | /******************* MOST COMMON VARS ******************/ 6 | 7 | #define floatVar(name, initial) __floatVar__(name, initial, false) 8 | #define stringVar(name, initial) __stringVar__(name, initial, false) 9 | #define intVar(name, initial) __intVar__(name, initial, false) 10 | 11 | #define persistentFloatVar(name, initial) __floatVar__(name, initial, true) 12 | #define persistentStringVar(name, initial) __stringVar__(name, initial, true) 13 | #define persistentIntVar(name, initial) __intVar__(name, initial, true) 14 | 15 | #define var(type, name, initial, setBody, getBody) __var__(type, name, initial, false, setBody, getBody) 16 | #define persistentVar(type, name, initial, setBody, getBody) __var__(type, name, initial, true, setBody, getBody) 17 | 18 | /******************* MOST COMMON VARS ******************/ 19 | 20 | #define __floatVar__(name, initial, isPersistent)\ 21 | float name = initial;\ 22 | FloatVariable* UNIQUE_NAME(var_ ## name) = (new FloatVariable(#name, &name, isPersistent))->addTo(vars); 23 | 24 | #define __stringVar__(name, initial, isPersistent)\ 25 | StringVariable* UNIQUE_NAME(var_ ## name) = (new StringVariable(#name, &name, isPersistent))->addTo(vars);\ 26 | String name = initial; 27 | 28 | #define __intVar__(name, initial, isPersistent)\ 29 | IntVariable* UNIQUE_NAME(var_ ## name) = (new IntVariable(#name, &name, isPersistent))->addTo(vars);\ 30 | int name = initial; 31 | 32 | #define subconfig(type, name)\ 33 | type * UNIQUE_NAME(name) = (new type())->withPrefix(#name)->addTo(subConfigs);\ 34 | type & name = *UNIQUE_NAME(name);\ 35 | 36 | /*************** GENERIC TYPE VARIABLES ****************/ 37 | 38 | #define __var__(type, name, initial, isPersistent, setBody, getBody)\ 39 | GenericVariable* UNIQUE_NAME(var_ ## name) = (new GenericVariable(#name, &name, isPersistent, &setterFuncName(name), &getterFuncName(name)))->addTo>(vars);\ 40 | type name = initial;\ 41 | setterFunc(type, name, setBody);\ 42 | getterFunc(type, name, getBody);\ 43 | 44 | #define setterFuncName(name) UNIQUE_NAME(set_ ## name) 45 | #define getterFuncName(name) UNIQUE_NAME(get_ ## name) 46 | 47 | #define setterFunc(type, name,body)\ 48 | static void setterFuncName(name)(type* UNIQUE_NAME(instance), const String& name ## String){\ 49 | auto & name = *UNIQUE_NAME(instance);\ 50 | body;\ 51 | } 52 | 53 | #define getterFunc(type, name, body)\ 54 | static String getterFuncName(name)(type* UNIQUE_NAME(instance)){\ 55 | const auto & name = *UNIQUE_NAME(instance);\ 56 | String name ## String = "STRING_CONVERTER_NOT_IMPLEMENTED";\ 57 | body;\ 58 | return name ## String;\ 59 | } 60 | -------------------------------------------------------------------------------- /src/Configuration/RootConfiguration.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "BaseConfiguration.h" 3 | #include "Observers/Observable.h" 4 | #include "Observers/ConfigurationObserver.h" 5 | #include "Bleeper/Initable.h" 6 | 7 | class RootConfiguration: 8 | public BaseConfiguration, 9 | public Observable, 10 | public ConfigMap, 11 | public Initable { 12 | protected: 13 | 14 | virtual void notifyObserversOfChange(VariableAddress address, const String & key, const String oldValue, const String newValue) { 15 | if (oldValue == newValue) 16 | return; 17 | for(ConfigurationObserver* o: observers) { 18 | if (o->isInterestedIn(address)) 19 | o->onConfigurationChanged({.key = key, .oldValue = oldValue, .newValue = newValue}); 20 | } 21 | } 22 | 23 | public: 24 | 25 | virtual void init() { 26 | fillConfigMap(this); 27 | } 28 | 29 | bool isAddressValid(VariableAddress address) { 30 | return addressMap.find(address) != addressMap.end(); 31 | } 32 | 33 | StringConvertibleVariable* getVariableForAddress(VariableAddress address) { 34 | return addressMap[address]; 35 | } 36 | 37 | ConfigurationDictionary getAsDictionary(bool onlyPersistent) { 38 | ConfigurationDictionary result; 39 | for(auto const &value : addressMap) { 40 | if (!onlyPersistent || value.second->isPersistentVariable()) { 41 | result[value.second->getFullKey()] = value.second->getAsString(); 42 | } 43 | } 44 | return result; 45 | } 46 | 47 | void setFromDictionary(const ConfigurationDictionary& dict) { 48 | for(auto const &pair : dict) { 49 | std::map::iterator it = keyMap.find(pair.first); 50 | if (it != keyMap.end()) { 51 | auto oldValue = it->second->getAsString(); 52 | it->second->setFromString(pair.second); 53 | notifyObserversOfChange(it->second->getMemoryAddress(), pair.first, oldValue, it->second->getAsString()); 54 | } 55 | } 56 | } 57 | 58 | const std::vector& getVariables() { 59 | return variables; 60 | } 61 | 62 | }; 63 | -------------------------------------------------------------------------------- /src/Configuration/StringConvertibleVariable.cpp: -------------------------------------------------------------------------------- 1 | #include "Configuration/StringConvertibleVariable.h" 2 | 3 | StringConvertibleVariable::StringConvertibleVariable(void* value, String key, bool isPersistent){ 4 | this->key = key; 5 | this->value = value; 6 | this->isPersistent = isPersistent; 7 | } 8 | 9 | bool StringConvertibleVariable::isPersistentVariable() { 10 | return isPersistent; 11 | } 12 | 13 | String StringConvertibleVariable::getKey() { 14 | return key; 15 | } 16 | 17 | void* StringConvertibleVariable::getMemoryAddress() { 18 | return value; 19 | } 20 | 21 | void StringConvertibleVariable::setFullKey(const String fullKey) { 22 | this->fullKey = fullKey; 23 | } 24 | 25 | String StringConvertibleVariable::getFullKey() { 26 | return fullKey; 27 | } 28 | -------------------------------------------------------------------------------- /src/Configuration/StringConvertibleVariable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Arduino.h" 3 | class BaseConfiguration; 4 | 5 | class StringConvertibleVariable { 6 | protected: 7 | String key; 8 | String fullKey; 9 | void* value; 10 | bool isPersistent; 11 | public: 12 | StringConvertibleVariable(void* value, String key, bool isPersistent); 13 | bool isPersistentVariable(); 14 | virtual String getAsString() = 0; 15 | virtual void setFromString(const String &value) = 0; 16 | String getKey(); 17 | void* getMemoryAddress(); 18 | void setFullKey(const String fullKey); 19 | String getFullKey(); 20 | 21 | }; 22 | -------------------------------------------------------------------------------- /src/Configuration/Variable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Helpers/utils.h" 3 | #include "StringConvertibleVariable.h" 4 | 5 | template 6 | class Variable: public StringConvertibleVariable { 7 | public: 8 | ValueT* value; 9 | 10 | Variable(String key, ValueT* value, bool isPersistent): StringConvertibleVariable(value, key, isPersistent) { 11 | this->value = value; 12 | } 13 | 14 | template 15 | T* addTo(std::vector& bag) { 16 | bag.push_back(this); 17 | return (T*) this; 18 | } 19 | 20 | virtual bool isPersistentVariable() { 21 | return false; 22 | } 23 | 24 | virtual String getAsString() = 0; 25 | virtual void setFromString(const String &value) = 0; 26 | 27 | }; 28 | 29 | class IntVariable: public Variable { 30 | public: 31 | IntVariable(String key, int* value, bool isPersistent): Variable(key, value, isPersistent) {} 32 | 33 | virtual String getAsString() { 34 | return String(*value); 35 | } 36 | 37 | virtual void setFromString(const String &stringValue) { 38 | *value = stringValue.toInt(); 39 | } 40 | 41 | }; 42 | 43 | class FloatVariable: public Variable { 44 | public: 45 | FloatVariable(String key, float* value, bool isPersistent): Variable(key, value, isPersistent) {} 46 | 47 | virtual String getAsString() { 48 | return String(*value); 49 | } 50 | 51 | virtual void setFromString(const String &stringValue) { 52 | *value = stringValue.toFloat(); 53 | } 54 | 55 | }; 56 | 57 | class StringVariable: public Variable { 58 | public: 59 | StringVariable(String key, String* value, bool isPersistent): Variable(key, value, isPersistent) {} 60 | 61 | virtual String getAsString() { 62 | return *value; 63 | } 64 | 65 | virtual void setFromString(const String &stringValue) { 66 | *value = stringValue; 67 | } 68 | 69 | }; 70 | 71 | #define SetterOfType(T) void (*SetterPtr)(T* intance, const String& value); 72 | #define GetterOfType(T) String (*GetterPtr)(T* intance); 73 | 74 | template 75 | class GenericVariable: public Variable { 76 | public: 77 | typedef SetterOfType(T); 78 | typedef GetterOfType(T); 79 | SetterPtr setter; 80 | GetterPtr getter; 81 | 82 | GenericVariable(String key, T* value, bool isPersistent, SetterPtr setter, GetterPtr getter): Variable(key, value, isPersistent) { 83 | this->setter = setter; 84 | this->getter = getter; 85 | } 86 | 87 | virtual String getAsString() { 88 | return getter(this->value); 89 | } 90 | 91 | virtual void setFromString(const String &stringValue) { 92 | setter(this->value, stringValue); 93 | } 94 | 95 | }; 96 | -------------------------------------------------------------------------------- /src/ConfigurationInterface/ConfigurationInterface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Bleeper/Initable.h" 4 | 5 | class ConfigurationInterface: public Initable { 6 | public: 7 | virtual void init() = 0; 8 | virtual void handle() = 0; 9 | }; 10 | -------------------------------------------------------------------------------- /src/ConfigurationInterface/WebServer/BleeperWebServer.cpp: -------------------------------------------------------------------------------- 1 | #include "ConfigurationInterface/WebServer/BleeperWebServer.h" 2 | #include "Configuration/ConfigurationDictionary.h" 3 | #include "Bleeper/BleeperClass.h" 4 | #include "Helpers/Logger.h" 5 | #include "ConfigurationInterface/WebServer/WebPage.h" 6 | 7 | BleeperWebServer::BleeperWebServer(int port) { 8 | this->port = port; 9 | } 10 | 11 | String BleeperWebServer::buildPage(const std::vector & vars) { 12 | Log("Page requested"); 13 | String first = "Bleeper

Bleeper

"; 14 | String middle = ""; 15 | for(auto const& v: vars) { 16 | yield(); 17 | String key = v->getFullKey(); 18 | String value = v->getAsString(); 19 | String classs = v->isPersistentVariable() ? "p" : ""; 20 | middle += "\n"; 21 | middle += "\n"; 22 | } 23 | yield(); 24 | String last = "
"; 25 | return first + middle + last; 26 | } 27 | 28 | void BleeperWebServer::saveValues(const std::map & args) { 29 | if (args.size() == 0) return; 30 | ConfigurationDictionary params; 31 | for(const auto & arg: args) { 32 | params[arg.first] = arg.second; 33 | } 34 | Bleeper.configuration.setFromDictionary(params); 35 | Bleeper.storage.persist(); 36 | Log("Configs saved!"); 37 | } 38 | 39 | const char* BleeperWebServer::getJS() { 40 | return JS; 41 | } 42 | 43 | const char* BleeperWebServer::getStyle() { 44 | return CSS; 45 | } 46 | -------------------------------------------------------------------------------- /src/ConfigurationInterface/WebServer/BleeperWebServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ConfigurationInterface/ConfigurationInterface.h" 4 | #include "Configuration/StringConvertibleVariable.h" 5 | #include 6 | #include 7 | 8 | #define HTTP_STATUS_OK 200 9 | #define HTTP_STATUS_SERVER_ERROR 500 10 | #define CONTENT_TYPE_HTML "text/html" 11 | #define CONTENT_TYPE_JS "application/javascript" 12 | #define CONTENT_TYPE_CSS "text/css" 13 | 14 | class BleeperWebServer : public ConfigurationInterface { 15 | protected: 16 | int port; 17 | public: 18 | BleeperWebServer(int port); 19 | virtual void init() = 0; 20 | virtual void handle() = 0; 21 | virtual String buildPage(const std::vector &); 22 | virtual const char* getJS(); 23 | virtual const char* getStyle(); 24 | virtual void saveValues(const std::map &); 25 | }; 26 | -------------------------------------------------------------------------------- /src/ConfigurationInterface/WebServer/ESP32/ESP32DefaultWebServer.cpp: -------------------------------------------------------------------------------- 1 | #ifdef ESP32 2 | 3 | #include "ConfigurationInterface/WebServer/ESP32/ESP32DefaultWebServer.h" 4 | #include "Bleeper/BleeperClass.h" 5 | #include "Helpers/Logger.h" 6 | #include 7 | 8 | ESP32DefaultWebServer::ESP32DefaultWebServer(int port): BleeperWebServer(port) { 9 | server = NULL; 10 | } 11 | 12 | void ESP32DefaultWebServer::init() { 13 | server = new WiFiServer(port); 14 | using std::placeholders::_1; 15 | using std::placeholders::_2; 16 | registerRoute( 17 | GET, 18 | "/", 19 | std::bind(&ESP32DefaultWebServer::handleRoot, this, _1, _2) 20 | ); 21 | registerRoute( 22 | GET, 23 | "/script", 24 | std::bind(&ESP32DefaultWebServer::handleScript, this, _1, _2) 25 | ); 26 | registerRoute( 27 | GET, 28 | "/style", 29 | std::bind(&ESP32DefaultWebServer::handleStyle, this, _1, _2) 30 | ); 31 | registerRoute( 32 | POST, 33 | "/save", 34 | std::bind(&ESP32DefaultWebServer::handleSave, this, _1, _2) 35 | ); 36 | } 37 | 38 | void ESP32DefaultWebServer::handle(){ 39 | // This server only starts working when 40 | // some connection is active. 41 | // It's safe to call it multiple times. 42 | server->begin(); 43 | 44 | WiFiClient client = server->available(); 45 | if (!client) return; 46 | Log("New client"); 47 | HTTPRequest request; 48 | while (client.connected()) { 49 | if (request.isFinished()) { 50 | handleRequest(request, client); 51 | break; 52 | } 53 | if (client.available()) { 54 | char c = client.read(); 55 | request.add(c); 56 | } 57 | } 58 | client.stop(); 59 | Log("Client disconnected"); 60 | } 61 | 62 | void ESP32DefaultWebServer::handleRequest(HTTPRequest & request, WiFiClient & client) { 63 | RouteHandler* handler = getRequestHandler(request); 64 | if (!handler) 65 | handleError(client); 66 | 67 | (*handler)(request, client); 68 | } 69 | 70 | void ESP32DefaultWebServer::handleRoot(HTTPRequest &, WiFiClient & client){ 71 | send(client, true, CONTENT_TYPE_HTML, buildPage(Bleeper.configuration.getVariables()).c_str()); 72 | } 73 | 74 | void ESP32DefaultWebServer::handleScript(HTTPRequest &, WiFiClient & client){ 75 | send(client, true, CONTENT_TYPE_JS, getJS()); 76 | } 77 | 78 | void ESP32DefaultWebServer::handleStyle(HTTPRequest &, WiFiClient & client){ 79 | send(client, true, CONTENT_TYPE_CSS, getStyle()); 80 | } 81 | 82 | void ESP32DefaultWebServer::handleSave(HTTPRequest & request, WiFiClient & client){ 83 | send(client, true, "", NULL); 84 | saveValues(request.getArgs()); 85 | } 86 | 87 | void ESP32DefaultWebServer::handleError(WiFiClient & client) { 88 | send(client, false, "", NULL); 89 | } 90 | 91 | void ESP32DefaultWebServer::send(WiFiClient & client, bool ok, String contentType, const char* data) { 92 | client.println(ok ? "HTTP/1.1 200 OK" : "HTTP/1.1 500 Internal Server Error"); 93 | if (contentType != "") client.println("Content-type:" + contentType); 94 | client.println(); 95 | if (data != NULL) client.print(data); 96 | client.println(); 97 | } 98 | 99 | void ESP32DefaultWebServer::registerRoute(HTTPMethod method, String route, RouteHandler handler) { 100 | router[String(method) + route] = handler; 101 | } 102 | 103 | RouteHandler* ESP32DefaultWebServer::getRequestHandler(HTTPRequest & request) { 104 | auto key = request.getMethod() + request.getRoute(); 105 | if (router.find(key) == router.end()) 106 | return NULL; 107 | return &router[key]; 108 | } 109 | 110 | #endif // ESP32 111 | -------------------------------------------------------------------------------- /src/ConfigurationInterface/WebServer/ESP32/ESP32DefaultWebServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef ESP32 4 | 5 | #include "Arduino.h" 6 | #include 7 | #include "ConfigurationInterface/WebServer/BleeperWebServer.h" 8 | #include "ConfigurationInterface/WebServer/ESP32/HTTPRequest.h" 9 | #include 10 | #include 11 | 12 | typedef std::function RouteHandler; 13 | 14 | class ESP32DefaultWebServer: public BleeperWebServer { 15 | protected: 16 | WiFiServer* server; 17 | std::map router; 18 | void handleRequest(HTTPRequest &, WiFiClient &); 19 | void handleRoot(HTTPRequest &, WiFiClient &); 20 | void handleScript(HTTPRequest &, WiFiClient &); 21 | void handleStyle(HTTPRequest &, WiFiClient &); 22 | void handleSave(HTTPRequest &, WiFiClient &); 23 | void handleError(WiFiClient &); 24 | void send(WiFiClient & client, bool ok, String contentType, const char* data); 25 | void registerRoute(HTTPMethod method, String route, RouteHandler handler); 26 | RouteHandler* getRequestHandler(HTTPRequest &); 27 | public: 28 | ESP32DefaultWebServer(int port); 29 | virtual void init(); 30 | virtual void handle(); 31 | }; 32 | 33 | #endif // ESP32 34 | -------------------------------------------------------------------------------- /src/ConfigurationInterface/WebServer/ESP32/HTTPRequest.cpp: -------------------------------------------------------------------------------- 1 | #include "ConfigurationInterface/WebServer/ESP32/HTTPRequest.h" 2 | #include "Helpers/Logger.h" 3 | #include "Helpers/utils.h" 4 | 5 | void HTTPRequest::add(const char c) { 6 | if (readingData) { 7 | if ((c == '\n') || (c == '\r')) return; 8 | data += c; 9 | dataLength--; 10 | if (!dataLength) { 11 | auto pairs = splitString(data, '&'); 12 | for(auto p: pairs) { 13 | auto split = splitString(p, '='); 14 | if (split.size() == 2) { 15 | args[split[0]] = split[1]; 16 | } 17 | } 18 | _isFinished = true; 19 | return; 20 | } 21 | return; 22 | } 23 | 24 | if (c == '\n') { 25 | if ((line.length() == 0) && (!hasData())) { 26 | _isFinished = true; 27 | } else if (hasData()) { 28 | readingData = true; 29 | } else { 30 | if (firstLine) { 31 | if (line.startsWith("GET")) { 32 | method = GET; 33 | } else if (line.startsWith("POST")) { 34 | method = POST; 35 | } else { 36 | method = UNKNOWN; 37 | } 38 | firstLine = false; 39 | auto strings = splitString(line, ' '); 40 | if (strings.size() > 1) 41 | route = strings[1]; 42 | } 43 | if (line.startsWith("Content-Length:")) { 44 | dataLength = line.substring(15).toInt(); 45 | } 46 | line = ""; 47 | } 48 | } else if (c != '\r') { 49 | line += c; 50 | } 51 | } 52 | 53 | bool HTTPRequest::hasData() { 54 | return (dataLength > 0); 55 | } 56 | 57 | bool HTTPRequest::isFinished() { 58 | return _isFinished; 59 | } 60 | 61 | HTTPMethod HTTPRequest::getMethod() { 62 | return method; 63 | } 64 | 65 | String HTTPRequest::getRoute() { 66 | return route; 67 | } 68 | 69 | std::map HTTPRequest::getArgs() { 70 | return args; 71 | } 72 | -------------------------------------------------------------------------------- /src/ConfigurationInterface/WebServer/ESP32/HTTPRequest.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Arduino.h" 3 | #include 4 | 5 | enum HTTPMethod { 6 | GET, 7 | POST, 8 | UNKNOWN, 9 | }; 10 | 11 | class HTTPRequest { 12 | private: 13 | bool readingData = false; 14 | bool firstLine = true; 15 | int dataLength = 0; 16 | String line; 17 | HTTPMethod method; 18 | String route; 19 | String data; 20 | std::map args; 21 | bool _isFinished = false; 22 | bool hasData(); 23 | public: 24 | void add(const char c); 25 | bool isFinished(); 26 | HTTPMethod getMethod(); 27 | String getRoute(); 28 | std::map getArgs(); 29 | }; 30 | -------------------------------------------------------------------------------- /src/ConfigurationInterface/WebServer/ESP8266/ESP8266DefaultWebServer.cpp: -------------------------------------------------------------------------------- 1 | #ifdef ESP8266 2 | 3 | #include "Bleeper/BleeperClass.h" 4 | #include "ConfigurationInterface/WebServer/ESP8266/ESP8266DefaultWebServer.h" 5 | #include 6 | #include 7 | #include 8 | #include "Helpers/Logger.h" 9 | 10 | ESP8266DefaultWebServer::ESP8266DefaultWebServer(int port): BleeperWebServer(port) { 11 | server = NULL; 12 | } 13 | 14 | void ESP8266DefaultWebServer::init() { 15 | server = new ESP8266WebServer(port); 16 | server->on( 17 | "/", std::bind(&ESP8266DefaultWebServer::handleRoot, this) 18 | ); 19 | server->on( 20 | "/save", HTTP_POST, 21 | std::bind(&ESP8266DefaultWebServer::handleSave, this) 22 | ); 23 | server->on( 24 | "/script", std::bind(&ESP8266DefaultWebServer::handleScript, this) 25 | ); 26 | server->on( 27 | "/style", std::bind(&ESP8266DefaultWebServer::handleStyle, this) 28 | ); 29 | server->begin(); 30 | } 31 | 32 | void ESP8266DefaultWebServer::handle(){ 33 | server->handleClient(); 34 | } 35 | 36 | void ESP8266DefaultWebServer::handleRoot(){ 37 | server->send(HTTP_STATUS_OK, CONTENT_TYPE_HTML, buildPage(Bleeper.configuration.getVariables())); 38 | } 39 | 40 | void ESP8266DefaultWebServer::handleScript(){ 41 | server->send_P(HTTP_STATUS_OK, CONTENT_TYPE_JS, getJS()); 42 | } 43 | 44 | void ESP8266DefaultWebServer::handleStyle(){ 45 | server->send_P(HTTP_STATUS_OK, CONTENT_TYPE_CSS, getStyle()); 46 | } 47 | 48 | void ESP8266DefaultWebServer::handleSave(){ 49 | server->send(HTTP_STATUS_OK, CONTENT_TYPE_HTML, ""); 50 | std::map values; 51 | for(auto i = 0; i < server->args(); i++) { 52 | values[server->argName(i)] = server->arg(i); 53 | } 54 | saveValues(values); 55 | } 56 | 57 | #endif // ESP8266 58 | -------------------------------------------------------------------------------- /src/ConfigurationInterface/WebServer/ESP8266/ESP8266DefaultWebServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef ESP8266 4 | 5 | #include "Arduino.h" 6 | #include 7 | #include "ConfigurationInterface/WebServer/BleeperWebServer.h" 8 | 9 | class ESP8266DefaultWebServer: public BleeperWebServer { 10 | protected: 11 | ESP8266WebServer* server; 12 | void handleRoot(); 13 | void handleScript(); 14 | void handleStyle(); 15 | void handleSave(); 16 | public: 17 | ESP8266DefaultWebServer(int port); 18 | virtual void init(); 19 | virtual void handle(); 20 | }; 21 | 22 | #endif // ESP8366 23 | -------------------------------------------------------------------------------- /src/ConfigurationInterface/WebServer/WebPage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const char JS[] PROGMEM = "function onLoad(){for(var e=document.getElementById(\"cf\"),n=e.getElementsByTagName(\"input\"),t=0,a=n.length;a>t;++t)configs[t]=n[t].value;e.addEventListener(\"animationend\",function(){e.className=\"\"});var o=document.getElementsByTagName(\"h1\")[0];o.addEventListener(\"animationend\",function(){o.className=\"\"})}function animate(){document.getElementById(\"cf\").className=\"animate\",document.getElementsByTagName(\"h1\")[0].className=\"animate\"}function save(){for(var e=new XMLHttpRequest,n=document.getElementById(\"cf\").getElementsByTagName(\"input\"),t=\"\",a=configs,o=0,m=n.length;m>o;++o){var s=n[o];s.value!=configs[o]&&(t+=s.name+\"=\"+s.value+\"&\",a[o]=s.value)}t.length>0&&(e.onreadystatechange=function(){4==e.readyState&&200==e.status&&(configs=a,animate())},e.open(\"POST\",document.URL+\"save\",!0),e.setRequestHeader(\"Content-type\",\"application/x-www-form-urlencoded\"),e.send(t))}var configs=new Array;window.onload=onLoad;" 4 | ; 5 | 6 | const char CSS[] PROGMEM = "#cf label{color:#FFF;font-size:90%;font-family:'Courier New'}#cf label.p{color:#2ECC9F}h1{color:#fff;font-weight:300}h1.animate{animation:bounce 1s}body{padding-top:30px;background:#25333c;font-family:'Righteous','Titillium Web',sans-serif;font-size:18px}#cf{padding:5px 15px;margin:0 auto;max-width:400px;background:#25333c;font-weight:80}input[type=text]{font-size:80%;width:100%;padding:10px 15px;margin:12px 0;margin-bottom:20px;display:inline-block;border:1px solid #92999d;box-sizing:border-box;color:#92999d;background-color:#25333c}input:focus{outline:none!important;border:1px solid #28b089;box-shadow:0 0 3px #28b089;color:#fff}input[type=submit]{background-color:#292F33;color:#fff;padding:14px 20px;margin:8px 0;border:none;border-radius:4px;cursor:pointer}button{margin-top:20px;border:none;padding:12px 30px;font-size:90%;background:#28b089;color:#FFF;float:right;outline:none}#cf.animate input{animation:flash 1.5s cubic-bezier(0.095,0.905,0.225,0.915)}@keyframes flash{50%{outline:none!important;border:1px solid #28b089;box-shadow:0 0 3px #28b089;color:#fff;transform:scale(1.03,1.03);background-color:#30434f}}@keyframes bounce{0%,20%,60%,100%{transform:translateY(0)}40%{transform:translateY(-4px)}80%{transform:translateY(-2px)}}button:hover{background:#219E7A}button:active{background:#1D8064}div{border-radius:5px;overflow:hidden}" 7 | ; 8 | -------------------------------------------------------------------------------- /src/Connectivity/AP.cpp: -------------------------------------------------------------------------------- 1 | #include "Connectivity/AP.h" 2 | 3 | #ifdef ESP8266 4 | #include 5 | #elif ESP32 6 | #include 7 | #endif 8 | 9 | #include "Helpers/Logger.h" 10 | 11 | void AP::connect() { 12 | Connection::connect(); 13 | 14 | if (!isExclusiveConnection) { 15 | #ifdef ESP8266 16 | WiFi.mode((WiFiMode)(WiFi.getMode() | WIFI_AP)); 17 | #elif ESP32 18 | WiFi.mode((WiFiMode_t)(WiFi.getMode() | WIFI_AP)); 19 | #endif 20 | } 21 | 22 | WiFi.softAP("Bleeper"); 23 | Log("AP IP address: " + WiFi.softAPIP().toString()); 24 | }; 25 | 26 | void AP::disconnect() { 27 | Connection::disconnect(); 28 | WiFi.softAPdisconnect(true); 29 | }; 30 | 31 | void AP::init() { 32 | } 33 | 34 | void AP::handle() { 35 | isConnected = true; 36 | isConnecting = false; 37 | }; 38 | -------------------------------------------------------------------------------- /src/Connectivity/AP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Connection.h" 3 | 4 | class AP: public Connection { 5 | public: 6 | virtual void connect(); 7 | virtual void disconnect(); 8 | virtual void init(); 9 | virtual void handle(); 10 | }; 11 | -------------------------------------------------------------------------------- /src/Connectivity/Connection.cpp: -------------------------------------------------------------------------------- 1 | #include "Connectivity/Connection.h" 2 | #include "Helpers/Logger.h" 3 | 4 | Connection::Connection() { 5 | triedToConnect = false; 6 | isExclusiveConnection = false; 7 | } 8 | 9 | void Connection::setIsExclusiveConnection(bool is) { 10 | isExclusiveConnection = is; 11 | } 12 | 13 | void Connection::connect() { 14 | triedToConnect = true; 15 | isConnecting = true; 16 | } 17 | 18 | void Connection::disconnect() { 19 | triedToConnect = false; 20 | isConnecting = false; 21 | isConnected = false; 22 | } 23 | 24 | bool Connection::connectionDidFail() { 25 | return triedToConnect && !isConnected && !isConnecting; 26 | } 27 | 28 | bool Connection::connectionDidComplete() { 29 | return triedToConnect && isConnected && !isConnecting; 30 | } 31 | 32 | void Connection::retry() { 33 | disconnect(); 34 | connect(); 35 | } 36 | 37 | bool Connection::wantsToRetryConnection() { 38 | return false; 39 | } 40 | 41 | Connections::Connections() { 42 | 43 | } 44 | 45 | Connections::Connections(std::vector connections) { 46 | this->connections = connections; 47 | } 48 | 49 | void Connections::handle() {}; 50 | 51 | void Connections::init() { 52 | for(auto c: connections) { 53 | c->init(); 54 | } 55 | }; 56 | 57 | void Connections::add(Connection* c) { 58 | connections.push_back(c); 59 | } 60 | 61 | MultipleConnection::MultipleConnection(std::vector connections) : Connections(connections) { 62 | } 63 | 64 | void MultipleConnection::connect() { 65 | Connections::connect(); 66 | for(Connection* c: connections) { 67 | c->setIsExclusiveConnection(false); 68 | c->connect(); 69 | } 70 | } 71 | 72 | void MultipleConnection::disconnect() { 73 | Connections::disconnect(); 74 | for(Connection* c: connections) { 75 | c->disconnect(); 76 | } 77 | } 78 | 79 | void MultipleConnection::handle() { 80 | bool allConnectionsConnected = true; 81 | for(Connection* c: connections) { 82 | if (c->wantsToRetryConnection()) { 83 | Log("retrying connection"); 84 | c->connect(); 85 | } 86 | c->handle(); 87 | isConnecting = isConnecting || c->isConnecting; 88 | allConnectionsConnected = allConnectionsConnected && c->isConnected; 89 | } 90 | isConnected = allConnectionsConnected; 91 | } 92 | 93 | OneOfMultipleConnection::OneOfMultipleConnection(std::vector connections) : Connections(connections) { 94 | connectionIndex = 0; 95 | betterConnectionIndex = -1; 96 | } 97 | 98 | OneOfMultipleConnection::OneOfMultipleConnection() { 99 | connectionIndex = 0; 100 | betterConnectionIndex = -1; 101 | } 102 | 103 | void OneOfMultipleConnection::connect() { 104 | Connections::connect(); 105 | connections[connectionIndex]->connect(); 106 | } 107 | 108 | void OneOfMultipleConnection::disconnect() { 109 | Connections::disconnect(); 110 | for(Connection* c: connections) { 111 | c->disconnect(); 112 | } 113 | } 114 | 115 | void OneOfMultipleConnection::disconnectFromAllExepct(int c) { 116 | for(auto i = 0; i < connections.size(); i++) { 117 | if (i != c) 118 | connections[i]->disconnect(); 119 | } 120 | } 121 | 122 | void OneOfMultipleConnection::handle() { 123 | if (connections[connectionIndex]->wantsToRetryConnection()) { 124 | connections[connectionIndex]->retry(); 125 | } 126 | connections[connectionIndex]->handle(); 127 | isConnected = connections[connectionIndex]->isConnected; 128 | if (isConnected) { 129 | isConnecting = false; 130 | } 131 | 132 | if (connectionIndex < connections.size() - 1) { 133 | if (connections[connectionIndex]->connectionDidFail()) { 134 | Log("Connection did fail for index " + String(connectionIndex)); 135 | connections[connectionIndex]->disconnect(); 136 | connectionIndex = (connectionIndex + 1) % connections.size(); 137 | connections[connectionIndex]->connect(); 138 | return; 139 | } 140 | } 141 | 142 | if (isConnected && (connectionIndex == 0)) { 143 | return; 144 | } 145 | 146 | if (betterConnectionIndex != -1) { 147 | connections[betterConnectionIndex]->handle(); 148 | if (connections[betterConnectionIndex]->connectionDidComplete()) { 149 | connections[connectionIndex]->disconnect(); 150 | connectionIndex = betterConnectionIndex; 151 | betterConnectionIndex = -1; 152 | } else if (connections[betterConnectionIndex]->connectionDidFail()) { 153 | connections[betterConnectionIndex]->disconnect(); 154 | betterConnectionIndex = -1; 155 | } 156 | } 157 | 158 | if (betterConnectionIndex == -1) { 159 | for(int b = 0; b < connectionIndex; b++) { 160 | if (connections[b]->wantsToRetryConnection()) { 161 | betterConnectionIndex = b; 162 | connections[b]->retry(); 163 | Log("Retrying connection " + String(b)); 164 | break; 165 | } 166 | } 167 | } 168 | 169 | } 170 | 171 | void OneOfMultipleConnection::retry() { 172 | Log("Retrying... " + String(connectionIndex)); 173 | connectionIndex = 0; 174 | Connections::retry(); 175 | } 176 | -------------------------------------------------------------------------------- /src/Connectivity/Connection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "Bleeper/Initable.h" 4 | 5 | class Connection: public Initable { 6 | protected: 7 | bool triedToConnect; 8 | bool isExclusiveConnection; 9 | public: 10 | bool isConnected; 11 | bool isConnecting; 12 | 13 | Connection(); 14 | virtual void setIsExclusiveConnection(bool is); 15 | virtual void connect(); 16 | virtual void disconnect(); 17 | virtual void handle() = 0; 18 | virtual void init() = 0; 19 | virtual bool connectionDidFail(); 20 | virtual bool connectionDidComplete(); 21 | virtual void retry(); 22 | virtual bool wantsToRetryConnection(); 23 | }; 24 | 25 | class Connections: public Connection { 26 | protected: 27 | std::vector connections; 28 | public: 29 | Connections(); 30 | Connections(std::vector connections); 31 | virtual void handle(); 32 | virtual void init(); 33 | void add(Connection* c); 34 | }; 35 | 36 | class MultipleConnection: public Connections { 37 | public: 38 | MultipleConnection(std::vector connections); 39 | virtual void connect(); 40 | virtual void disconnect(); 41 | virtual void handle(); 42 | }; 43 | 44 | class OneOfMultipleConnection: public Connections { 45 | private: 46 | int connectionIndex; 47 | int betterConnectionIndex; 48 | public: 49 | OneOfMultipleConnection(std::vector connections); 50 | OneOfMultipleConnection(); 51 | virtual void connect(); 52 | virtual void disconnect(); 53 | void disconnectFromAllExepct(int c); 54 | virtual void handle(); 55 | virtual void retry(); 56 | }; 57 | -------------------------------------------------------------------------------- /src/Connectivity/Wifi.cpp: -------------------------------------------------------------------------------- 1 | #include "Connectivity/Wifi.h" 2 | 3 | #ifdef ESP8266 4 | #include 5 | #elif ESP32 6 | #include 7 | #endif 8 | 9 | #include "Bleeper/BleeperClass.h" 10 | #include "Helpers/Logger.h" 11 | #include "Helpers/macros.h" 12 | 13 | void Wifi::printWifiStatus() { 14 | Log("SSID: " + WiFi.SSID()); 15 | IPAddress ip = WiFi.localIP(); 16 | Log("IP Address: " + ip.toString()); 17 | } 18 | 19 | Wifi::Wifi(VariableAddress ssidAddress, VariableAddress passwordAddress) { 20 | this->ssidAddress = ssidAddress; 21 | this->passwordAddress = passwordAddress; 22 | } 23 | 24 | void Wifi::init() { 25 | auto c = Bleeper.configuration.get(); 26 | guard(c, return); 27 | guard(c->isAddressValid(ssidAddress), return); 28 | guard(c->isAddressValid(passwordAddress), return); 29 | 30 | StringConvertibleVariable* ssid = c->getVariableForAddress(ssidAddress); 31 | StringConvertibleVariable* password = c->getVariableForAddress(passwordAddress); 32 | this->ssid = ssid->getAsString(); 33 | ssidKey = ssid->getFullKey(); 34 | this->password = password->getAsString(); 35 | _wantsToRetryConnection = false; 36 | Bleeper.configuration.addObserver(this, {ssidAddress, passwordAddress}); 37 | } 38 | 39 | void Wifi::onConfigurationChanged(const ConfigurationPropertyChange value) { 40 | Log("wifi settings changed..."); 41 | Log("Wifi -> " + value.key + " changed from " + value.oldValue + " to " + value.newValue); 42 | if (value.key == ssidKey) { 43 | ssid = value.newValue; 44 | } else { 45 | password = value.newValue; 46 | } 47 | _wantsToRetryConnection = true; 48 | } 49 | 50 | void Wifi::connect() { 51 | Connection::connect(); 52 | _wantsToRetryConnection = false; 53 | if (!isExclusiveConnection) { 54 | #ifdef ESP8266 55 | WiFi.mode((WiFiMode)(WiFi.getMode() | WIFI_STA)); 56 | #elif ESP32 57 | WiFi.mode((WiFiMode_t)(WiFi.getMode() | WIFI_STA)); 58 | #endif 59 | } 60 | WiFi.begin(ssid.c_str(), password.c_str()); 61 | }; 62 | 63 | void Wifi::disconnect() { 64 | Connection::disconnect(); 65 | WiFi.disconnect(true); 66 | }; 67 | 68 | void Wifi::handle() { 69 | auto status = WiFi.status(); 70 | isConnected = (status == WL_CONNECTED); 71 | isConnecting = !isConnected && (status != WL_CONNECT_FAILED) && (status != WL_NO_SSID_AVAIL); 72 | if (isConnected && firstTime) { 73 | printWifiStatus(); 74 | firstTime = false; 75 | } 76 | }; 77 | 78 | bool Wifi::wantsToRetryConnection() { 79 | return _wantsToRetryConnection; 80 | } 81 | 82 | void Wifi::retry() { 83 | firstTime = true; 84 | WiFi.disconnect(); 85 | connect(); 86 | } 87 | -------------------------------------------------------------------------------- /src/Connectivity/Wifi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Connection.h" 4 | #include "Observers/ConfigurationObserver.h" 5 | 6 | class Wifi: public Connection, public ConfigurationObserver { 7 | private: 8 | String ssid, password; 9 | String ssidKey; 10 | bool _wantsToRetryConnection; 11 | bool firstTime = true; 12 | 13 | VariableAddress ssidAddress; 14 | VariableAddress passwordAddress; 15 | 16 | void printWifiStatus(); 17 | public: 18 | Wifi(VariableAddress ssidAddress, VariableAddress passwordAddress); 19 | virtual void init(); 20 | virtual void onConfigurationChanged(const ConfigurationPropertyChange value); 21 | virtual void connect(); 22 | virtual void disconnect(); 23 | virtual void handle(); 24 | virtual bool wantsToRetryConnection(); 25 | virtual void retry(); 26 | }; 27 | -------------------------------------------------------------------------------- /src/Helpers/Logger.cpp: -------------------------------------------------------------------------------- 1 | #include "Logger.h" 2 | 3 | bool Logger::verbose = false; 4 | int Logger::baudRate = 115200; 5 | bool Logger::serialInitialized = false; 6 | -------------------------------------------------------------------------------- /src/Helpers/Logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Arduino.h" 3 | 4 | #define Log(msg) Logger::print(msg) 5 | 6 | class Logger { 7 | public: 8 | static bool verbose; 9 | static bool serialInitialized; 10 | static int baudRate; 11 | static void print(String msg) { 12 | if (!serialInitialized) { 13 | Serial.begin(baudRate); 14 | while (!Serial) yield(); 15 | serialInitialized = true; 16 | } 17 | if (verbose) { 18 | Serial.println("[Bleeper]: " + msg); 19 | } 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/Helpers/macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define guard(condition, returnStatement) if (!(condition)) { returnStatement; } 4 | -------------------------------------------------------------------------------- /src/Helpers/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | std::vector splitString(String string, char delimiter){ 4 | std::vector result; 5 | int lastStart = 0; 6 | for(int i = 0; i < string.length(); i++){ 7 | if (string.charAt(i) == delimiter){ 8 | result.push_back(string.substring(lastStart,i)); 9 | lastStart = i+1; 10 | } 11 | } 12 | if (lastStart < string.length()){ 13 | result.push_back(string.substring(lastStart,string.length())); 14 | } 15 | return result; 16 | } 17 | 18 | void flashLed(int led, int onMs, int offMs, int times){ 19 | for(int i = 0; i < times; i++){ 20 | digitalWrite(led,LOW); 21 | delay(onMs); 22 | digitalWrite(led,HIGH); 23 | delay(offMs); 24 | } 25 | } 26 | 27 | void configureLed(int led) { 28 | pinMode(led,OUTPUT); 29 | digitalWrite(led,HIGH); 30 | flashLed(led, 300,250,3); 31 | } 32 | -------------------------------------------------------------------------------- /src/Helpers/utils.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Arduino.h" 3 | 4 | void configureLed(int led); 5 | void flashLed(int led, int onMs, int offMs, int times); 6 | std::vector splitString(String string, char delimiter); 7 | -------------------------------------------------------------------------------- /src/Observers/ConfigurationObserver.cpp: -------------------------------------------------------------------------------- 1 | #include "Observers/ConfigurationObserver.h" 2 | 3 | bool ConfigurationObserver::isEnabled = true; 4 | 5 | ConfigurationObserver::ConfigurationObserver() {} 6 | 7 | void ConfigurationObserver::setFilter(std::set addressFilter) { 8 | this->addressFilter = addressFilter; 9 | } 10 | 11 | bool ConfigurationObserver::isInterestedIn(VariableAddress address) { 12 | return ConfigurationObserver::isEnabled && (addressFilter.empty() || (addressFilter.find(address) != addressFilter.end())); 13 | } 14 | -------------------------------------------------------------------------------- /src/Observers/ConfigurationObserver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Configuration/ConfigMap.h" 3 | #include 4 | 5 | struct ConfigurationPropertyChange { 6 | String key; 7 | String oldValue; 8 | String newValue; 9 | }; 10 | 11 | class ConfigurationObserver { 12 | protected: 13 | std::set addressFilter; 14 | public: 15 | static bool isEnabled; 16 | ConfigurationObserver(); 17 | void setFilter(std::set addressFilter); 18 | virtual bool isInterestedIn(VariableAddress address); 19 | virtual void onConfigurationChanged(const ConfigurationPropertyChange value) = 0; 20 | }; 21 | -------------------------------------------------------------------------------- /src/Observers/Observable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | template 5 | class Observable { 6 | protected: 7 | std::vector observers; 8 | public: 9 | Observer& addObserver(Observer* observer) { 10 | observers.push_back(observer); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/Storage/EEPROMHelper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "Arduino.h" 4 | #include "Helpers/Logger.h" 5 | 6 | #define MAX_EEPROM_SIZE 4096 7 | #define FORMAT_VALUE 0x55 8 | 9 | class EEPROMHelper { 10 | public: 11 | 12 | EEPROMHelper(){ 13 | EEPROM.begin(MAX_EEPROM_SIZE); 14 | byte v; 15 | EEPROM.get(0,v); 16 | if (v != FORMAT_VALUE){ 17 | Log("Initializing EEPROM..."); 18 | EEPROM.write(0,FORMAT_VALUE); 19 | unsigned short stringsCount = 0; 20 | EEPROM.put(1, 0); 21 | for(int i = 3; i < MAX_EEPROM_SIZE; i++) 22 | EEPROM.write(i, 0); 23 | Log("Initializing EEPROM...Done!"); 24 | } 25 | EEPROM.end(); 26 | delay(500); 27 | }; 28 | 29 | public: 30 | std::vector readStrings() { 31 | EEPROM.begin(MAX_EEPROM_SIZE); 32 | std::vector result; 33 | unsigned short stringsCount; 34 | EEPROM.get(1, stringsCount); 35 | int addr = 3; 36 | for(auto s = 0; s < stringsCount; s++) { 37 | String res = ""; 38 | for(; addr < MAX_EEPROM_SIZE; addr++){ 39 | char r = char(EEPROM.read(addr)); 40 | if (r == '\0') 41 | break; 42 | res += String(r); 43 | } 44 | addr++; 45 | result.push_back(res); 46 | } 47 | EEPROM.end(); 48 | return result; 49 | } 50 | 51 | void writeStrings(const std::vector & strings) { 52 | EEPROM.begin(MAX_EEPROM_SIZE); 53 | 54 | unsigned short stringsCount = strings.size(); 55 | EEPROM.put(1, stringsCount); 56 | 57 | int addr = 3; 58 | for(auto s: strings) { 59 | for(int i = 0; addr < MAX_EEPROM_SIZE && i < s.length(); i++, addr++){ 60 | EEPROM.write(addr, s.charAt(i)); 61 | } 62 | EEPROM.write(addr,'\0'); 63 | addr++; 64 | } 65 | 66 | EEPROM.end(); 67 | delay(500); 68 | } 69 | 70 | }; 71 | -------------------------------------------------------------------------------- /src/Storage/SPIFFSStorage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Bleeper/BleeperClass.h" 3 | #include "Helpers/Logger.h" 4 | #include "Helpers/utils.h" 5 | 6 | #define CONFIGS_FILE "/bleeperVars.bpr" 7 | 8 | #ifdef ESP8266 9 | #include "FS.h" 10 | #elif ESP32 11 | #include "SPIFFS.h" 12 | #endif 13 | 14 | class SPIFFSStorage: public Storage { 15 | public: 16 | void init() { 17 | SPIFFS.begin(); 18 | if (!SPIFFS.exists(CONFIGS_FILE)) { 19 | SPIFFS.format(); 20 | } 21 | } 22 | 23 | void persist() { 24 | File f = SPIFFS.open(CONFIGS_FILE, "w"); 25 | if (!f) { 26 | f.close(); 27 | return; 28 | } 29 | std::vector persistentVars = Bleeper.configuration.getAsDictionary(true).toStrings(); 30 | for(auto const & s: persistentVars) { 31 | f.print(s); 32 | f.write(0); 33 | } 34 | f.close(); 35 | } 36 | 37 | void load() { 38 | File f = SPIFFS.open(CONFIGS_FILE, "r"); 39 | if (!f) { 40 | f.close(); 41 | persist(); 42 | return; 43 | } 44 | std::vector storedVars; 45 | while (f.available()) { 46 | auto readString = f.readStringUntil('\0'); 47 | storedVars.push_back(readString); 48 | } 49 | Bleeper.configuration.setFromDictionary(ConfigurationDictionary(storedVars)); 50 | f.close(); 51 | } 52 | 53 | }; 54 | -------------------------------------------------------------------------------- /src/Storage/Storage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Bleeper/Initable.h" 4 | 5 | class Storage: public Initable { 6 | public: 7 | virtual void persist() = 0; 8 | virtual void load() = 0; 9 | }; 10 | -------------------------------------------------------------------------------- /src/Storage/VariablesMapStorage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Bleeper/BleeperClass.h" 3 | #include "Storage/EEPROMHelper.h" 4 | 5 | class VariablesMapStorage: public Storage { 6 | private: 7 | EEPROMHelper eeprom; 8 | public: 9 | void init() { 10 | 11 | } 12 | 13 | void persist() { 14 | std::vector persistentVars = Bleeper.configuration.getAsDictionary(true).toStrings(); 15 | eeprom.writeStrings(persistentVars); 16 | } 17 | 18 | void load() { 19 | std::vector storedVars = eeprom.readStrings(); 20 | if (storedVars.size() == 0) { 21 | persist(); 22 | } else { 23 | Bleeper.configuration.setFromDictionary(ConfigurationDictionary(storedVars)); 24 | } 25 | } 26 | 27 | }; 28 | --------------------------------------------------------------------------------