Commit ce87c556 authored by Eric Duminil's avatar Eric Duminil
Browse files

Merge. Ready for release

parents 1815167d b8a71725
...@@ -10,3 +10,8 @@ before_script: ...@@ -10,3 +10,8 @@ before_script:
job: job:
stage: build stage: build
script: "platformio run" script: "platformio run"
artifacts:
paths:
- .pio/build/esp32/firmware.bin
- .pio/build/esp8266/firmware.bin
expire_in: never
...@@ -16,6 +16,7 @@ The *CO<sub>2</sub> Ampel* can: ...@@ -16,6 +16,7 @@ The *CO<sub>2</sub> Ampel* can:
* Send data over [MQTT](https://en.wikipedia.org/wiki/MQTT). * Send data over [MQTT](https://en.wikipedia.org/wiki/MQTT).
* Send data over [LoRaWAN](https://en.wikipedia.org/wiki/LoRa#LoRaWAN). * Send data over [LoRaWAN](https://en.wikipedia.org/wiki/LoRa#LoRaWAN).
* Display measurements and configuration on a small website. * Display measurements and configuration on a small website.
* Allow configuration of every parameter via web-interface.
* Log data to a [CSV](https://en.wikipedia.org/wiki/Comma-separated_values) file, directly on the ESP flash memory. * Log data to a [CSV](https://en.wikipedia.org/wiki/Comma-separated_values) file, directly on the ESP flash memory.
* Accept many interactive commands. * Accept many interactive commands.
...@@ -36,7 +37,8 @@ or ...@@ -36,7 +37,8 @@ or
## Installation ## Installation
* If `config.h` does not exist, copy it from `config.public.h` * If `config.h` does not exist, copy it from `config.public.h`
* Modify `config.h`, e.g. for measurement time-steps, WiFi access, MQTT, NTP and web-server. * You can modify `config.h`, e.g. for measurement time-steps, WiFi access, MQTT, NTP and web-server.
* Every parameter can be modified later, via the web-interface.
### PlatformIO ### PlatformIO
...@@ -68,15 +70,44 @@ make upload board=esp32 && make monitor # For ESP32 ...@@ -68,15 +70,44 @@ make upload board=esp32 && make monitor # For ESP32
* *Upload* * *Upload*
* *Tools > Serial Monitor* * *Tools > Serial Monitor*
## Usage and configuration
* Start the *Ampel*
* If you didn't define wifi SSID and Passwords in config.h
* The *Ampel* should blink turquoise, to indicate *Access Point* mode.
* Connect with a laptop/smartphone to ESPxxxxxx WiFi.
* Open a browser, it should bring you to the *Ampel* web-page.
* The *Ampel* web-page will be shown. You can click on "Configuration".
* You'll have to set an *Ampel* password with at least 8 characters.
* You can set WiFi SSID + Password if you want. It will then try to connect if you disconnect from the *Access Point*.
* If you don't specify SSID + Password, the *Ampel* will simply stay in *Access Point* mode.
* The *Ampel* password will be required if a connection fails, and the *Access Point* starts again.
* The *Ampel* password will be needed (with user 'admin') to connect to the config web-page once the *Ampel* is connected to WiFi.
* Once you defined Wifi SSID, Ampel password and WiFi password:
* the *Ampel* will try to connect, displaying a rainbow wheel.
* if the connection is successful, LEDs will be green.
* if the connection fails, LEDs will be red, and the *Ampel* will return back to *Access Point* mode.
* It might be necessary to reset the *Ampel* after changing important parameters.
* You can rename the *Ampel*. This name will be used instead of ESPxxxxxx for CSV files and the mDNS address. You'll get a new CSV file after renaming, which can be convenient, e.g. to indicate at which location the measurements were made.
* If you forgot the password, you can send `reset_config` as command, and `reset` the ampel.
* If you disabled WiFi, you can enable it again with `wifi 1`.
* It's also possible to specify WiFi SSID and password in the console, with `ssid wifi_ssid_here` and `pwd wifi_password_here` commands.
* You can enable CSV by clicking on "+CSV", or disable it by clicking on "Disable CSV".
* The same applies to MQTT and LoRaWAN.
* If you disable a set, the parameters are still saved, and will appear again once the service is enabled.
## Available commands ## Available commands
In Arduino IDE *Serial Monitor* or PlatformIO *Monitor*, type `help` + <kbd>Enter</kbd> in order to list the available commands: In Arduino IDE *Serial Monitor* or PlatformIO *Monitor*, type `help` + <kbd>Enter</kbd> in order to list the available commands:
* `ap 0/1` (Disables/enables access point).
* `auto_calibrate 0/1` (Disables/enables autocalibration). * `auto_calibrate 0/1` (Disables/enables autocalibration).
* `calibrate` (Starts calibration process).
* `calibrate 600` (Starts calibration process, to given ppm). * `calibrate 600` (Starts calibration process, to given ppm).
* `calibrate` (Starts calibration process).
* `calibrate! 600` (Calibrates right now, to given ppm). * `calibrate! 600` (Calibrates right now, to given ppm).
* `co2 1500` (Sets CO<sub>2</sub> level, for debugging purposes). * `co2 1500` (Sets CO<sub>2</sub> level, for debugging).
* `color 0xFF0015` (Shows color, specified as RGB, for debugging). * `color 0xFF0015` (Shows color, specified as RGB, for debugging).
* `csv 60` (Sets CSV writing interval, in s). * `csv 60` (Sets CSV writing interval, in s).
* `format_filesystem` (Deletes the whole filesystem). * `format_filesystem` (Deletes the whole filesystem).
...@@ -85,12 +116,17 @@ In Arduino IDE *Serial Monitor* or PlatformIO *Monitor*, type `help` + <kbd>Ente ...@@ -85,12 +116,17 @@ In Arduino IDE *Serial Monitor* or PlatformIO *Monitor*, type `help` + <kbd>Ente
* `local_ip` (Displays local IP and current SSID). * `local_ip` (Displays local IP and current SSID).
* `lora 300` (Sets LoRaWAN sending interval, in s). * `lora 300` (Sets LoRaWAN sending interval, in s).
* `mqtt 60` (Sets MQTT sending interval, in s). * `mqtt 60` (Sets MQTT sending interval, in s).
* `pwd abc` (Sets WiFi password to 'abc').
* `reset` (Restarts the ESP). * `reset` (Restarts the ESP).
* `reset_config` (Resets the complete IotWeb config).
* `reset_scd` (Resets SCD30). * `reset_scd` (Resets SCD30).
* `save_config` (Saves the config to EEPROM).
* `send_local_ip` (Sends local IP and SSID via MQTT. Can be useful to find sensor). * `send_local_ip` (Sends local IP and SSID via MQTT. Can be useful to find sensor).
* `set_time 1618829570` (Sets time to the given UNIX time). * `set_time 1618829570` (Sets time to the given UNIX time).
* `show_csv` (Displays the complete CSV file on Serial). * `show_csv` (Displays the complete CSV file on Serial).
* `ssid name` (Sets SSID to 'name').
* `timer 30` (Sets measurement interval, in s). * `timer 30` (Sets measurement interval, in s).
* `wifi 0/1` (Turns Wifi on/off).
* `wifi_scan` (Scans available WiFi networks). * `wifi_scan` (Scans available WiFi networks).
The commands can be sent via the Serial interface, from the webpage or via MQTT. The commands can be sent via the Serial interface, from the webpage or via MQTT.
......
...@@ -3,34 +3,17 @@ ...@@ -3,34 +3,17 @@
/***************************************************************** /*****************************************************************
* Libraries * * Libraries *
*****************************************************************/ *****************************************************************/
#include "config.h"
#ifndef MEASUREMENT_TIMESTEP
# error Missing config.h file. Please copy config.public.h to config.h.
#endif
#ifdef AMPEL_CSV //NOTE: Too many headers. Move them to include/ folder?
# include "csv_writer.h" #include "web_config.h" // Needed for offline config too.
#endif
#ifdef AMPEL_WIFI #include "csv_writer.h"
# include "wifi_util.h"
# ifdef AMPEL_MQTT
# include "mqtt.h"
# endif
# ifdef AMPEL_HTTP
# include "web_server.h"
# endif
# if defined(ESP8266)
//allows sensor to be seen as SENSOR_ID.local, from the local network. For example : espd03cc5.local
# include <ESP8266mDNS.h>
# elif defined(ESP32)
# include <ESPmDNS.h>
# endif
#endif
#ifdef AMPEL_LORAWAN #include "wifi_util.h"
# include "lorawan.h" #include "mqtt.h"
#endif #include "web_server.h"
#include "lorawan.h"
#include "util.h" #include "util.h"
#include "ntp.h" #include "ntp.h"
...@@ -38,4 +21,12 @@ ...@@ -38,4 +21,12 @@
#include "co2_sensor.h" #include "co2_sensor.h"
#include "led_effects.h" #include "led_effects.h"
void wifiConnecting();
void wifiConnected();
void wifiFailed();
void apModeStarts();
void keepServicesAlive();
void checkFlashButton();
#endif #endif
...@@ -63,13 +63,22 @@ void setup() { ...@@ -63,13 +63,22 @@ void setup() {
led_effects::setupOnBoardLED(); led_effects::setupOnBoardLED();
led_effects::onBoardLEDOff(); led_effects::onBoardLEDOff();
Serial.begin(BAUDS); Serial.begin(config::bauds);
web_config::initialize();
web_config::setWifiConnectingCallback(wifiConnecting);
web_config::setWifiConnectionCallback(wifiConnected);
web_config::setWifiFailCallback(wifiFailed);
web_config::setApModeCallback(apModeStarts);
pinMode(0, INPUT); // Flash button (used for forced calibration) pinMode(0, INPUT); // Flash button (used for forced calibration)
Serial.println(); Serial.println();
Serial.print(F("Sensor ID: ")); Serial.print(F("Sensor ID: "));
Serial.println(ampel.sensorId); Serial.println(ampel.sensorId);
Serial.print(F("Name : "));
Serial.println(config::ampel_name());
Serial.print(F("MAC : ")); Serial.print(F("MAC : "));
Serial.println(ampel.macAddress); Serial.println(ampel.macAddress);
Serial.print(F("Board : ")); Serial.print(F("Board : "));
...@@ -81,56 +90,36 @@ void setup() { ...@@ -81,56 +90,36 @@ void setup() {
sensor::initialize(); sensor::initialize();
#ifdef AMPEL_CSV csv_writer::initialize(config::ampel_name());
csv_writer::initialize(ampel.sensorId);
#endif
#ifdef AMPEL_WIFI
wifi::connect(ampel.sensorId);
if (wifi::connected()) {
# ifdef AMPEL_HTTP
web_server::initialize();
# endif
ntp::initialize(); ntp::initialize();
if (MDNS.begin(ampel.sensorId)) { // Start the mDNS responder for SENSOR_ID.local if (config::is_wifi_on) {
MDNS.addService("http", "tcp", 80); wifi::defineCommands();
Serial.println(F("mDNS responder started")); web_server::definePages();
} else { wifi::tryConnection();
Serial.println(F("Error setting up MDNS responder!"));
}
# ifdef AMPEL_MQTT
mqtt::initialize(ampel.sensorId);
# endif
} }
#endif
#if defined(AMPEL_LORAWAN) && defined(ESP32) #if defined(ESP32)
lorawan::initialize(); if (config::is_lorawan_active()) {
lorawan::initialize();
}
#endif #endif
} }
/*****************************************************************
* Helper functions *
*****************************************************************/
void keepServicesAlive();
void checkFlashButton();
void checkSerialInput();
/***************************************************************** /*****************************************************************
* Main loop * * Main loop *
*****************************************************************/ *****************************************************************/
void loop() { void loop() {
#if defined(AMPEL_LORAWAN) && defined(ESP32) #if defined(ESP32)
//LMIC Library seems to be very sensitive to timing issues, so run it first. if (config::is_lorawan_active()) {
lorawan::process(); //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. if (lorawan::waiting_for_confirmation) {
return; // If node is waiting for join confirmation from Gateway, nothing else should run.
return;
}
} }
#endif #endif
//NOTE: Loop should never take more than 1000ms. Split in smaller methods and logic if needed. //NOTE: Loop should never take more than 1000ms. Split in smaller methods and logic if needed.
...@@ -142,19 +131,21 @@ void loop() { ...@@ -142,19 +131,21 @@ void loop() {
// Short press for night mode, Long press for calibration. // Short press for night mode, Long press for calibration.
checkFlashButton(); checkFlashButton();
checkSerialInput(); sensor_console::checkSerialInput();
if (sensor::processData()) { if (sensor::processData()) {
#ifdef AMPEL_CSV if (config::is_csv_active()) {
csv_writer::logIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity); csv_writer::logIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity);
#endif }
#if defined(AMPEL_WIFI) && defined(AMPEL_MQTT) if (config::is_wifi_on && config::is_mqtt_active()) {
mqtt::publishIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity); mqtt::publishIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity);
#endif }
#if defined(AMPEL_LORAWAN) && defined(ESP32) #if defined(ESP32)
lorawan::preparePayloadIfTimeHasCome(sensor::co2, sensor::temperature, sensor::humidity); if (config::is_lorawan_active()) {
lorawan::preparePayloadIfTimeHasCome(sensor::co2, sensor::temperature, sensor::humidity);
}
#endif #endif
} }
...@@ -167,12 +158,59 @@ void loop() { ...@@ -167,12 +158,59 @@ void loop() {
} }
} }
void checkSerialInput() { /*****************************************************************
while (Serial.available() > 0) { * Callbacks *
sensor_console::processSerialInput(Serial.read()); *****************************************************************/
void wifiConnecting() {
Serial.print(F("WiFi - Trying to connect to "));
Serial.print(config::selected_ssid());
Serial.print(F(" (max "));
Serial.print(config::wifi_timeout);
Serial.println(F("s)."));
led_effects::showRainbowWheel();
}
void wifiConnected() {
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);
led_effects::showKITTWheel(color::green);
ntp::connect();
if (config::is_mqtt_active()) {
mqtt::initialize(ampel.sensorId);
} }
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());
} }
void wifiFailed() {
// Ampel will go back to Access Point mode for AP_TIMEOUT seconds, and try connection again after
Serial.println();
Serial.print(F("WiFi - Could not connect to "));
Serial.println(config::selected_ssid());
led_effects::showKITTWheel(color::red);
}
void apModeStarts() {
Serial.print(F("WiFi - Starting Access Point mode ("));
Serial.print(config::ampel_name());
Serial.println(F(")."));
led_effects::alert(color::turquoise);
}
/*****************************************************************
* Helper functions *
*****************************************************************/
/** /**
* Checks if flash button has been pressed: * Checks if flash button has been pressed:
* If not, do nothing. * If not, do nothing.
...@@ -186,6 +224,7 @@ void checkFlashButton() { ...@@ -186,6 +224,7 @@ void checkFlashButton() {
if (digitalRead(0)) { if (digitalRead(0)) {
Serial.println(F("Flash has been pressed for a short time. Should toggle night mode.")); Serial.println(F("Flash has been pressed for a short time. Should toggle night mode."));
led_effects::toggleNightMode(); led_effects::toggleNightMode();
//NOTE: Start Access Point instead?
} else { } else {
Serial.println(F("Flash has been pressed for a long time. Keep it pressed for calibration.")); Serial.println(F("Flash has been pressed for a long time. Keep it pressed for calibration."));
if (led_effects::countdownToZero()) { if (led_effects::countdownToZero()) {
...@@ -199,20 +238,13 @@ void checkFlashButton() { ...@@ -199,20 +238,13 @@ void checkFlashButton() {
} }
void keepServicesAlive() { void keepServicesAlive() {
#ifdef AMPEL_WIFI if (config::is_wifi_on) {
if (wifi::connected()) { web_config::update();
# if defined(ESP8266) if (wifi::connected()) {
//NOTE: Sadly, there seems to be a bug in the current MDNS implementation. ntp::update(); // NTP client has its own timer. It will connect to NTP server every 60s.
// It stops working after 2 minutes. And forcing a restart leads to a memory leak. if (config::is_mqtt_active()) {
MDNS.update(); mqtt::keepConnection(); // MQTT client has its own timer. It will keep alive every 15s.
# endif }
ntp::update(); // NTP client has its own timer. It will connect to NTP server every 60s. }
# ifdef AMPEL_HTTP
web_server::update();
# endif
# ifdef AMPEL_MQTT
mqtt::keepConnection(); // MQTT client has its own timer. It will keep alive every 15s.
# endif
} }
#endif
} }
#include "co2_sensor.h" #include "co2_sensor.h"
#include "config.h" #include "web_config.h"
#include "ntp.h" #include "ntp.h"
#include "led_effects.h" #include "led_effects.h"
#include "sensor_console.h" #include "sensor_console.h"
...@@ -11,25 +11,12 @@ ...@@ -11,25 +11,12 @@
#include "src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h" // From: http://librarymanager/All#SparkFun_SCD30 #include "src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h" // From: http://librarymanager/All#SparkFun_SCD30
namespace config { namespace config {
// UPPERCASE values should be defined in config.h
uint16_t measurement_timestep = MEASUREMENT_TIMESTEP; // [s] Value between 2 and 1800 (range for SCD30 sensor).
const uint16_t altitude_above_sea_level = ALTITUDE_ABOVE_SEA_LEVEL; // [m]
uint16_t co2_calibration_level = ATMOSPHERIC_CO2_CONCENTRATION; // [ppm]
const uint16_t measurement_timestep_bootup = 5; // [s] Measurement timestep during acclimatization. const uint16_t measurement_timestep_bootup = 5; // [s] Measurement timestep during acclimatization.
const uint8_t max_deviation_during_bootup = 20; // [%] const uint8_t max_deviation_during_bootup = 20; // [%]
const int8_t max_deviation_during_calibration = 30; // [ppm] const int8_t max_deviation_during_calibration = 30; // [ppm]
const int16_t timestep_during_calibration = 10; // [s] WARNING: Measurements can be unreliable for timesteps shorter than 10s. const int16_t timestep_during_calibration = 10; // [s] WARNING: Measurements can be unreliable for timesteps shorter than 10s.
const int8_t stable_measurements_before_calibration = 120 / timestep_during_calibration; // [-] Stable measurements during at least 2 minutes. const int8_t stable_measurements_before_calibration = 120 / timestep_during_calibration; // [-] Stable measurements during at least 2 minutes.
const uint16_t co2_alert_threshold = 2000; // [ppm] Display a flashing led ring, if concentration exceeds this value const uint16_t co2_alert_threshold = 2000; // [ppm] Display a flashing led ring, if concentration exceeds this value
#ifdef TEMPERATURE_OFFSET
// Residual heat from CO2 sensor seems to be high enough to change the temperature reading. How much should it be offset?
// NOTE: Sign isn't relevant. The returned temperature will always be shifted down.
const float temperature_offset = TEMPERATURE_OFFSET; // [K]
#else
const float temperature_offset = -3.0; // [K] Temperature measured by sensor is usually at least 3K too high.
#endif
bool auto_calibrate_sensor = AUTO_CALIBRATE_SENSOR; // [true / false]
const bool debug_sensor_states = false; // If true, log state transitions over serial console const bool debug_sensor_states = false; // If true, log state transitions over serial console
} }
...@@ -65,6 +52,10 @@ namespace sensor { ...@@ -65,6 +52,10 @@ namespace sensor {
state current_state = BOOTUP; state current_state = BOOTUP;
void switchState(state); void switchState(state);
void setCO2forDebugging(int32_t fakeCo2);
void calibrateSensorToSpecificPPM(int32_t calibrationLevel);
void calibrateSensorRightNow(int32_t calibrationLevel);
void setAutoCalibration(int32_t autoCalibration);
void initialize() { void initialize() {
#if defined(ESP8266) #if defined(ESP8266)
...@@ -95,14 +86,17 @@ namespace sensor { ...@@ -95,14 +86,17 @@ namespace sensor {
// for acclimatization. Resetting it after startup seems to fix this behaviour. // for acclimatization. Resetting it after startup seems to fix this behaviour.
scd30.reset(); scd30.reset();
//NOTE: It seems that the sensor needs some time for getting/setting temperature offset.
delay(500);
Serial.print(F("Setting temperature offset to -")); Serial.print(F("Setting temperature offset to -"));
Serial.print(abs(config::temperature_offset)); Serial.print(abs(config::temperature_offset));
Serial.println(F(" K.")); Serial.println(F(" K."));
scd30.setTemperatureOffset(abs(config::temperature_offset)); // setTemperatureOffset only accepts positive numbers, but shifts the temperature down. scd30.setTemperatureOffset(abs(config::temperature_offset)); // setTemperatureOffset only accepts positive numbers, but shifts the temperature down.
delay(100); delay(500);
Serial.print(F("Temperature offset is : -")); //NOTE: Even once the temperature offset is saved, the sensor still needs some time (~10 minutes?) to apply it.
Serial.print(scd30.getTemperatureOffset()); Serial.print(F("Temperature offset is : "));
Serial.print(getTemperatureOffset());
Serial.println(F(" K")); Serial.println(F(" K"));
Serial.print(F("Auto-calibration is ")); Serial.print(F("Auto-calibration is "));
...@@ -116,7 +110,7 @@ namespace sensor { ...@@ -116,7 +110,7 @@ namespace sensor {
Serial.println(F(" s during acclimatization.")); Serial.println(F(" s during acclimatization."));
scd30.setMeasurementInterval(config::measurement_timestep_bootup); // [s] scd30.setMeasurementInterval(config::measurement_timestep_bootup); // [s]
sensor_console::defineIntCommand("co2", setCO2forDebugging, F("1500 (Sets co2 level, for debugging purposes)")); sensor_console::defineIntCommand("co2", setCO2forDebugging, F("1500 (Sets co2 level, for debugging)"));
sensor_console::defineIntCommand("timer", setTimer, F("30 (Sets measurement interval, in s)")); sensor_console::defineIntCommand("timer", setTimer, F("30 (Sets measurement interval, in s)"));
sensor_console::defineCommand("calibrate", startCalibrationProcess, F("(Starts calibration process)")); sensor_console::defineCommand("calibrate", startCalibrationProcess, F("(Starts calibration process)"));
sensor_console::defineIntCommand("calibrate", calibrateSensorToSpecificPPM, sensor_console::defineIntCommand("calibrate", calibrateSensorToSpecificPPM,
...@@ -243,7 +237,7 @@ namespace sensor { ...@@ -243,7 +237,7 @@ namespace sensor {
delay(100); delay(100);
} else { } else {
// Display a flashing led ring, if concentration exceeds a specific value // Display a flashing led ring, if concentration exceeds a specific value
led_effects::redAlert(); led_effects::alert(color::red);
} }
} }
...@@ -294,6 +288,10 @@ namespace sensor { ...@@ -294,6 +288,10 @@ namespace sensor {
return freshData && (current_state == READY || current_state == NEEDS_CALIBRATION); return freshData && (current_state == READY || current_state == NEEDS_CALIBRATION);
} }
float getTemperatureOffset() {
return -abs(scd30.getTemperatureOffset());
}
/***************************************************************** /*****************************************************************
* Callbacks for sensor commands * * Callbacks for sensor commands *
*****************************************************************/ *****************************************************************/
......
...@@ -3,13 +3,6 @@ ...@@ -3,13 +3,6 @@
#include <stdint.h> // For uint16_t #include <stdint.h> // For uint16_t
namespace config {
extern uint16_t measurement_timestep; // [s] Value between 2 and 1800 (range for SCD30 sensor)
extern bool auto_calibrate_sensor; // [true / false]
extern uint16_t co2_calibration_level; // [ppm]
extern const float temperature_offset; // [K] Sign isn't relevant.
}
namespace sensor { namespace sensor {
extern uint16_t co2; extern uint16_t co2;
extern float temperature; extern float temperature;
...@@ -19,12 +12,8 @@ namespace sensor { ...@@ -19,12 +12,8 @@ namespace sensor {
void initialize(); void initialize();
bool processData(); bool processData();
void startCalibrationProcess(); void startCalibrationProcess();
void setCO2forDebugging(int32_t fakeCo2);
void setTimer(int32_t timestep); void setTimer(int32_t timestep);
void calibrateSensorToSpecificPPM(int32_t calibrationLevel);
void calibrateSensorRightNow(int32_t calibrationLevel);
void setAutoCalibration(int32_t autoCalibration);
void resetSCD(); void resetSCD();
float getTemperatureOffset();
} }
#endif #endif
#ifndef CONFIG_H_INCLUDED #ifndef CONFIG_H_INCLUDED
# define CONFIG_H_INCLUDED # define CONFIG_H_INCLUDED
// This file is a config template, and can be copied to config.h. Please don't save any important password in this template. /*** _ _
* / \ _ __ ___ _ __ ___| |
* / _ \ | '_ ` _ \| '_ \ / _ \ |
* / ___ \| | | | | | |_) | __/ |
* /_/ __\_\_| |_| |_| .__/_\___|_|
* / ___|___ _ __|_/ _(_) __ _
* | | / _ \| '_ \| |_| |/ _` |
* | |__| (_) | | | | _| | (_| |
* \____\___/|_| |_|_| |_|\__, |
* |___/
***/
// This file is a config template, and can be copied to config.h.
// Please don't save any important password in this template.
// IMPORTANT: Parameters defined in config.h are only default values, and are applied if:
// * the ampel is flashed for the first time
// * or 'reset_config' command is called
// * or AMPEL_CONFIG_VERSION has been changed.
// Once those default values have been applied, uploading the firmware with a modified config.h will not update the ampel configuration!
// Every parameter can be modified and saved later via the web-config.
// Some of those parameters can also be modified via commands in the Serial monitor :
// e.g. 'wifi 0' to turn WiFi off, or 'csv 60' to log data in csv every minute.
/***
* AMPEL
*/
// You can rename the Ampel if you want.
// This name will be used for CSV files and the mDNS address.
// You'll get a new CSV file after renaming, which can be convenient, e.g. after moving
// the ampel to another room.
// If left empty, the name will be ESPxxxxxx, where xxxxxx represent the last half of the MAC address.
# define AMPEL_NAME ""
// This password will be used for Access Point (without username), and for web-server available at http://local_ip with user 'admin', without quotes.
// If left empty, the password will have to be set during the first configuration, via access point.
// In order to be set successfully, it should have at least 8 characters.
# define AMPEL_PASSWORD ""
// AMPEL_CONFIG_VERSION should be defined, and have exactly 3 characters.
// If you modify this string, every parameter saved on the Ampel will be replaced by the ones in config.h.
// AMPEL_CONFIG_VERSION should also be updated if the configuration structure is modified.
// The structure of the Ampel configuration has been modified 11 times, so it's called "a11" for now.
# define AMPEL_CONFIG_VERSION "a11"
/** /**
* SERVICES * SERVICES
*/ */
// Comment or remove those lines if you want to disable the corresponding services // Define the default for corresponding services. They can be enabled/disabled later in the web-config.
# define AMPEL_WIFI // Should ESP connect to WiFi? It allows the Ampel to get time from an NTP server. # define AMPEL_WIFI true // Should ESP connect to WiFi? Web configuration will not be available when set to false. Use "wifi 1" command to set to true.
# define AMPEL_HTTP // Should HTTP web server be started? (AMPEL_WIFI should be enabled too) # define AMPEL_MQTT true // Should data be sent over MQTT? (AMPEL_WIFI should be enabled too)
# define AMPEL_MQTT // Should data be sent over MQTT? (AMPEL_WIFI should be enabled too) # define AMPEL_CSV true // Should data be logged as CSV, on the ESP flash memory?
# define AMPEL_CSV // Should data be logged as CSV, on the ESP flash memory? # define AMPEL_LORAWAN false // Should data be sent over LoRaWAN? (Requires ESP32 + LoRa modem, and "MCCI LoRaWAN LMIC library")
// # define AMPEL_LORAWAN // Should data be sent over LoRaWAN? (Requires ESP32 + LoRa modem, and "MCCI LoRaWAN LMIC library")
/** /**
* WIFI * WIFI
*/ */
# define WIFI_SSID "MY_SSID" // SSID and PASSWORD need to be defined, but can be empty.
# define WIFI_PASSWORD "P4SSW0RD" # define WIFI_SSID ""
# define WIFI_PASSWORD ""
// How long should the Ampel try to connect to WIFI_SSID?
# define WIFI_TIMEOUT 30 // [s] # define WIFI_TIMEOUT 30 // [s]
// If the Ampel cannot connect to WIFI_SSID, it will start an Access Point for ACCESS_POINT_TIMEOUT seconds.
// If someone connects to this Access Point, the Ampel will stay in this mode until everybody logs out.
// If nobody connects to the Access Point before ACCESS_POINT_TIMEOUT seconds, the Ampel will try to connect WIFI_SSID again.
# define ACCESS_POINT_TIMEOUT 60 // [s]
/** /**
* Sensor * Sensor
...@@ -61,22 +107,14 @@ ...@@ -61,22 +107,14 @@
// LED brightness, which can vary between min and max brightness ("LED breathing") // LED brightness, which can vary between min and max brightness ("LED breathing")
// MAX_BRIGHTNESS must be defined, and should be between 0 and 255. // MAX_BRIGHTNESS must be defined, and should be between 0 and 255.
# define MAX_BRIGHTNESS 255 // NOTE: LEDs require a decent amount of electricity, so the default is chosen to be relativitely low.
// MIN_BRIGHTNESS, if defined, should be between 0 and MAX_BRIGHTNESS - 1 # define MAX_BRIGHTNESS 80
// If MIN_BRIGHTNESS is not set, or if it is set to MAX_BRIGHTNESS, breathing is disabled. // MIN_BRIGHTNESS, must be defined, and should be between 0 and MAX_BRIGHTNESS
# define MIN_BRIGHTNESS 60 // If MIN_BRIGHTNESS is set to MAX_BRIGHTNESS, breathing is disabled.
// How many LEDs in the ring? 12 and 16 are currently supported. If undefined, 12 is used as default. # define MIN_BRIGHTNESS 40
// How many LEDs in the ring? 12 and 16 are currently supported.
# define LED_COUNT 12 # define LED_COUNT 12
/**
* WEB SERVER
* available at http://local_ip, with user HTTP_USER and password HTTP_PASSWORD
*/
// Define empty strings in order to disable authentication, or remove the constants altogether.
# define HTTP_USER "co2ampel"
# define HTTP_PASSWORD "my_password"
/** /**
* MQTT * MQTT
*/ */
...@@ -107,48 +145,49 @@ ...@@ -107,48 +145,49 @@
// How often should measurements be sent to MQTT server? // How often should measurements be sent to MQTT server?
// Set to 0 if you want to send values after each measurement // Set to 0 if you want to send values after each measurement
// # define MQTT_SENDING_INTERVAL MEASUREMENT_TIMESTEP * 5 // [s] // # define MQTT_SENDING_INTERVAL MEASUREMENT_TIMESTEP * 5 // [s]
# define MQTT_SENDING_INTERVAL 60 // [s] # define MQTT_SENDING_INTERVAL 300 // [s]
# define MQTT_SERVER "test.mosquitto.org" // MQTT server URL or IP address # define MQTT_SERVER "test.mosquitto.org" // MQTT server URL or IP address
# define MQTT_PORT 8883 # define MQTT_PORT 8883
# define MQTT_ENCRYPTED true // Set to false for unencrypted MQTT (e.g. with port 1883). If undefined, MQTT_ENCRYPTED will be set to true. # define MQTT_ENCRYPTED true // Set to false for unencrypted MQTT (e.g. with port 1883).
# define MQTT_USER "" # define MQTT_USER ""
# define MQTT_PASSWORD "" # define MQTT_PASSWORD ""
# define MQTT_TOPIC_PREFIX "CO2sensors/" // ESPxxxxxx will be added to the prefix, so complete topic will be "CO2sensors/ESPxxxxxx". The prefix should probably end with '/'
/** /**
* LoRaWAN * LoRaWAN
*/ */
// 1) Requires "MCCI LoRaWAN LMIC library", which will be automatically used with PlatformIO but should be added in "Arduino IDE". // 1) Requires "MCCI LoRaWAN LMIC library", which will be automatically used with PlatformIO but should be added in "Arduino IDE".
// 2) If you need to, region and transceiver type can be specified in lorawan.cpp. Default is "Europe 868" // 2) Region and transceiver type should be specified in:
// * Arduino/libraries/MCCI_LoRaWAN_LMIC_library/project_config/lmic_project_config.h for Arduino IDE
// * platformio.ini for PlatformIO
// See https://github.com/mcci-catena/arduino-lmic#configuration for more information
// 3) It has been tested with "TTGO ESP32 SX1276 LoRa 868" and will only work with an ESP32 + LoRa modem // 3) It has been tested with "TTGO ESP32 SX1276 LoRa 868" and will only work with an ESP32 + LoRa modem
// 4) In order to use LoRaWAN, a gateway should be close to the co2ampel, and an account, an application and a device should be registered, // 4) In order to use LoRaWAN, a gateway should be close to the co2ampel, and an account, an application and a device should be registered,
// e.g. on https://www.thethingsnetwork.org/docs/applications/ // e.g. on https://www.thethingsindustries.com/docs/integrations/
// with "Europe 863-870 MHz (SF9 for RX2 - recommended)", "MAC v1.0.3"
// 5) The corresponding keys should be defined in LORAWAN_DEVICE_EUI, LORAWAN_APPLICATION_EUI and LORAWAN_APPLICATION_KEY // 5) The corresponding keys should be defined in LORAWAN_DEVICE_EUI, LORAWAN_APPLICATION_EUI and LORAWAN_APPLICATION_KEY
// How often should measurements be sent over LoRaWAN? // How often should measurements be sent over LoRaWAN?
# define LORAWAN_SENDING_INTERVAL 300 // [s] This value should not be too low. See https://www.thethingsnetwork.org/docs/lorawan/duty-cycle.html#maximum-duty-cycle # define LORAWAN_SENDING_INTERVAL 300 // [s] This value should not be too low. See https://www.thethingsnetwork.org/docs/lorawan/duty-cycle.html#maximum-duty-cycle
// WARNING: If AMPEL_LORAWAN is enabled, you need to modify the 3 following constants! // WARNING: If AMPEL_LORAWAN is true, you need to modify the 3 following constants
// This EUI must be in little-endian format, so least-significant-byte first. // They are written as hexadecimal strings, and will be parsed in the correct order.
// When copying an EUI from ttnctl output, this means to reverse the bytes.
# define LORAWAN_DEVICE_EUI {0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11} // This EUI must be in big-endian format, so most-significant-byte first.
// This should also be in little endian format, see above. // You can copy the string from TheThingsNetwork as-is, without reversing the bytes.
// For TheThingsNetwork issued EUIs the last bytes should be 0xD5, 0xB3, 0x70. // For TheThingsNetwork issued EUIs the string should start with "70B3D5..."
# define LORAWAN_APPLICATION_EUI {0x00, 0x00, 0x00, 0x00, 0x00, 0xD5, 0xB3, 0x70} # define LORAWAN_DEVICE_EUI "70B3D57ED004CB17"
// This key should be in big endian format (or, since it is not really a // This should also be in big-endian format, and can be copied as is from TheThingsNetwork.
// number but a block of memory, endianness does not really apply). In # define LORAWAN_APPLICATION_EUI "0102030405060708"
// practice, a key taken from ttnctl can be copied as-is. // This should also be in big-endian format, and can be copied as is from TheThingsNetwork.
# define LORAWAN_APPLICATION_KEY {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } # define LORAWAN_APPLICATION_KEY "9D06308E20B974919DA6404E063BE01D"
/** /**
* NTP * NTP
*/ */
# define NTP_SERVER "pool.ntp.org" # define NTP_SERVER "pool.ntp.org"
# define UTC_OFFSET_IN_SECONDS 7200 // [s] 3600 for UTC+1, 7200 for UTC+1 and daylight saving time # define UTC_OFFSET 1 // [h] +1 for Paris/Berlin, -5 for NYC
# define DAYLIGHT_SAVING_TIME false // true in summer, false in winter
/**
* Others
*/
# define BAUDS 115200 // Transmission rate
#endif #endif
#include "csv_writer.h" #include "csv_writer.h"
#include "config.h" #include "web_config.h"
#include "ntp.h" #include "ntp.h"
#include "led_effects.h" #include "led_effects.h"
#include "sensor_console.h" #include "sensor_console.h"
namespace config {
// Values should be defined in config.h
uint16_t csv_interval = CSV_INTERVAL; // [s]
}
namespace csv_writer { namespace csv_writer {
unsigned long last_written_at = 0; unsigned long last_written_at = 0;
char last_successful_write[23]; char last_successful_write[23];
...@@ -83,14 +79,14 @@ namespace csv_writer { ...@@ -83,14 +79,14 @@ namespace csv_writer {
} }
#endif #endif
char filename[15]; // "/ESPxxxxxx.csv\0" char filename[20]; // e.g. "/ESPxxxxxx.csv\0"
int getAvailableSpace() { int getAvailableSpace() {
return getTotalSpace() - getUsedSpace(); return getTotalSpace() - getUsedSpace();
} }
void initialize(const char *sensorId) { void initialize(const char *basename) {
snprintf(filename, sizeof(filename), "/%s.csv", sensorId); snprintf(filename, sizeof(filename), "/%.14s.csv", basename);
Serial.println(); Serial.println();
Serial.print(F("Initializing FS...")); Serial.print(F("Initializing FS..."));
...@@ -184,6 +180,7 @@ namespace csv_writer { ...@@ -184,6 +180,7 @@ namespace csv_writer {
} }
void showCSVContent() { void showCSVContent() {
//TODO: Now that ampel_name can be set, should show the content of every csv
Serial.print(F("### ")); Serial.print(F("### "));
Serial.print(filename); Serial.print(filename);
Serial.println(F(" ###")); Serial.println(F(" ###"));
......
...@@ -10,13 +10,11 @@ ...@@ -10,13 +10,11 @@
#else #else
# error Board should be either ESP8266 or ESP832 # error Board should be either ESP8266 or ESP832
#endif #endif
//NOTE: LittleFS will be available for Arduino esp32 core v2
namespace config {
extern uint16_t csv_interval; // [s]
}
namespace csv_writer { namespace csv_writer {
extern char last_successful_write[]; extern char last_successful_write[];
void initialize(const char *sensorId); void initialize(const char *basename);
void logIfTimeHasCome(const char *timestamp, const int16_t &co2, const float &temperature, const float &humidity); void logIfTimeHasCome(const char *timestamp, const int16_t &co2, const float &temperature, const float &humidity);
int getAvailableSpace(); int getAvailableSpace();
extern char filename[]; extern char filename[];
......
#include "led_effects.h" #include "led_effects.h"
#include "config.h" #include "web_config.h"
#include "sensor_console.h" #include "sensor_console.h"
// Adafruit NeoPixel (Arduino library for controlling single-wire-based LED pixels and strip) // Adafruit NeoPixel (Arduino library for controlling single-wire-based LED pixels and strip)
...@@ -12,37 +12,14 @@ ...@@ -12,37 +12,14 @@
* Configuration * * Configuration *
*****************************************************************/ *****************************************************************/
namespace config { namespace config {
const uint8_t max_brightness = MAX_BRIGHTNESS;
#if defined(MIN_BRIGHTNESS)
const uint8_t min_brightness = MIN_BRIGHTNESS;
#else
const uint8_t min_brightness = MAX_BRIGHTNESS;
#endif
const uint8_t brightness_amplitude = config::max_brightness - config::min_brightness;
const int kitt_tail = 3; // How many dimmer LEDs follow in K.I.T.T. wheel const int kitt_tail = 3; // How many dimmer LEDs follow in K.I.T.T. wheel
const uint16_t poor_air_quality_ppm = 1600; // Above this threshold, LED breathing effect is faster. const uint16_t poor_air_quality_ppm = 1600; // Above this threshold, LED breathing effect is faster.
bool display_led = true; // Will be set to false during "night mode". bool display_led = true; // Will be set to false during "night mode".
#if !defined(LED_COUNT)
# define LED_COUNT 12
#endif
const uint16_t led_count = LED_COUNT;
#if LED_COUNT == 12
//NOTE: One value has been prepended, to make calculations easier and avoid out of bounds index. //NOTE: One value has been prepended, to make calculations easier and avoid out of bounds index.
const uint16_t co2_ticks[led_count + 1] = { 0, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000, 2200 }; // [ppm] uint16_t co2_ticks[16 + 1] = { 0, 500, 600, 700, 800, 900, 1000 }; // rest will be filled later
// For a given LED, which color should be displayed? First LED will be pure green (hue angle 120°), // For a given LED, which color should be displayed? First LED will be pure green (hue angle 120°),
// LEDs >= 1600ppm will be pure red (hue angle 0°), LEDs in-between will be yellowish. // LEDs >= 1600ppm will be pure red (hue angle 0°), LEDs in-between will be yellowish.
const uint16_t led_hues[led_count] = { 21845U, 19114U, 16383U, 13653U, 10922U, 8191U, 5461U, 2730U, 0, 0, 0, 0 }; // [hue angle] uint16_t led_hues[16];
#elif LED_COUNT == 16
const uint16_t co2_ticks[led_count + 1] = { 0, 500, 600, 700, 800, 900, 1000, 1100, 1200,
1300, 1400, 1500, 1600, 1700, 1800, 2000, 2200 }; // [ppm]
const uint16_t led_hues[led_count] = {21845U, 19859U, 17873U, 15887U, 13901U, 11915U, 9929U, 7943U,
5957U, 3971U, 1985U, 0, 0, 0, 0, 0}; // [hue angle]
#else
# error "Only 12 and 16 LEDs rings are currently supported."
#endif
} }
#if defined(ESP8266) #if defined(ESP8266)
...@@ -53,7 +30,8 @@ const int NEOPIXELS_PIN = 5; ...@@ -53,7 +30,8 @@ const int NEOPIXELS_PIN = 5;
const int NEOPIXELS_PIN = 23; const int NEOPIXELS_PIN = 23;
#endif #endif
Adafruit_NeoPixel pixels(config::led_count, NEOPIXELS_PIN, NEO_GRB + NEO_KHZ800); // config::led_count is not yet known, will be set later.
Adafruit_NeoPixel pixels(0, NEOPIXELS_PIN, NEO_GRB + NEO_KHZ800);
namespace led_effects { namespace led_effects {
//On-board LED on D4, aka GPIO02 //On-board LED on D4, aka GPIO02
...@@ -94,6 +72,64 @@ namespace led_effects { ...@@ -94,6 +72,64 @@ namespace led_effects {
} }
void setupRing() { void setupRing() {
Serial.print(F("Ring : "));
Serial.print(config::led_count);
Serial.println(F(" LEDs."));
pixels.updateLength(config::led_count);
if (config::led_count == 12) {
config::co2_ticks[7] = 1200;
config::co2_ticks[8] = 1400;
config::co2_ticks[9] = 1600;
config::co2_ticks[10] = 1800;
config::co2_ticks[11] = 2000;
config::co2_ticks[12] = 2200;
config::led_hues[0] = 21845U;
config::led_hues[1] = 19114U;
config::led_hues[2] = 16383U;
config::led_hues[3] = 13653U;
config::led_hues[4] = 10922U;
config::led_hues[5] = 8191U;
config::led_hues[6] = 5461U;
config::led_hues[7] = 2730U;
config::led_hues[8] = 0;
config::led_hues[9] = 0;
config::led_hues[10] = 0;
config::led_hues[11] = 0;
} else if (config::led_count == 16) {
config::co2_ticks[7] = 1100;
config::co2_ticks[8] = 1200;
config::co2_ticks[9] = 1300;
config::co2_ticks[10] = 1400;
config::co2_ticks[11] = 1500;
config::co2_ticks[12] = 1600;
config::co2_ticks[13] = 1700;
config::co2_ticks[14] = 1800;
config::co2_ticks[15] = 2000;
config::co2_ticks[16] = 2200;
config::led_hues[0] = 21845U;
config::led_hues[1] = 19859U;
config::led_hues[2] = 17873U;
config::led_hues[3] = 15887U;
config::led_hues[4] = 13901U;
config::led_hues[5] = 11915U;
config::led_hues[6] = 9929U;
config::led_hues[7] = 7943U;
config::led_hues[8] = 5957U;
config::led_hues[9] = 3971U;
config::led_hues[10] = 1985U;
config::led_hues[11] = 0;
config::led_hues[12] = 0;
config::led_hues[13] = 0;
config::led_hues[14] = 0;
config::led_hues[15] = 0;
} else {
// "Only 12 and 16 LEDs rings are currently supported."
config::display_led = false;
}
pixels.begin(); pixels.begin();
pixels.setBrightness(config::max_brightness); pixels.setBrightness(config::max_brightness);
LEDsOff(); LEDsOff();
...@@ -106,6 +142,7 @@ namespace led_effects { ...@@ -106,6 +142,7 @@ namespace led_effects {
} }
void turnLEDsOnOff(int32_t display_led) { void turnLEDsOnOff(int32_t display_led) {
//TODO: Could use strategy pattern with 2 different Effects classes.
config::display_led = display_led; config::display_led = display_led;
if (config::display_led) { if (config::display_led) {
Serial.println(F("LEDs are on!")); Serial.println(F("LEDs are on!"));
...@@ -165,7 +202,8 @@ namespace led_effects { ...@@ -165,7 +202,8 @@ namespace led_effects {
*/ */
void breathe(int16_t co2) { void breathe(int16_t co2) {
static uint8_t breathing_offset = 0; static uint8_t breathing_offset = 0;
uint16_t brightness = config::min_brightness + pixels.sine8(breathing_offset) * config::brightness_amplitude / 255; uint8_t brightness_amplitude = config::max_brightness - config::min_brightness;
uint16_t brightness = config::min_brightness + pixels.sine8(breathing_offset) * brightness_amplitude / 255;
pixels.setBrightness(brightness); pixels.setBrightness(brightness);
pixels.show(); pixels.show();
breathing_offset += co2 > config::poor_air_quality_ppm ? 6 : 3; // breathing speed. +3 looks like slow human breathing. breathing_offset += co2 > config::poor_air_quality_ppm ? 6 : 3; // breathing speed. +3 looks like slow human breathing.
...@@ -184,7 +222,7 @@ namespace led_effects { ...@@ -184,7 +222,7 @@ namespace led_effects {
pixels.setPixelColor(ledId, pixels.ColorHSV(config::led_hues[ledId], 255, brightness)); pixels.setPixelColor(ledId, pixels.ColorHSV(config::led_hues[ledId], 255, brightness));
} }
pixels.show(); pixels.show();
if (config::brightness_amplitude > 0) { if (config::max_brightness > config::min_brightness) {
breathe(co2); breathe(co2);
} }
} }
...@@ -207,7 +245,7 @@ namespace led_effects { ...@@ -207,7 +245,7 @@ namespace led_effects {
} }
} }
void redAlert() { void alert(uint32_t color) {
if (!config::display_led) { if (!config::display_led) {
onBoardLEDOn(); onBoardLEDOn();
delay(500); delay(500);
...@@ -218,7 +256,7 @@ namespace led_effects { ...@@ -218,7 +256,7 @@ namespace led_effects {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
pixels.setBrightness(static_cast<int>(config::max_brightness * (1 - i * 0.1))); pixels.setBrightness(static_cast<int>(config::max_brightness * (1 - i * 0.1)));
delay(50); delay(50);
pixels.fill(color::red); pixels.fill(color);
pixels.show(); pixels.show();
} }
} }
......
...@@ -9,6 +9,7 @@ namespace color { ...@@ -9,6 +9,7 @@ namespace color {
const uint32_t blue = 0x0000FF; const uint32_t blue = 0x0000FF;
const uint32_t black = 0x000000; const uint32_t black = 0x000000;
const uint32_t magenta = 0xFF00FF; const uint32_t magenta = 0xFF00FF;
const uint32_t turquoise = 0x1CFF68;
} }
namespace led_effects { namespace led_effects {
...@@ -20,7 +21,7 @@ namespace led_effects { ...@@ -20,7 +21,7 @@ namespace led_effects {
void LEDsOff(); void LEDsOff();
void setupRing(); void setupRing();
void redAlert(); void alert(uint32_t color);
bool countdownToZero(); bool countdownToZero();
void showWaitingLED(uint32_t color); void showWaitingLED(uint32_t color);
void showKITTWheel(uint32_t color, uint16_t duration_s = 2); void showKITTWheel(uint32_t color, uint16_t duration_s = 2);
......
#include "lorawan.h" #include "lorawan.h"
#if defined(AMPEL_LORAWAN) && defined(ESP32) #if defined(ESP32)
#include "web_config.h"
#include "led_effects.h" #include "led_effects.h"
#include "sensor_console.h" #include "sensor_console.h"
#include "util.h" #include "util.h"
#include "ntp.h" #include "ntp.h"
/*** Define region and transceiver type, and ignore lmic_project_config.h from lmic library ***/
// Those values are probably okay if you're in Europe.
#define ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS
#define CFG_eu868 1
#define CFG_sx1276_radio 1
/****************************************************************************************/
// Requires "MCCI LoRaWAN LMIC library", which will be automatically used with PlatformIO but should be added in "Arduino IDE" // Requires "MCCI LoRaWAN LMIC library", which will be automatically used with PlatformIO but should be added in "Arduino IDE"
// Tested successfully with v3.2.0 and connected to a thethingsnetwork.org app. // Tested successfully with v3.2.0 and connected to a thethingsnetwork.org app.
#include <lmic.h> #include <lmic.h>
...@@ -37,12 +31,6 @@ namespace config { ...@@ -37,12 +31,6 @@ namespace config {
#else #else
# error "Region should be specified" # error "Region should be specified"
#endif #endif
// Values should be defined in config.h
uint16_t lorawan_sending_interval = LORAWAN_SENDING_INTERVAL; // [s]
static const u1_t PROGMEM APPEUI[8] = LORAWAN_APPLICATION_EUI;
static const u1_t PROGMEM DEVEUI[8] = LORAWAN_DEVICE_EUI;
static const u1_t PROGMEM APPKEY[16] = LORAWAN_APPLICATION_KEY;
} }
// Payloads will be automatically sent via MQTT by TheThingsNetwork, and can be seen with: // Payloads will be automatically sent via MQTT by TheThingsNetwork, and can be seen with:
...@@ -53,18 +41,6 @@ namespace config { ...@@ -53,18 +41,6 @@ namespace config {
// co2ampel-test/devices/esp3a7c94/up {"app_id":"co2ampel-test","dev_id":"esp3a7c94","hardware_serial":"00xxxxxxxx","port":1,"counter":5,"payload_raw":"TJd7","payload_fields":{"co2":760,"rh":61.5,"temp":20.2},"metadata":{"time":"2020-12-23T23:00:51.44020438Z","frequency":867.5,"modulation":"LORA","data_rate":"SF7BW125","airtime":51456000,"coding_rate":"4/5","gateways":[{"gtw_id":"eui-xxxxxxxxxxxxxxxxxx","timestamp":1765406908,"time":"2020-12-23T23:00:51.402519Z","channel":5,"rssi":-64,"snr":7.5,"rf_chain":0,"latitude":22.7,"longitude":114.24,"altitude":450}]}} // co2ampel-test/devices/esp3a7c94/up {"app_id":"co2ampel-test","dev_id":"esp3a7c94","hardware_serial":"00xxxxxxxx","port":1,"counter":5,"payload_raw":"TJd7","payload_fields":{"co2":760,"rh":61.5,"temp":20.2},"metadata":{"time":"2020-12-23T23:00:51.44020438Z","frequency":867.5,"modulation":"LORA","data_rate":"SF7BW125","airtime":51456000,"coding_rate":"4/5","gateways":[{"gtw_id":"eui-xxxxxxxxxxxxxxxxxx","timestamp":1765406908,"time":"2020-12-23T23:00:51.402519Z","channel":5,"rssi":-64,"snr":7.5,"rf_chain":0,"latitude":22.7,"longitude":114.24,"altitude":450}]}}
// More info : https://www.thethingsnetwork.org/docs/applications/mqtt/quick-start.html // More info : https://www.thethingsnetwork.org/docs/applications/mqtt/quick-start.html
void os_getArtEui(u1_t *buf) {
memcpy_P(buf, config::APPEUI, 8);
}
void os_getDevEui(u1_t *buf) {
memcpy_P(buf, config::DEVEUI, 8);
}
void os_getDevKey(u1_t *buf) {
memcpy_P(buf, config::APPKEY, 16);
}
namespace lorawan { namespace lorawan {
bool waiting_for_confirmation = false; bool waiting_for_confirmation = false;
bool connected = false; bool connected = false;
...@@ -104,9 +80,9 @@ namespace lorawan { ...@@ -104,9 +80,9 @@ namespace lorawan {
void onEvent(ev_t ev) { void onEvent(ev_t ev) {
char current_time[23]; char current_time[23];
ntp::getLocalTime(current_time); ntp::getLocalTime(current_time);
Serial.print("LoRa - "); Serial.print(F("LoRa - "));
Serial.print(current_time); Serial.print(current_time);
Serial.print(" - "); Serial.print(F(" - "));
switch (ev) { switch (ev) {
case EV_JOINING: case EV_JOINING:
Serial.println(F("EV_JOINING")); Serial.println(F("EV_JOINING"));
...@@ -122,6 +98,7 @@ namespace lorawan { ...@@ -122,6 +98,7 @@ namespace lorawan {
u1_t nwkKey[16]; u1_t nwkKey[16];
u1_t artKey[16]; u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey); LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
//NOTE: Saving session to EEPROM seems like a good idea at first, but unfortunately: too much info is needed, and a counter would need to be save every single time data is sent.
Serial.print(F(" netid: ")); Serial.print(F(" netid: "));
Serial.println(netid, DEC); Serial.println(netid, DEC);
Serial.print(F(" devaddr: ")); Serial.print(F(" devaddr: "));
...@@ -210,14 +187,19 @@ namespace lorawan { ...@@ -210,14 +187,19 @@ namespace lorawan {
// Prepare upstream data transmission at the next possible time. // Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, buff, sizeof(buff), 0); LMIC_setTxData2(1, buff, sizeof(buff), 0);
//NOTE: To decode in TheThingsNetwork: //NOTE: To decode in TheThingsNetwork:
//function Decoder(bytes, port) { // function decodeUplink(input) {
// return { // return {
// co2: bytes[0] * 20, // data: {
// temp: bytes[1] / 5.0 - 10, // co2: input.bytes[0] * 20,
// rh: bytes[2] / 2.0 // temp: input.bytes[1] / 5.0 - 10,
// }; // rh: input.bytes[2] / 2.0
//} // },
// warnings: [],
// errors: []
// };
// }
} }
} }
...@@ -245,4 +227,53 @@ namespace lorawan { ...@@ -245,4 +227,53 @@ namespace lorawan {
void onEvent(ev_t ev) { void onEvent(ev_t ev) {
lorawan::onEvent(ev); lorawan::onEvent(ev);
} }
// 'A' -> 10, 'F' -> 15, 'f' -> 15, 'z' -> -1
int8_t hexCharToInt(char c) {
int8_t v = -1;
if ((c >= '0') && (c <= '9')) {
v = (c - '0');
} else if ((c >= 'A') && (c <= 'F')) {
v = (c - 'A' + 10);
} else if ((c >= 'a') && (c <= 'f')) {
v = (c - 'a' + 10);
}
return v;
}
/**
* Parses hex string and saves the corresponding bytes in buf.
* msb is true for most-significant-byte, false for least-significant-byte.
*
* "112233" will be loaded into {0x11, 0x22, 0x33} in MSB, {0x33, 0x22, 0x11} in LSB.
*/
void hexStringToByteArray(uint8_t *buf, const char *hex, uint max_n, bool msb) {
int n = util::min(strlen(hex) / 2, max_n);
for (int i = 0; i < n; i++) {
int j;
if (msb) {
j = i;
} else {
j = n - 1 - i;
}
uint8_t r = hexCharToInt(hex[j * 2]) * 16 + hexCharToInt(hex[j * 2 + 1]);
buf[i] = r;
}
}
// Load config into LMIC byte arrays.
void os_getArtEui(u1_t *buf) {
hexStringToByteArray(buf, config::lorawan_app_eui, 8, false);
}
void os_getDevEui(u1_t *buf) {
hexStringToByteArray(buf, config::lorawan_device_eui, 8, false);
}
void os_getDevKey(u1_t *buf) {
hexStringToByteArray(buf, config::lorawan_app_key, 16, true);
}
#endif #endif
#ifndef AMPEL_LORAWAN_H_ #ifndef AMPEL_LORAWAN_H_
#define AMPEL_LORAWAN_H_ #define AMPEL_LORAWAN_H_
#include "config.h" # if defined(ESP32)
# if defined(AMPEL_LORAWAN) && defined(ESP32)
#include <stdint.h> // For uint32_t & uint16_t #include <stdint.h> // For uint32_t & uint16_t
namespace config { namespace config {
extern uint16_t lorawan_sending_interval; // [s]
extern const char *lorawan_frequency_plan; // e.g. "Europe 868" extern const char *lorawan_frequency_plan; // e.g. "Europe 868"
} }
......
#include "mqtt.h" #include "mqtt.h"
#include "config.h" #include "web_config.h"
#include "led_effects.h" #include "led_effects.h"
#include "sensor_console.h" #include "sensor_console.h"
#include "wifi_util.h" #include "wifi_util.h"
...@@ -14,46 +14,44 @@ ...@@ -14,46 +14,44 @@
#endif #endif
namespace config { namespace config {
// Values should be defined in config.h // Values should be defined in config.h or over webconfig
uint16_t mqtt_sending_interval = MQTT_SENDING_INTERVAL; // [s]
//INFO: Listen to every CO2 sensor which is connected to the server: //INFO: Listen to every CO2 sensor which is connected to the server:
// mosquitto_sub -h MQTT_SERVER -t 'CO2sensors/#' -p 443 --capath /etc/ssl/certs/ -u "MQTT_USER" -P "MQTT_PASSWORD" -v // mosquitto_sub -h MQTT_SERVER -t 'CO2sensors/#' -p 443 --capath /etc/ssl/certs/ -u "MQTT_USER" -P "MQTT_PASSWORD" -v
const char *mqtt_server = MQTT_SERVER;
const uint16_t mqtt_port = MQTT_PORT;
const char *mqtt_user = MQTT_USER;
const char *mqtt_password = MQTT_PASSWORD;
const bool allow_mqtt_commands = ALLOW_MQTT_COMMANDS;
const unsigned long wait_after_fail = 900; // [s] Wait 15 minutes after an MQTT connection fail, before trying again. const unsigned long wait_after_fail = 900; // [s] Wait 15 minutes after an MQTT connection fail, before trying again.
} }
#if MQTT_ENCRYPTED #if defined(ESP32)
# if defined(ESP32) # include <WiFiClientSecure.h>
# include <WiFiClientSecure.h>
# endif
WiFiClientSecure espClient;
#else
WiFiClient espClient;
#endif #endif
PubSubClient mqttClient(espClient); WiFiClient *espClient;
PubSubClient mqttClient;
namespace mqtt { namespace mqtt {
unsigned long last_sent_at = 0; unsigned long last_sent_at = 0;
unsigned long last_failed_at = 0; unsigned long last_failed_at = 0;
bool connected = false; bool connected = false;
char publish_topic[21]; // e.g. "CO2sensors/ESPxxxxxx\0" char publish_topic[42]; // "MQTT_TOPIC_PREFIX/ESPxxxxxx\0", e.g. "CO2sensors/ESPxxxxxx\0"
const char *json_sensor_format; const char *json_sensor_format;
char last_successful_publish[23] = ""; char last_successful_publish[23] = "";
void initialize(const char *sensorId) { void initialize(const char *sensorId) {
json_sensor_format = PSTR("{\"time\":\"%s\", \"co2\":%d, \"temp\":%.1f, \"rh\":%.1f}"); json_sensor_format = PSTR("{\"time\":\"%s\", \"co2\":%d, \"temp\":%.1f, \"rh\":%.1f}");
snprintf(publish_topic, sizeof(publish_topic), "CO2sensors/%s", sensorId); snprintf(publish_topic, sizeof(publish_topic), "%s%s", config::mqtt_topic_prefix, sensorId);
#if MQTT_ENCRYPTED
// The sensor doesn't check the fingerprint of the MQTT broker, because otherwise this fingerprint should be updated if (config::mqtt_encryption) {
// on the sensor every 3 months. The connection can still be encrypted, though: // The sensor doesn't check the fingerprint of the MQTT broker, because otherwise this fingerprint should be updated
espClient.setInsecure(); // If not available for ESP32, please update Arduino IDE / PlatformIO // on the sensor every 3 months. The connection can still be encrypted, though:
#endif WiFiClientSecure *secureClient = new WiFiClientSecure();
secureClient->setInsecure();
espClient = secureClient;
} else {
espClient = new WiFiClient();
}
mqttClient.setClient(*espClient);
mqttClient.setServer(config::mqtt_server, config::mqtt_port); mqttClient.setServer(config::mqtt_server, config::mqtt_port);
sensor_console::defineIntCommand("mqtt", setMQTTinterval, F("60 (Sets MQTT sending interval, in s)")); sensor_console::defineIntCommand("mqtt", setMQTTinterval, F("60 (Sets MQTT sending interval, in s)"));
...@@ -62,13 +60,15 @@ namespace mqtt { ...@@ -62,13 +60,15 @@ namespace mqtt {
} }
void publish(const char *timestamp, int16_t co2, float temperature, float humidity) { void publish(const char *timestamp, int16_t co2, float temperature, float humidity) {
if (WiFi.status() == WL_CONNECTED && mqttClient.connected()) { if (wifi::connected() && mqttClient.connected()) {
led_effects::onBoardLEDOn(); led_effects::onBoardLEDOn();
Serial.print(F("MQTT - Publishing message ... ")); Serial.print(F("MQTT - Publishing message to '"));
Serial.print(publish_topic);
Serial.print(F("' ... "));
char payload[75]; // Should be enough for json... char payload[75]; // Should be enough for json...
snprintf(payload, sizeof(payload), json_sensor_format, timestamp, co2, temperature, humidity); snprintf(payload, sizeof(payload), json_sensor_format, timestamp, co2, temperature, humidity);
// Topic is the same as clientID. e.g. 'CO2sensors/ESP3d03da' // Topic is 'MQTT_TOPIC_PREFIX/ESP123456'
if (mqttClient.publish(publish_topic, payload)) { if (mqttClient.publish(publish_topic, payload)) {
Serial.println(F("OK")); Serial.println(F("OK"));
ntp::getLocalTime(last_successful_publish); ntp::getLocalTime(last_successful_publish);
...@@ -108,17 +108,20 @@ namespace mqtt { ...@@ -108,17 +108,20 @@ namespace mqtt {
// It failed less than wait_after_fail ago. Not even trying. // It failed less than wait_after_fail ago. Not even trying.
return; return;
} }
if (WiFi.status() != WL_CONNECTED) { //NOTE: Sadly, WiFi.status is sometimes WL_CONNECTED even though it's really not if (!wifi::connected()) { //NOTE: Sadly, WiFi.status is sometimes WL_CONNECTED even though it's really not
// No WIFI // No WIFI
return; return;
} }
Serial.print(F("MQTT - Attempting connection to ")); Serial.print(F("MQTT - Attempting connection to "));
Serial.print(MQTT_SERVER); Serial.print(config::mqtt_server);
Serial.print(MQTT_ENCRYPTED ? F(" (Encrypted") : F(" (Unencrypted")); Serial.print(config::mqtt_encryption ? F(" (Encrypted") : F(" (Unencrypted"));
Serial.print(F(", port ")); Serial.print(F(", port "));
Serial.print(MQTT_PORT); Serial.print(config::mqtt_port);
Serial.print(F(") ...")); Serial.print(F(") "));
Serial.print(F("User:'"));
Serial.print(config::mqtt_user);
Serial.print(F("' ..."));
led_effects::onBoardLEDOn(); led_effects::onBoardLEDOn();
// Wait for connection, at most 15s (default) // Wait for connection, at most 15s (default)
...@@ -129,7 +132,7 @@ namespace mqtt { ...@@ -129,7 +132,7 @@ namespace mqtt {
if (connected) { if (connected) {
if (config::allow_mqtt_commands) { if (config::allow_mqtt_commands) {
char control_topic[60]; // Should be enough for "CO2sensors/ESPd03cc5/control" char control_topic[50]; // Should be enough for "MQTT_TOPIC_PREFIX/ESPd03cc5/control\0"
snprintf(control_topic, sizeof(control_topic), "%s/control", publish_topic); snprintf(control_topic, sizeof(control_topic), "%s/control", publish_topic);
mqttClient.subscribe(control_topic); mqttClient.subscribe(control_topic);
mqttClient.setCallback(controlSensorCallback); mqttClient.setCallback(controlSensorCallback);
...@@ -137,10 +140,15 @@ namespace mqtt { ...@@ -137,10 +140,15 @@ namespace mqtt {
Serial.println(F(" Connected.")); Serial.println(F(" Connected."));
last_failed_at = 0; last_failed_at = 0;
} else { } else {
// As defined in PubSubClient, between -4 and 5
const __FlashStringHelper *mqtt_statuses[] = { F("Connection timeout"), F("Connection lost"), F(
"Connection failed"), F("Disconnected"), F("Connected"), F("Bad protocol"), F("Bad client ID"), F(
"Unavailable"), F("Bad credentials"), F("Unauthorized") };
last_failed_at = seconds(); last_failed_at = seconds();
Serial.print(F(" Failed! Error code=")); Serial.print(mqtt_statuses[mqttClient.state() + 4]);
Serial.print("! (Code=");
Serial.print(mqttClient.state()); Serial.print(mqttClient.state());
Serial.print(F(". Will try again in ")); Serial.print(F("). Will try again in "));
Serial.print(config::wait_after_fail); Serial.print(config::wait_after_fail);
Serial.println("s."); Serial.println("s.");
} }
...@@ -170,7 +178,7 @@ namespace mqtt { ...@@ -170,7 +178,7 @@ namespace mqtt {
config::mqtt_sending_interval = sending_interval; config::mqtt_sending_interval = sending_interval;
Serial.print(F("Setting MQTT sending interval to : ")); Serial.print(F("Setting MQTT sending interval to : "));
Serial.print(config::mqtt_sending_interval); Serial.print(config::mqtt_sending_interval);
Serial.println("s."); Serial.println(F("s."));
led_effects::showKITTWheel(color::green, 1); led_effects::showKITTWheel(color::green, 1);
} }
...@@ -178,12 +186,12 @@ namespace mqtt { ...@@ -178,12 +186,12 @@ namespace mqtt {
// If the sensor can be reach by MQTT, it can answer with info about local_ip and ssid. // If the sensor can be reach by MQTT, it can answer with info about local_ip and ssid.
// The sensor will send the info to "CO2sensors/ESP123456/info". // The sensor will send the info to "CO2sensors/ESP123456/info".
void sendInfoAboutLocalNetwork() { void sendInfoAboutLocalNetwork() {
char info_topic[60]; // Should be enough for "CO2sensors/ESP123456/info" char info_topic[50]; // Should be enough for "MQTT_TOPIC_PREFIX/ESP123456/info"
snprintf(info_topic, sizeof(info_topic), "%s/info", publish_topic); snprintf(info_topic, sizeof(info_topic), "%s/info", publish_topic);
char payload[75]; // Should be enough for info json... char payload[75]; // Should be enough for info json...
const char *json_info_format = PSTR("{\"local_ip\":\"%s\", \"ssid\":\"%s\"}"); const char *json_info_format = PSTR("{\"local_ip\":\"%s\", \"ssid\":\"%s\"}");
snprintf(payload, sizeof(payload), json_info_format, wifi::local_ip, WIFI_SSID); snprintf(payload, sizeof(payload), json_info_format, wifi::local_ip, config::selected_ssid());
mqttClient.publish(info_topic, payload); mqttClient.publish(info_topic, payload);
} }
......
...@@ -3,15 +3,6 @@ ...@@ -3,15 +3,6 @@
#include <stdint.h> // For uint32_t & uint16_t #include <stdint.h> // For uint32_t & uint16_t
#include "config.h"
#if !defined(MQTT_ENCRYPTED)
# define MQTT_ENCRYPTED true // Old config files might not define it, and encryption was on by default.
#endif
namespace config {
extern uint16_t mqtt_sending_interval; // [s]
}
namespace mqtt { namespace mqtt {
extern char last_successful_publish[]; extern char last_successful_publish[];
extern bool connected; extern bool connected;
......
#include "ntp.h" #include "ntp.h"
#include "sensor_console.h" #include "sensor_console.h"
#include "config.h" #include "web_config.h"
#include <WiFiUdp.h> // required for NTP #include <WiFiUdp.h> // required for NTP
#include "src/lib/NTPClient/NTPClient.h" // NTP #include "src/lib/NTPClient/NTPClient.h" // NTP
namespace config {
const char *ntp_server = NTP_SERVER;
const long utc_offset_in_seconds = UTC_OFFSET_IN_SECONDS; // UTC+1
}
//NOTE: ESP32 sometimes couldn't access the NTP server, and every loop would take +1000ms //NOTE: ESP32 sometimes couldn't access the NTP server, and every loop would take +1000ms
// ifdefs could be used to define functions specific to ESP32, e.g. with configTime // ifdefs could be used to define functions specific to ESP32, e.g. with configTime
namespace ntp { namespace ntp {
WiFiUDP ntpUDP; WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, config::ntp_server, config::utc_offset_in_seconds, 60000UL); NTPClient timeClient(ntpUDP);
bool connected_at_least_once = false; bool connected_at_least_once = false;
void setLocalTime(int32_t unix_seconds); void setLocalTime(int32_t unix_seconds);
// Should be defined, even offline
void initialize() { void initialize() {
timeClient.begin(); timeClient.setTimeOffset((config::time_zone + config::daylight_saving_time) * 3600);
sensor_console::defineIntCommand("set_time", ntp::setLocalTime, F("1618829570 (Sets time to the given UNIX time)")); sensor_console::defineIntCommand("set_time", ntp::setLocalTime, F("1618829570 (Sets time to the given UNIX time)"));
} }
void connect(){
timeClient.setPoolServerName(config::ntp_server);
timeClient.setUpdateInterval(60000UL);
Serial.print("NTP - Trying to connect to : ");
Serial.println(config::ntp_server);
timeClient.begin();
}
void update() { void update() {
connected_at_least_once |= timeClient.update(); connected_at_least_once |= timeClient.update();
} }
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
#define AMPEL_TIME_H_INCLUDED #define AMPEL_TIME_H_INCLUDED
namespace ntp { namespace ntp {
extern bool connected_at_least_once;
void initialize(); void initialize();
void connect();
void update(); void update();
void getLocalTime(char *timestamp); void getLocalTime(char *timestamp);
} }
......
#include "sensor_console.h" #include "sensor_console.h"
namespace sensor_console { namespace sensor_console {
const uint8_t MAX_COMMANDS = 20; const uint8_t MAX_COMMANDS = 26;
const uint8_t MAX_COMMAND_SIZE = 30; const uint8_t MAX_COMMAND_SIZE = 40;
uint8_t commands_count = 0; uint8_t commands_count = 0;
...@@ -149,6 +149,12 @@ namespace sensor_console { ...@@ -149,6 +149,12 @@ namespace sensor_console {
} }
} }
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. * Tries to find the corresponding callback for a given command. Name and parameter type should fit.
*/ */
......
...@@ -12,7 +12,7 @@ namespace sensor_console { ...@@ -12,7 +12,7 @@ namespace sensor_console {
void defineIntCommand(const char *name, void (*function)(int32_t), 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 defineStringCommand(const char *name, void (*function)(char*), const __FlashStringHelper *doc_fstring);
void processSerialInput(const uint8_t in_byte); void checkSerialInput();
void execute(const char *command_line); void execute(const char *command_line);
} }
......
set(COMPONENT_SRCS
src/IotWebConf.cpp
src/IotWebConfMultipleWifi.cpp
src/IotWebConfOptionalGroup.cpp
src/IotWebConfParameter.cpp
src/IotWebConfESP32HTTPUpdateServer.cpp
)
set(COMPONENT_ADD_INCLUDEDIRS
src/
)
list(APPEND COMPONENT_REQUIRES "arduino")
register_component()
#ADD_DEFINITIONS(-DESP32)
list(APPEND DEFINITIONS "ESP32")
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