This is a sort-of application framework for ESP32/ESP8266 Arduino/PlatformIO projects. Essentially, it provides a single Application class that offers a number of features, all configurable using a single text file in LittleFS. Most important among these are a WiFi connection, the current time, a built-in web server, OTA updating, and periodic task execution.
Also, there is a en extended class called MqttApplication, that layers Mqtt support on top of Application. It can connect to an Mqtt broker, and send and receive Mqtt messages. This makes it a great starting point for "sensor apps": applications that monitor some external data and report periodically to a central location.
Just add a the URL of this repository to your platform.ini file:
lib_deps = https://github.com/mobzystems/mobzesplib
mobzesplib depends on other libraries but these will be installed automatically.
If not present already, configure your file system to LittleFS:
board_build.filesystem = littlefs
Warning: If you see errors about header files not being found, add the following to platform.ini:
lib_ldf_mode = deep
The main idea behing mobzesplib is to have a single instance of the Application
class in your project. In the examples we call it _app
but you can choose any other name.
The application object has a number of features built-in:
- WiFi - it connects to a WiFi network on startup and periodically checks the connection, restoring it if possible
- Time - using WiFi, it queries the current time (using ezTime)
- A web server on port 80 (configurable in the
Application
constructor) - Over The Air (OTA) updating of both the firmware and the contents of the file system (using ElegantOTA)
- (optional) Configuration via a web page
- (optional) File system editing via a web page
To facilitate trouble shooting, mobzesplib uses logging throughout. Logging is independent of the application object: all funcionality is
in the Log
class.
Log::setSerialLogLevel(Log::LOGLEVEL::Debug);
: Configure a minimal log level forSerial
(the serial or USB port). All log messages with a higher or equal level are shown; those lower are suppressed. By default this isInformation
. The various log levels are:- Trace - Extremely verbose
- Debug - Verbose
- Information - Normal
- Warning - Important
- Critical - Very important
Log::logInformation("The value of the sensor is %d", value);
Do your own logging. ChangeInformation
toDebug
etc. for the level you want. Note that this is aprintf()
-like method that always adds a newline. Note: the maximu length of a single log meesage is 256, as defined byMAX_LOGMESSAGE_SIZE
. To override this,#define MAX_LOGMESSAGE_SIZE xxx
to some other value before includingApplication.h
.
You configure the application object, then call _app.setup()
on it and add a call to its _app.loop()
method in you own loop()
method.
So, the absolute minimum to get the code for a running application is:
#include <Application.h>
Application _app("Application title", "0.0.1");
void setup() {
// Initialize serial communications
Serial.begin(115200);
// Choose a log level for the serial output. Default is Information
Log::setSerialLogLevel(Log::LOGLEVEL::Debug);
_app.setup();
}
void loop() {
_app.loop();
}
After including Application.h
we create the _app
object, supplying a title and a version. (Normally you'd create a const char *
for this, or a #define
but this is minimal, remember?)
Then, from setup()
we call _app.setup()
to initialize the application and from our own loop()
we call _app.loop()
.
When run, this application will connect to a WiFi network using its host name, and connect to a time server. But which WiFi-network?
Most configuration required to get the app up and running comes from a configuration file in the file system. mobzesplib requires the LittleFS
file system, with a single text file in the root called /config.sys
. The minimum configuration is:
# REQUIRED: WiFi
hostname=mobzhub-default
wifi-ssid=YOUR_WIFI_SSID
wifi-password=YOUR_WIFI_PASSWORD
# Optional: OTA
ota-username=admin
ota-password=test
# Optional: time zone (defaults to Europe/Amsterdam)
timezone=YOUR_TIMEZONE
Using the WiFi configuration, the application will connect to WiFi and enable OTA updates. When both ota-username
and ota-password
are set, these are used to secure the OTA web page. After setup()
the application's boot time is available in both local and UTC form.
There is a sample config.sys
file in the data
folder in the examples.
You can add your own configuration entries. The application will read them and make them available through
_app.config("key", "default-value");
This returns a const char *
that either points to the value of the key, or to "default-value"
if the key was not present. You can supply NULL as a default value.
Note: the first value of the key encountered is used. If your configuration contains
key=one
...
key=two
then the value returned will be "one"
.
_app.enableConfigEditor("/config");
Enable editing of the /config.sys
configuration file at the path /config
. Note: you can change the path but the name of the configuration file is always /config.sys
!
_app.enableFileEditor("/read", "/write", "/edit");
Enable reading, writing and editing of any file in the file system at these paths. Needless to say: dangerous, use at your own risk. Doesn't have authentication (yet).
_app.webserver()->serveStatic("/", LittleFS, "/wwwroot/");
Enable serving of all files in the /wwwroot
folder. Should be safe.
_app.mapGet("/info", [](WEBSERVER *server) {
// ...
});
_app.mapPost("/info", [](WEBSERVER *server) {
// ...
});
Simple web server mappings using lambda functions. The single argument to the lambda function is a pointer to a WEBSERVER
which is an alias to the correct web server type for ESP32 or ESP8266. From within the lambda you can call server->send()
etc.
_app.enableInfoPage("/info")
Uses mapGet()
to display a simple text-only page on the specified path. Handy for testing.
Most applications require some kind of periodic task to run, like sampling a sensor or reporting a heart beat. You can add a task using a single statement with a lambda:
_app.addTask("Sample the sensor", 60 * 1000, []() {
// Sample the sensor here every 60 seconds (60 * 1000 milliseconds)
});
You can add any number of tasks this way. The application will call the supplied lambda function for each when it's its turn.
Note: Tasks are run from _app.loop()
and are not interrupt or timer based. Therefore they're not accurate at the millisecond level. But ESP8266s loop around 20,000 times per second, and ESP32s at around 1,000 times per second, so your tasks will probably run on time. Using a task, you can avoid calling delay()
and keep your process responsive. The application will not call delay()
from its own loop.
Make sure to add
lib_ldf_mode = deep
to platform.ini if you have problems loading header files. This configures the Library dependency Finder to deep
and allows mobzesplib to find the other libraries it depends on.
(Note on note: this should not be neccessary anymore from version 0.3.1 onwards. By restructuring the inclusion of header files the normal LDF mode ('chain') will find all header files.)