Commit 92b9b904 authored by Eric Duminil's avatar Eric Duminil
Browse files

Removing stuff

parent acc05041
#ifndef AMPEL_H_INCLUDED
#define AMPEL_H_INCLUDED
/*****************************************************************
* Libraries *
*****************************************************************/
//NOTE: Too many headers. Move them to include/ folder?
#include "web_config.h" // Needed for offline config too.
#include "csv_writer.h"
#include "wifi_util.h"
#include "mqtt.h"
#include "web_server.h"
#include "lorawan.h"
#include "util.h"
#include "ntp.h"
#include "sensor_console.h"
#include "co2_sensor.h"
#include "led_effects.h"
void wifiConnected();
void wifiFailed();
void keepServicesAlive();
void checkFlashButton();
#endif
/***
* ____ ___ ____ _ _
* / ___/ _ \___ \ / \ _ __ ___ _ __ ___| |
* | | | | | |__) | / _ \ | '_ ` _ \| '_ \ / _ \ |
* | |__| |_| / __/ / ___ \| | | | | | |_) | __/ |
* \____\___/_____| /_/__ \_\_| |_| |_| .__/ \___|_| _
* | | | |/ _|_ _| / ___|| |_ _ _| |_| |_ __ _ __ _ _ __| |_
* | |_| | |_ | | \___ \| __| | | | __| __/ _` |/ _` | '__| __|
* | _ | _| | | ___) | |_| |_| | |_| || (_| | (_| | | | |_
* |_| |_|_| |_| |____/ \__|\__,_|\__|\__\__, |\__,_|_| \__|
* |___/
*/
#include "ampel-firmware.h"
/*****************************************************************
* GPL License *
*****************************************************************/
/*
* This file is part of the "CO2 Ampel" project ( https://transfer.hft-stuttgart.de/gitlab/co2ampel and
* https://transfer.hft-stuttgart.de/gitlab/co2ampel/ampel-firmware )
* Copyright (c) 2020 HfT Stuttgart.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
/**
* IotWebConf03TypedParameters.ino -- IotWebConf is an ESP8266/ESP32
* non blocking WiFi/AP web configuration library for Arduino.
* https://github.com/prampec/IotWebConf
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Copyright (C) 2020 Balazs Kelemen <prampec+arduino@gmail.com>
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
/*****************************************************************
* Authors *
*****************************************************************/
/*
* Eric Duminil
* Robert Otto
* Myriam Guedey
* Tobias Gabriel Erhart
* Jonas Stave
* Michael Käppler
*/
/*****************************************************************
* Configuration *
*****************************************************************/
/*
* Please define settings in 'config.h'.
* There's an example config file called 'config.example.h'.
* You can copy 'config.public.h' (stored in Git) to 'config.h' (not stored in Git),
* and define your credentials and parameters in 'config.h'.
/**
* Example: Custom parameters
* Description:
* Typed Parameters are a new approach to store/handle parameter data.
* This part of the software is very experimental, and certainly
* not recommended for beginners.
* The goal of this particular example is to compare the original
* approach of IotWebConf03CustomParameters to this new typed
* parameters, as both examples should work the same.
*
* Hardware setup for this example:
* - An LED is attached to LED_BUILTIN pin with setup On=LOW.
* - [Optional] A push button is attached to pin D2, the other leg of the
* button should be attached to GND.
*/
/*****************************************************************
* Setup *
*****************************************************************/
void setup() {
led_effects::setupOnBoardLED();
led_effects::onBoardLEDOff();
Serial.begin(config::bauds);
web_config::initialize();
web_config::setWifiConnectionCallback(wifiConnected);
web_config::setWifiFailCallback(wifiFailed);
pinMode(0, INPUT); // Flash button (used for forced calibration)
#include <IotWebConf.h>
#include <IotWebConfTParameter.h>
// -- Initial name of the Thing. Used e.g. as SSID of the own Access Point.
const char thingName[] = "testThing";
// -- Initial password to connect to the Thing, when it creates an own Access Point.
const char wifiInitialApPassword[] = "smrtTHNG8266";
#define STRING_LEN 128
#define NUMBER_LEN 32
// -- Configuration specific key. The value should be modified if config structure was changed.
#define CONFIG_VERSION "dem4"
// -- When CONFIG_PIN is pulled to ground on startup, the Thing will use the initial
// password to buld an AP. (E.g. in case of lost password)
#define CONFIG_PIN 2 // D2
// -- Status indicator pin.
// First it will light up (kept LOW), on Wifi connection it will blink,
// when connected to the Wifi it will turn off (kept HIGH).
#define STATUS_PIN LED_BUILTIN
// -- Method declarations.
void handleRoot();
// -- Callback methods.
void configSaved();
bool formValidator(iotwebconf::WebRequestWrapper* webRequestWrapper);
DNSServer dnsServer;
WebServer server(80);
static const char chooserValues[][STRING_LEN] = { "red", "blue", "darkYellow" };
static const char chooserNames[][STRING_LEN] = { "Red", "Blue", "Dark yellow" };
IotWebConf iotWebConf(thingName, &dnsServer, &server, wifiInitialApPassword, CONFIG_VERSION);
iotwebconf::TextTParameter<STRING_LEN> stringParam =
iotwebconf::Builder<iotwebconf::TextTParameter<STRING_LEN>>("stringParam").
label("String param").
defaultValue("").
build();
iotwebconf::ParameterGroup group1 = iotwebconf::ParameterGroup("group1", "");
iotwebconf::IntTParameter<int16_t> intParam =
iotwebconf::Builder<iotwebconf::IntTParameter<int16_t>>("intParam").
label("Int param").
defaultValue(30).
min(1).
max(100).
step(1).
placeholder("1..100").
build();
// -- We can add a legend to the separator
iotwebconf::ParameterGroup group2 = iotwebconf::ParameterGroup("c_factor", "Calibration factor");
iotwebconf::FloatTParameter floatParam =
iotwebconf::Builder<iotwebconf::FloatTParameter>("floatParam").
label("Float param").
defaultValue(0.0).
step(0.1).
placeholder("e.g. 23.4").
build();
iotwebconf::CheckboxTParameter checkboxParam =
iotwebconf::Builder<iotwebconf::CheckboxTParameter>("checkParam").
label("Check param").
defaultValue(true).
build();
iotwebconf::SelectTParameter<STRING_LEN> chooserParam =
iotwebconf::Builder<iotwebconf::SelectTParameter<STRING_LEN>>("chooseParam").
label("Choose param").
optionValues((const char*)chooserValues).
optionNames((const char*)chooserNames).
optionCount(sizeof(chooserValues) / STRING_LEN).
nameLength(STRING_LEN).
defaultValue("blue").
build();
iotwebconf::ColorTParameter colorParam =
iotwebconf::Builder<iotwebconf::ColorTParameter>("colorParam").
label("Choose color").
defaultValue("#FFDD88").
build();
iotwebconf::DateTParameter dateParam =
iotwebconf::Builder<iotwebconf::DateTParameter>("dateParam").
label("Select date").
defaultValue("").
build();
iotwebconf::TimeTParameter timeParam =
iotwebconf::Builder<iotwebconf::TimeTParameter>("timeParam").
label("Select time").
defaultValue("").
build();
void setup()
{
Serial.begin(115200);
Serial.println();
Serial.print(F("Sensor ID: "));
Serial.println(ampel.sensorId);
Serial.print(F("Name : "));
Serial.println(config::ampel_name());
Serial.print(F("MAC : "));
Serial.println(ampel.macAddress);
Serial.print(F("Board : "));
Serial.println(ampel.board);
Serial.print(F("Firmware : "));
Serial.println(ampel.version);
led_effects::setupRing();
sensor::initialize();
csv_writer::initialize(config::ampel_name());
ntp::initialize();
if (config::is_wifi_on) {
wifi::defineCommands();
web_server::definePages();
wifi::tryConnection();
}
#if defined(ESP32)
if (config::is_lorawan_active()) {
lorawan::initialize();
}
#endif
Serial.println("Starting up...");
group1.addItem(&intParam);
group2.addItem(&floatParam);
group2.addItem(&checkboxParam);
group2.addItem(&chooserParam);
group2.addItem(&colorParam);
group2.addItem(&dateParam);
group2.addItem(&timeParam);
iotWebConf.setStatusPin(STATUS_PIN);
iotWebConf.setConfigPin(CONFIG_PIN);
iotWebConf.addSystemParameter(&stringParam);
iotWebConf.addParameterGroup(&group1);
iotWebConf.addParameterGroup(&group2);
iotWebConf.setConfigSavedCallback(&configSaved);
iotWebConf.setFormValidator(&formValidator);
iotWebConf.getApTimeoutParameter()->visible = true;
// -- Initializing the configuration.
iotWebConf.init();
// -- Set up required URL handlers on the web server.
server.on("/", handleRoot);
server.on("/config", []{ iotWebConf.handleConfig(); });
server.onNotFound([](){ iotWebConf.handleNotFound(); });
Serial.println("Ready.");
}
/*****************************************************************
* Main loop *
*****************************************************************/
void loop() {
#if defined(ESP32)
if (config::is_lorawan_active()) {
//LMIC Library seems to be very sensitive to timing issues, so run it first.
lorawan::process();
if (lorawan::waiting_for_confirmation) {
// If node is waiting for join confirmation from Gateway, nothing else should run.
return;
}
}
#endif
//NOTE: Loop should never take more than 1000ms. Split in smaller methods and logic if needed.
//NOTE: Only use millis() for duration comparison, not timestamps comparison. Otherwise, problems happen when millis roll over.
uint32_t t0 = millis();
keepServicesAlive();
// Short press for night mode, Long press for calibration.
checkFlashButton();
sensor_console::checkSerialInput();
if (sensor::processData()) {
if (config::is_csv_active()) {
csv_writer::logIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity);
}
if (config::is_wifi_on && config::is_mqtt_active()) {
mqtt::publishIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity);
}
#if defined(ESP32)
if (config::is_lorawan_active()) {
lorawan::preparePayloadIfTimeHasCome(sensor::co2, sensor::temperature, sensor::humidity);
}
#endif
}
uint32_t duration = millis() - t0;
if (duration > ampel.max_loop_duration) {
ampel.max_loop_duration = duration;
Serial.print(F("Debug - Max loop duration : "));
Serial.print(ampel.max_loop_duration);
Serial.println(F(" ms."));
}
void loop()
{
// -- doLoop should be called as frequently as possible.
iotWebConf.doLoop();
}
/*****************************************************************
* Callbacks *
*****************************************************************/
void wifiConnected() {
led_effects::showKITTWheel(color::green);
Serial.println();
Serial.print(F("WiFi - Connected to "));
Serial.print(WiFi.SSID());
Serial.print(F(", IP address: "));
IPAddress address = WiFi.localIP();
snprintf(wifi::local_ip, sizeof(wifi::local_ip), "%d.%d.%d.%d", address[0], address[1], address[2], address[3]);
Serial.println(wifi::local_ip);
ntp::connect();
if (config::is_mqtt_active()) {
mqtt::initialize(ampel.sensorId);
/**
* Handle web requests to "/" path.
*/
void handleRoot()
{
// -- Let IotWebConf test and handle captive portal requests.
if (iotWebConf.handleCaptivePortal())
{
// -- Captive portal request were already served.
return;
}
Serial.print(F("You can access this sensor via http://"));
Serial.print(config::ampel_name());
Serial.print(F(".local (might be unstable) or http://"));
Serial.println(WiFi.localIP());
String s = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>";
s += "<title>IotWebConf 03 Custom Parameters</title></head><body>Hello world!";
s += "<ul>";
s += "<li>String param value: ";
s += stringParam.value();
s += "<li>Int param value: ";
s += intParam.value();
s += "<li>Float param value: ";
s += floatParam.value();
s += "<li>CheckBox selected: ";
s += checkboxParam.isChecked();
s += "<li>Option selected: ";
s += chooserParam.value();
s += "<li>Color selected: <div style='background-color:";
s += colorParam.value();
s += "';> sample </div> (";
s += colorParam.value();
s += ")";
s += "<li>Date value: ";
s += dateParam.value();
s += "<li>Time value: ";
s += timeParam.value();
s += "</ul>";
s += "Go to <a href='config'>configure page</a> to change values.";
s += "</body></html>\n";
server.send(200, "text/html", s);
}
void wifiFailed() {
// Ampel will go back to Access Point mode for AP_TIMEOUT seconds, and try connection again after
Serial.print(F("WiFi - Could not connect to "));
Serial.println(config::selected_ssid());
led_effects::showKITTWheel(color::red);
void configSaved()
{
Serial.println("Configuration was updated.");
}
/*****************************************************************
* Helper functions *
*****************************************************************/
bool formValidator(iotwebconf::WebRequestWrapper* webRequestWrapper)
{
Serial.println("Validating form.");
bool valid = true;
/**
* Checks if flash button has been pressed:
* If not, do nothing.
* If short press, toggle LED display.
* If long press, start calibration process.
*/
void checkFlashButton() {
if (!digitalRead(0)) { // Button has been pressed
led_effects::onBoardLEDOn();
delay(300);
if (digitalRead(0)) {
Serial.println(F("Flash has been pressed for a short time. Should toggle night mode."));
led_effects::toggleNightMode();
//NOTE: Start Access Point instead?
} else {
Serial.println(F("Flash has been pressed for a long time. Keep it pressed for calibration."));
if (led_effects::countdownToZero()) {
Serial.println(F("You can now release the button."));
sensor::startCalibrationProcess();
led_effects::showKITTWheel(color::red, 2);
}
}
led_effects::onBoardLEDOff();
/*
int l = webRequestWrapper->arg(stringParam.getId()).length();
if (l < 3)
{
stringParam.errorMessage = "Please provide at least 3 characters for this test!";
valid = false;
}
*/
return valid;
}
void keepServicesAlive() {
if (config::is_wifi_on) {
web_config::update();
if (wifi::connected()) {
ntp::update(); // NTP client has its own timer. It will connect to NTP server every 60s.
if (config::is_mqtt_active()) {
mqtt::keepConnection(); // MQTT client has its own timer. It will keep alive every 15s.
}
}
}
}
#include "sensor_console.h"
namespace sensor_console {
const uint8_t MAX_COMMANDS = 26;
const uint8_t MAX_COMMAND_SIZE = 40;
uint8_t commands_count = 0;
enum input_type {
NONE,
INT32,
STRING
};
struct Command {
const char *name;
union {
void (*voidFunction)();
void (*intFunction)(int32_t);
void (*strFunction)(char*);
};
const char *doc;
input_type parameter_type;
};
struct CommandLine {
char function_name[MAX_COMMAND_SIZE];
input_type argument_type;
int32_t int_argument;
char str_argument[MAX_COMMAND_SIZE];
};
Command commands[MAX_COMMANDS];
bool addCommand(const char *name, const __FlashStringHelper *doc_fstring) {
if (commands_count < MAX_COMMANDS) {
commands[commands_count].name = name;
commands[commands_count].doc = (const char*) doc_fstring;
return true;
} else {
Serial.println(F("Too many commands have been defined."));
return false;
}
}
void defineCommand(const char *name, void (*function)(), const __FlashStringHelper *doc_fstring) {
if (addCommand(name, doc_fstring)) {
commands[commands_count].voidFunction = function;
commands[commands_count++].parameter_type = NONE;
}
}
void defineIntCommand(const char *name, void (*function)(int32_t), const __FlashStringHelper *doc_fstring) {
if (addCommand(name, doc_fstring)) {
commands[commands_count].intFunction = function;
commands[commands_count++].parameter_type = INT32;
}
}
void defineStringCommand(const char *name, void (*function)(char*), const __FlashStringHelper *doc_fstring) {
if (addCommand(name, doc_fstring)) {
commands[commands_count].strFunction = function;
commands[commands_count++].parameter_type = STRING;
}
}
/*
* Tries to split a string command (e.g. 'mqtt 60' or 'show_csv') into
* a CommandLine struct (function_name, argument_type and argument)
*/
void parseCommand(const char *command, CommandLine &command_line) {
if (strlen(command) == 0) {
Serial.println(F("Received empty command"));
command_line.argument_type = NONE;
return;
}
char *first_space;
first_space = strchr(command, ' ');
if (first_space == NULL) {
command_line.argument_type = NONE;
strlcpy(command_line.function_name, command, MAX_COMMAND_SIZE);
return;
}
strlcpy(command_line.function_name, command, first_space - command + 1);
strlcpy(command_line.str_argument, first_space + 1, MAX_COMMAND_SIZE - (first_space - command) - 1);
char *end;
command_line.int_argument = strtol(command_line.str_argument, &end, 0); // Accepts 123 or 0xFF00FF
if (*end) {
command_line.argument_type = STRING;
} else {
command_line.argument_type = INT32;
}
}
int compareCommandNames(const void *s1, const void *s2) {
struct Command *c1 = (struct Command*) s1;
struct Command *c2 = (struct Command*) s2;
return strcmp(c1->name, c2->name);
}
void listAvailableCommands() {
qsort(commands, commands_count, sizeof(commands[0]), compareCommandNames);
for (uint8_t i = 0; i < commands_count; i++) {
Serial.print(F(" "));
Serial.print(commands[i].name);
Serial.print(F(" "));
Serial.print(commands[i].doc);
Serial.println(F("."));
}
}
/*
* Saves bytes from Serial.read() until enter is pressed, and tries to run the corresponding command.
* http://www.gammon.com.au/serial
*/
void processSerialInput(const byte input_byte) {
static char input_line[MAX_COMMAND_SIZE];
static unsigned int input_pos = 0;
switch (input_byte) {
case '\n': // end of text
Serial.println();
input_line[input_pos] = 0;
execute(input_line);
input_pos = 0;
break;
case '\r': // discard carriage return
break;
case '\b': // backspace
if (input_pos > 0) {
input_pos--;
Serial.print(F("\b \b"));
}
break;
default:
if (input_pos == 0) {
Serial.print(F("> "));
}
// keep adding if not full ... allow for terminating null byte
if (input_pos < (MAX_COMMAND_SIZE - 1)) {
input_line[input_pos++] = input_byte;
Serial.print((char) input_byte);
}
break;
}
}
void checkSerialInput() {
while (Serial.available() > 0) {
sensor_console::processSerialInput(Serial.read());
}
}
/*
* Tries to find the corresponding callback for a given command. Name and parameter type should fit.
*/
void execute(const char *command_str) {
CommandLine input;
parseCommand(command_str, input);
for (uint8_t i = 0; i < commands_count; i++) {
if (!strcmp(input.function_name, commands[i].name) && input.argument_type == commands[i].parameter_type) {
Serial.print(F("Calling : "));
Serial.print(input.function_name);
switch (input.argument_type) {
case NONE:
Serial.println(F("()"));
commands[i].voidFunction();
return;
case INT32:
Serial.print(F("("));
Serial.print(input.int_argument);
Serial.println(F(")"));
commands[i].intFunction(input.int_argument);
return;
case STRING:
Serial.print(F("('"));
Serial.print(input.str_argument);
Serial.println(F("')"));
commands[i].strFunction(input.str_argument);
return;
}
}
}
Serial.print(F("'"));
Serial.print(command_str);
Serial.println(F("' not supported. Available commands :"));
listAvailableCommands();
}
}
#ifndef SENSOR_CONSOLE_H_INCLUDED
#define SENSOR_CONSOLE_H_INCLUDED
#include <Arduino.h> // For Flash strings, uint8_t and int32_t
/** Other scripts can use this namespace, in order to define commands, via callbacks.
* Those callbacks can then be used to send commands to the sensor (reset, calibrate, led on/off, ...)
* The callbacks can either have no parameter, or one int32_t parameter.
*/
namespace sensor_console {
void defineCommand(const char *name, void (*function)(), const __FlashStringHelper *doc_fstring);
void defineIntCommand(const char *name, void (*function)(int32_t), const __FlashStringHelper *doc_fstring);
void defineStringCommand(const char *name, void (*function)(char*), const __FlashStringHelper *doc_fstring);
void checkSerialInput();
void execute(const char *command_line);
}
#endif
#include "util.h"
#include "sensor_console.h"
#if defined(ESP8266)
# include <ESP8266WiFi.h> // required to get MAC address
const char *current_board = "ESP8266";
#elif defined(ESP32)
# include <WiFi.h> // required to get MAC address
const char *current_board = "ESP32";
#else
const char *current_board = "UNKNOWN";
#endif
void Ampel::showFreeSpace() {
Serial.print(F("Free heap space : "));
Serial.print(ESP.getFreeHeap());
Serial.println(F(" bytes."));
Serial.print(F("Max free block size : "));
Serial.print(esp_get_max_free_block_size());
Serial.println(F(" bytes."));
Serial.print(F("Heap fragmentation : "));
Serial.print(esp_get_heap_fragmentation());
Serial.println(F(" %"));
}
char sensorId[10]; // e.g "ESPxxxxxx\0"
char macAddress[18]; // e.g "XX:XX:XX:XX:XX:XX\0"
uint8_t mac[6];
char* getMacString() {
WiFi.macAddress(mac);
// Get all 6 bytes of ESP MAC
snprintf(macAddress, sizeof(macAddress), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4],
mac[5]);
return macAddress;
}
char* getSensorId() {
WiFi.macAddress(mac);
// Get last 3 bytes of ESP MAC (worldwide unique)
snprintf(sensorId, sizeof(sensorId), "ESP%02x%02x%02x", mac[3], mac[4], mac[5]);
return sensorId;
}
Ampel::Ampel() :
board(current_board), sensorId(getSensorId()), macAddress(getMacString()), max_loop_duration(0) {
sensor_console::defineCommand("free", Ampel::showFreeSpace, F("(Displays available heap space)"));
sensor_console::defineCommand("reset", []() {
ESP.restart();
}, F("(Restarts the ESP)"));
}
Ampel ampel;
#ifndef AMPEL_UTIL_H_INCLUDED
#define AMPEL_UTIL_H_INCLUDED
#include <stdint.h> // For uint32_t
#if defined(ESP8266)
# define esp_get_max_free_block_size() ESP.getMaxFreeBlockSize()
# define esp_get_heap_fragmentation() ESP.getHeapFragmentation()
#elif defined(ESP32)
# define esp_get_max_free_block_size() ESP.getMaxAllocHeap() //largest block of heap that can be allocated.
# define esp_get_heap_fragmentation() -1 // apparently not available for ESP32
#endif
namespace util {
template<typename Tpa, typename Tpb>
inline auto min(const Tpa &a, const Tpb &b) -> decltype(a < b ? a : b) {
return b < a ? b : a;
}
template<typename Tpa, typename Tpb>
inline auto max(const Tpa &a, const Tpb &b) -> decltype(b > a ? b : a) {
return b > a ? b : a;
}
}
class Ampel {
private:
static void showFreeSpace();
public:
const char *version = "webconf-DEV"; // Update manually after significant changes.
const char *board;
const char *sensorId;
const char *macAddress;
uint32_t max_loop_duration;
Ampel();
};
extern Ampel ampel;
#endif
#include "web_config.h"
#define STRING_LEN 33 // Should be enough for ip, addresses, passwords...
#define STRING(x) #x
#define STRINGIFY(x) STRING(x) // Allows to call STRINGIFY(SOME_MACRO) and convert the value of SOME_MACRO to string
#include "config.h"
#ifndef MQTT_TOPIC_PREFIX
# error Missing config.h file. Please copy config.public.h to config.h.
# warning config.h has a new structure (e.g. AMPEL_WIFI should be set to true or false)
#endif
#include "util.h"
#include "sensor_console.h"
#include "src/lib/IotWebConf/src/IotWebConf.h"
#include "src/lib/IotWebConf/src/IotWebConfTParameter.h"
#include "src/lib/IotWebConf/src/IotWebConfOptionalGroup.h"
//TODO: Convert all strings to F-strings
//TODO: Add callbacks for states, e.g. AP mode or connecting?
namespace web_config {
#if defined(ESP8266)
ESP8266WebServer http(80); // Create a webserver object that listens for HTTP request on port 80
#elif defined(ESP32)
WebServer http(80);
#endif
DNSServer dnsServer; //TODO: Check if needed
IotWebConf *iotWebConf;
const char config_version[IOTWEBCONF_CONFIG_VERSION_LENGTH] = AMPEL_CONFIG_VERSION; // -- Configuration specific key. The value should be modified if config structure was changed.
using namespace iotwebconf;
/**
* WiFi params
*/
CheckboxTParameter ampelWifiParam =
Builder<CheckboxTParameter>("WiFi").label("WiFi?").defaultValue(AMPEL_WIFI).build();
IntTParameter<uint16_t> wifiTimeoutParam =
Builder<IntTParameter<uint16_t>>("wifi_timeout").label("WiFi timeout").defaultValue(WIFI_TIMEOUT).min(0).placeholder(
"[s]").build();
//TODO: Distribute to corresponding classes, possibly with callbacks?
/**
* CO2 sensor
*/
ParameterGroup co2Params = ParameterGroup("co2", "CO2 Sensor");
IntTParameter<uint16_t> timestepParam =
Builder<IntTParameter<uint16_t>>("timestep").label("Measurement timestep").defaultValue(MEASUREMENT_TIMESTEP).min(
2).max(1800).placeholder("[s]").build();
FloatTParameter temperatureOffsetParam =
Builder<FloatTParameter>("temp_offset").label("Temperature offset").defaultValue(TEMPERATURE_OFFSET).placeholder(
"[K]").step(0.1).build();
IntTParameter<uint16_t> altitudeParam = Builder<IntTParameter<uint16_t>>("altitude").label("Altitude").defaultValue(
ALTITUDE_ABOVE_SEA_LEVEL).min(0).step(1).placeholder("[m]").build();
IntTParameter<uint16_t> atmosphericCO2Param = Builder<IntTParameter<uint16_t>>("atmospheric_co2").label(
"Atmospheric CO2 concentration").defaultValue(
ATMOSPHERIC_CO2_CONCENTRATION).min(400).max(2000).step(1).placeholder("ppm").build();
CheckboxTParameter autoCalibrateParam = Builder<CheckboxTParameter>("asc").label("Auto-calibration?").defaultValue(
AUTO_CALIBRATE_SENSOR).build();
/**
* LED
*/
//NOTE: Could also be optional : for LED 0 / 1
ParameterGroup ledParams = ParameterGroup("LED", "LED");
IntTParameter<uint8_t> maxBrightnessParam =
Builder<IntTParameter<uint8_t>>("max_brightness").label("Max Brightness").defaultValue(MAX_BRIGHTNESS).min(0).max(
255).build();
IntTParameter<uint8_t> minBrightnessParam =
Builder<IntTParameter<uint8_t>>("min_brightness").label("Min Brightness").defaultValue(MIN_BRIGHTNESS).min(0).max(
255).build();
IntTParameter<uint16_t> ledCountParam = Builder<IntTParameter<uint16_t>>("led_count").label("LED ring").defaultValue(
LED_COUNT).min(12).max(16).step(4).build();
/**
* CSV
*/
OptionalParameterGroup csvParams = OptionalParameterGroup("CSV", "CSV", AMPEL_CSV);
IntTParameter<uint16_t> csvIntervalParam =
Builder<IntTParameter<uint16_t>>("csv_interval").label("CSV interval").defaultValue(CSV_INTERVAL).min(0).step(1).placeholder(
"[s]").build();
/**
* MQTT
*/
OptionalParameterGroup mqttParams = OptionalParameterGroup("MQTT", "MQTT", AMPEL_MQTT);
IntTParameter<uint16_t> mqttIntervalParam =
Builder<IntTParameter<uint16_t>>("mqtt_interval").label("MQTT interval").defaultValue(MQTT_SENDING_INTERVAL).min(
0).step(1).defaultValue(300).placeholder("[s]").build();
CheckboxTParameter mqttEncryptionParam =
Builder<CheckboxTParameter>("mqtt_encryption").label("Encrypt MQTT?").defaultValue(MQTT_ENCRYPTED).build();
CheckboxTParameter mqttCommandsParam =
Builder<CheckboxTParameter>("mqtt_commands").label("Allow MQTT commands?").defaultValue(ALLOW_MQTT_COMMANDS).build();
TextTParameter<STRING_LEN> mqttServerParam =
Builder<TextTParameter<STRING_LEN>>("mqtt_server").label("MQTT Server").defaultValue(MQTT_SERVER).build();
IntTParameter<uint16_t> mqttPortParam = Builder<IntTParameter<uint16_t>>("mqtt_port").label("MQTT Port").defaultValue(
MQTT_PORT).build();
TextTParameter<STRING_LEN> mqttUserParam =
Builder<TextTParameter<STRING_LEN>>("mqtt_user").label("MQTT User").defaultValue(MQTT_USER).build();
PasswordTParameter<STRING_LEN> mqttPasswordParam = Builder<PasswordTParameter<STRING_LEN>>("mqtt_password").label(
"MQTT password").defaultValue(MQTT_PASSWORD).build();
TextTParameter<STRING_LEN> mqttTopicPrefixParam =
Builder<TextTParameter<STRING_LEN>>("mqtt_topic").label("MQTT Topic prefix").defaultValue(MQTT_TOPIC_PREFIX).build();
/**
* NTP Time
*/
ParameterGroup timeParams = ParameterGroup("NTP", "Time server");
TextTParameter<STRING_LEN> ntpServerParam =
Builder<TextTParameter<STRING_LEN>>("ntp_server").label("NTP Server").defaultValue(NTP_SERVER).build();
IntTParameter<int16_t> timeOffsetParam = Builder<IntTParameter<int16_t>>("timezone").label("Timezone").defaultValue(
UTC_OFFSET).min(-23).max(23).step(1).placeholder("[h]").build();
CheckboxTParameter dstParam = Builder<CheckboxTParameter>("dst").label("Daylight Saving Time?").defaultValue(
DAYLIGHT_SAVING_TIME).build();
/**
* LoRaWAN
*/
#if defined(ESP32)
OptionalParameterGroup loraParams = OptionalParameterGroup("LoRaWan", "LoRaWan", AMPEL_LORAWAN);
IntTParameter<uint16_t> loraIntervalParam =
Builder<IntTParameter<uint16_t>>("lora_interval").label("LoRa interval").defaultValue(LORAWAN_SENDING_INTERVAL).min(
0).step(1).defaultValue(300).placeholder("[s]").build();
TextTParameter<17> deviceEUIParam = Builder<TextTParameter<17>>("device_eui").label("Device EUI (MSB)").defaultValue(
LORAWAN_DEVICE_EUI).build();
TextTParameter<17> appEUIParam = Builder<TextTParameter<17>>("app_eui").label("App EUI (MSB)").defaultValue(
LORAWAN_APPLICATION_EUI).build();
PasswordTParameter<33> appKeyParam = Builder<PasswordTParameter<33>>("app_key").label("App key (MSB)").defaultValue(
LORAWAN_APPLICATION_KEY).build();
#endif
OptionalGroupHtmlFormatProvider optionalGroupHtmlFormatProvider;
void update() {
iotWebConf->doLoop(); // Listen for HTTP requests from clients
}
void setWifiConnectionCallback(void (*success_function)()) {
iotWebConf->setWifiConnectionCallback(success_function);
}
void setWifiFailCallback(void (*fail_function)()) {
std::function<WifiAuthInfo* ()> fail_and_return_null = [fail_function]() {
fail_function();
return nullptr;
};
iotWebConf->setWifiConnectionFailedHandler(fail_and_return_null);
}
void defineStructure() {
iotWebConf->setHtmlFormatProvider(&optionalGroupHtmlFormatProvider);
iotWebConf->addSystemParameter(&ampelWifiParam);
// Somehow, making ampelWifi invisible set it to 0 :-/
// ampelWifiParam.visible = false; // To avoid users getting locked out. In order to set WiFi on/off, console commands can be used.
iotWebConf->addSystemParameter(&wifiTimeoutParam);
iotWebConf->getThingNameParameter()->label = "Ampel name";
iotWebConf->getApPasswordParameter()->label = "Ampel password (user : 'admin')";
iotWebConf->getApTimeoutParameter()->label = "Access Point timeout";
iotWebConf->getApTimeoutParameter()->visible = true;
iotWebConf->getApPasswordParameter()->defaultValue = AMPEL_PASSWORD;
iotWebConf->getWifiSsidParameter()->defaultValue = WIFI_SSID;
iotWebConf->getWifiPasswordParameter()->defaultValue = WIFI_PASSWORD;
iotWebConf->getApTimeoutParameter()->defaultValue = STRINGIFY(ACCESS_POINT_TIMEOUT); // Defined as number in config.h but stored as string in webconf.
co2Params.addItem(&timestepParam);
co2Params.addItem(&temperatureOffsetParam);
co2Params.addItem(&altitudeParam);
co2Params.addItem(&atmosphericCO2Param);
co2Params.addItem(&autoCalibrateParam);
ledParams.addItem(&minBrightnessParam);
ledParams.addItem(&maxBrightnessParam);
ledParams.addItem(&ledCountParam);
timeParams.addItem(&ntpServerParam);
timeParams.addItem(&timeOffsetParam);
timeParams.addItem(&dstParam);
csvParams.addItem(&csvIntervalParam);
mqttParams.addItem(&mqttIntervalParam);
mqttParams.addItem(&mqttServerParam);
mqttParams.addItem(&mqttPortParam);
mqttParams.addItem(&mqttUserParam);
mqttParams.addItem(&mqttPasswordParam);
mqttParams.addItem(&mqttTopicPrefixParam);
mqttParams.addItem(&mqttEncryptionParam);
mqttParams.addItem(&mqttCommandsParam);
#if defined(ESP32)
loraParams.addItem(&loraIntervalParam);
loraParams.addItem(&deviceEUIParam);
loraParams.addItem(&appEUIParam);
loraParams.addItem(&appKeyParam);
#endif
iotWebConf->addParameterGroup(&co2Params);
iotWebConf->addParameterGroup(&ledParams);
iotWebConf->addParameterGroup(&timeParams);
iotWebConf->addParameterGroup(&csvParams);
iotWebConf->addParameterGroup(&mqttParams);
#if defined(ESP32)
iotWebConf->addParameterGroup(&loraParams);
#endif
}
void defineCommands() {
sensor_console::defineCommand("save_config", config::save, F("(Saves the config to EEPROM)"));
sensor_console::defineCommand("reset_config", []() {
Serial.println(F("Resetting config..."));
iotWebConf->getRootParameterGroup()->applyDefaultValue();
iotWebConf->saveConfig();
Serial.println(F("Done!"));
}, F("(Resets the complete IotWeb config)"));
sensor_console::defineStringCommand("ssid", [](char *ssid) {
Serial.print(F("Setting WiFi ssid to "));
Serial.println(ssid);
strlcpy(iotWebConf->getWifiSsidParameter()->valueBuffer, ssid, iotWebConf->getWifiSsidParameter()->getLength());
iotWebConf->saveConfig();
}, F("name (Sets SSID to name)"));
sensor_console::defineStringCommand("pwd", [](char *ssid) {
Serial.print(F("Setting WiFi password to "));
Serial.println(ssid);
strlcpy(iotWebConf->getWifiPasswordParameter()->valueBuffer, ssid, iotWebConf->getWifiPasswordParameter()->getLength());
iotWebConf->saveConfig();
}, F("abc (Sets WiFi password to abc)"));
sensor_console::defineIntCommand("wifi", [](int32_t onOff) {
config::is_wifi_on = onOff;
iotWebConf->saveConfig();
Serial.print(F("WiFi - "));
Serial.println(onOff ? F("On!") : F("Off!"));
}, F("0/1 (Turns Wifi on/off)"));
}
void initialize() {
iotWebConf = new IotWebConf(strlen(AMPEL_NAME) == 0 ? ampel.sensorId : AMPEL_NAME, &dnsServer, &http, "",
config_version);
defineStructure();
defineCommands();
iotWebConf->loadConfig();
if (!config::is_wifi_on) {
Serial.println(F("Wifi is off : no access point or connection. Use 'wifi 1' to turn on again!"));
return;
}
//NOTE: Can only work if wifi is on.
sensor_console::defineIntCommand("ap", [](int onOff) {
//TODO: Add On-board LED effects to show the changes?
if (onOff) {
Serial.print(F("Enable "));
} else {
Serial.print(F("Disable "));
}
Serial.println(F("AP mode!"));
iotWebConf->forceApMode(onOff);
}, F("0/1 (Enables/disables access point)"));
iotWebConf->setWifiConnectionTimeoutMs(1000UL * config::wifi_timeout);
iotWebConf->skipApStartup();
iotWebConf->init();
http.on("/config", [] {
iotWebConf->handleConfig();
});
http.onNotFound([]() {
iotWebConf->handleNotFound();
});
}
}
/***
* Define all the corresponding config values as reference, so that they can be updated.
*/
namespace config {
char* ampel_name() {
return web_config::iotWebConf->getThingName();
}
char* selected_ssid() {
return web_config::iotWebConf->getWifiSsidParameter()->valueBuffer;
}
char* ampel_password() {
return web_config::iotWebConf->getApPasswordParameter()->valueBuffer;
}
// Sensor
uint16_t &measurement_timestep = web_config::timestepParam.value(); // [s] Value between 2 and 1800 (range for SCD30 sensor).
uint16_t &altitude_above_sea_level = web_config::altitudeParam.value(); // [m]
uint16_t &co2_calibration_level = web_config::atmosphericCO2Param.value(); // [ppm]
bool &auto_calibrate_sensor = web_config::autoCalibrateParam.value(); // [true / false]
float &temperature_offset = web_config::temperatureOffsetParam.value(); // [K] Sign isn't relevant.
bool &is_wifi_on = web_config::ampelWifiParam.value();
uint16_t &wifi_timeout = web_config::wifiTimeoutParam.value();
void save() {
web_config::iotWebConf->saveConfig();
}
// LEDs
uint8_t &max_brightness = web_config::maxBrightnessParam.value();
uint8_t &min_brightness = web_config::minBrightnessParam.value();
uint16_t &led_count = web_config::ledCountParam.value();
// Time server
char *ntp_server = web_config::ntpServerParam.value();
int16_t &time_zone = web_config::timeOffsetParam.value();
bool &daylight_saving_time = web_config::dstParam.value();
// CSV
bool is_csv_active() {
return web_config::csvParams.isActive();
}
uint16_t &csv_interval = web_config::csvIntervalParam.value();
// MQTT
bool is_mqtt_active() {
return web_config::mqttParams.isActive();
}
char *mqtt_server = web_config::mqttServerParam.value();
char *mqtt_user = web_config::mqttUserParam.value();
char *mqtt_password = web_config::mqttPasswordParam.value();
char *mqtt_topic_prefix = web_config::mqttTopicPrefixParam.value();
uint16_t &mqtt_port = web_config::mqttPortParam.value();
uint16_t &mqtt_sending_interval = web_config::mqttIntervalParam.value();
bool &mqtt_encryption = web_config::mqttEncryptionParam.value();
bool &allow_mqtt_commands = web_config::mqttCommandsParam.value();
// LORAWAN
#if defined(ESP32)
bool is_lorawan_active() {
return web_config::loraParams.isActive();
}
uint16_t &lorawan_sending_interval = web_config::loraIntervalParam.value();
char *lorawan_device_eui = web_config::deviceEUIParam.value();
char *lorawan_app_key = web_config::appKeyParam.value();
char *lorawan_app_eui = web_config::appEUIParam.value();
#endif
}
#ifndef AMPEL_WEB_CONFIG_H_
#define AMPEL_WEB_CONFIG_H_
#if defined(ESP8266)
# include <ESP8266WebServer.h>
#elif defined(ESP32)
# include <WebServer.h>
#endif
namespace config {
void save(); // Save config to EEPROM
char* ampel_name();
// WiFi
char* selected_ssid();
char* ampel_password(); // For Access Point, and for HTML page
extern bool &is_wifi_on; // [true / false]
extern uint16_t &wifi_timeout; // [s]
// Sensor
extern uint16_t &measurement_timestep; // [s] Value between 2 and 1800 (range for SCD30 sensor).
extern uint16_t &altitude_above_sea_level; // [m]
extern uint16_t &co2_calibration_level; // [ppm]
extern bool &auto_calibrate_sensor; // [true / false]
extern float &temperature_offset; // [K] Sign isn't relevant.
// LED
extern uint8_t &max_brightness;
extern uint8_t &min_brightness;
extern uint16_t &led_count;
// Time server
extern char *ntp_server;
extern int16_t &time_zone; // [h]
extern bool &daylight_saving_time; // [true / false]
//CSV
bool is_csv_active(); // [true / false]
extern uint16_t &csv_interval; // [s]
// MQTT
bool is_mqtt_active(); // [true / false]
extern char *mqtt_server;
extern char *mqtt_user;
extern char *mqtt_password;
extern char *mqtt_topic_prefix;
extern uint16_t &mqtt_port;
extern uint16_t &mqtt_sending_interval; // [s]
extern bool &mqtt_encryption; // [true / false]
extern bool &allow_mqtt_commands; // [true / false]
// HTTP
const char http_user[] = "admin"; // "admin" by default
// LORAWAN
#if defined(ESP32)
bool is_lorawan_active(); // also defined for ESP8266, and set to false
extern uint16_t &lorawan_sending_interval;
extern char *lorawan_device_eui;
extern char *lorawan_app_key;
extern char *lorawan_app_eui;
#endif
// Transmission rate
constexpr uint32_t bauds = 115200;
}
namespace web_config {
void initialize();
void setWifiConnectionCallback(void (*success_function)());
void setWifiFailCallback(void (*fail_function)());
void update();
#if defined(ESP8266)
extern ESP8266WebServer http;
#elif defined(ESP32)
extern WebServer http;
#endif
}
#endif
......@@ -16,6 +16,8 @@ platform = espressif8266
board = esp12e
framework = arduino
monitor_speed = 115200
lib_deps =
IoTWebConf
[env:esp32]
platform = espressif32
......@@ -23,8 +25,4 @@ board = ttgo-lora32-v1
framework = arduino
monitor_speed = 115200
lib_deps =
MCCI LoRaWAN LMIC library
build_flags =
-D ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS
-D CFG_eu868=1
-D CFG_sx1276_radio=1
IoTWebConf
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment