Commit 1f7a20c1 authored by Eric Duminil's avatar Eric Duminil
Browse files

Merge branch 'develop'

parents da1f41c0 34496cfe
......@@ -10,7 +10,7 @@ The room should be ventilated as soon as one LED turns red.
The *CO<sub>2</sub> Ampel* can:
* Display CO2 concentration on LED ring.
* Display CO<sub>2</sub> concentration on LED ring.
* Allow calibration.
* Get current time over [NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol)
* Send data over [MQTT](https://en.wikipedia.org/wiki/MQTT).
......@@ -76,15 +76,15 @@ In Arduino IDE *Serial Monitor* or PlatformIO *Monitor*, type `help` + <kbd>Ente
* `calibrate` (Starts calibration process).
* `calibrate 600` (Starts calibration process, to given ppm).
* `calibrate! 600` (Calibrates right now, to given ppm).
* `co2 1500` (Sets co2 level, for debugging purposes).
* `co2 1500` (Sets CO<sub>2</sub> level, for debugging purposes).
* `color 0xFF0015` (Shows color, specified as RGB, for debugging).
* `csv 60` (Sets CSV writing interval, in s).
* `format_filesystem` (Deletes the whole filesystem).
* `free` (Displays available heap space).
* `led 0/1` (Turns LEDs on/off).
* `local_ip` (Displays local IP and current SSID).
* `lora 300` (Sets LoRaWAN sending interval, in s).
* `mqtt 60` (Sets MQTT sending interval, in s).
* `night_mode` (Toggles night mode on/off).
* `reset` (Restarts the ESP).
* `reset_scd` (Resets SCD30).
* `send_local_ip` (Sends local IP and SSID via MQTT. Can be useful to find sensor).
......
......@@ -33,6 +33,7 @@
#endif
#include "util.h"
#include "ntp.h"
#include "sensor_console.h"
#include "co2_sensor.h"
#include "led_effects.h"
......
......@@ -88,10 +88,7 @@ void setup() {
#ifdef AMPEL_WIFI
wifi::connect(ampel.sensorId);
Serial.print(F("WiFi - Status: "));
Serial.println(WiFi.status());
if (WiFi.status() == WL_CONNECTED) {
if (wifi::connected()) {
# ifdef AMPEL_HTTP
web_server::initialize();
# endif
......@@ -203,7 +200,7 @@ void checkFlashButton() {
void keepServicesAlive() {
#ifdef AMPEL_WIFI
if (WiFi.status() == WL_CONNECTED) {
if (wifi::connected()) {
# if defined(ESP8266)
//NOTE: Sadly, there seems to be a bug in the current MDNS implementation.
// It stops working after 2 minutes. And forcing a restart leads to a memory leak.
......
#include "co2_sensor.h"
#include "config.h"
#include "ntp.h"
#include "led_effects.h"
#include "sensor_console.h"
#include <Wire.h>
// The SCD30 from Sensirion is a high quality Nondispersive Infrared (NDIR) based CO₂ sensor capable of detecting 400 to 10000ppm with an accuracy of ±(30ppm+3%).
// https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library
#include "src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h" // From: http://librarymanager/All#SparkFun_SCD30
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).
......
#ifndef CO2_SENSOR_H_
#define CO2_SENSOR_H_
// The SCD30 from Sensirion is a high quality Nondispersive Infrared (NDIR) based CO₂ sensor capable of detecting 400 to 10000ppm with an accuracy of ±(30ppm+3%).
// https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library
#include "src/lib/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h" // From: http://librarymanager/All#SparkFun_SCD30
#include "config.h"
#include "led_effects.h"
#include "util.h"
#include "sensor_console.h"
#include <Wire.h>
#include <stdint.h> // For uint16_t
namespace config {
extern uint16_t measurement_timestep; // [s] Value between 2 and 1800 (range for SCD30 sensor)
......@@ -18,7 +11,6 @@ namespace config {
}
namespace sensor {
extern SCD30 scd30;
extern uint16_t co2;
extern float temperature;
extern float humidity;
......
......@@ -33,7 +33,6 @@
# define MEASUREMENT_TIMESTEP 60 // [s] Value between 2 and 1800 (range for SCD30 sensor)
// How often should measurements be appended to CSV ?
// Probably a good idea to use a multiple of MEASUREMENT_TIMESTEP, so that averages can be calculated
// Set to 0 if you want to send values after each measurement
// WARNING: Writing too often might damage the ESP memory
# define CSV_INTERVAL 300 // [s]
......@@ -54,7 +53,7 @@
// Should the sensor try to calibrate itself?
// Sensirion recommends 7 days of continuous readings with at least 1 hour a day of 'fresh air' for self-calibration to complete.
# define AUTO_CALIBRATE_SENSOR true // [true / false]
# define AUTO_CALIBRATE_SENSOR false // [true / false]
/**
* LEDs
......@@ -106,12 +105,12 @@
# define ALLOW_MQTT_COMMANDS false
// How often should measurements be sent to MQTT server?
// Probably a good idea to use a multiple of MEASUREMENT_TIMESTEP, so that averages can be calculated
// Set to 0 if you want to send values after each measurement
// # define MQTT_SENDING_INTERVAL MEASUREMENT_TIMESTEP * 5 // [s]
# define MQTT_SENDING_INTERVAL 60 // [s]
# define MQTT_SERVER "test.mosquitto.org" // MQTT server URL or IP address
# 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_USER ""
# define MQTT_PASSWORD ""
......@@ -120,10 +119,7 @@
*/
// 1) Requires "MCCI LoRaWAN LMIC library", which will be automatically used with PlatformIO but should be added in "Arduino IDE".
// 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
// 2) If you need to, region and transceiver type can be specified in lorawan.cpp. Default is "Europe 868"
// 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,
// e.g. on https://www.thethingsnetwork.org/docs/applications/
......
#include "csv_writer.h"
#include "config.h"
#include "ntp.h"
#include "led_effects.h"
#include "sensor_console.h"
namespace config {
// Values should be defined in config.h
uint16_t csv_interval = CSV_INTERVAL; // [s]
......
......@@ -11,11 +11,6 @@
# error Board should be either ESP8266 or ESP832
#endif
#include "config.h"
#include "util.h"
#include "led_effects.h"
#include "sensor_console.h"
namespace config {
extern uint16_t csv_interval; // [s]
}
......
#include "led_effects.h"
#include "config.h"
#include "sensor_console.h"
// Adafruit NeoPixel (Arduino library for controlling single-wire-based LED pixels and strip)
// https://github.com/adafruit/Adafruit_NeoPixel
// Documentation : http://adafruit.github.io/Adafruit_NeoPixel/html/class_adafruit___neo_pixel.html
#include "src/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.h"
/*****************************************************************
* Configuration *
*****************************************************************/
......@@ -12,7 +21,7 @@ namespace config {
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 uint16_t poor_air_quality_ppm = 1600; // Above this threshold, LED breathing effect is faster.
bool night_mode = false; //NOTE: Use a class instead? NightMode could then be another state.
bool display_led = true; // Will be set to false during "night mode".
#if !defined(LED_COUNT)
# define LED_COUNT 12
......@@ -78,7 +87,7 @@ namespace led_effects {
}
void showColor(int32_t color) {
config::night_mode = true; // In order to avoid overwriting the desired color next time CO2 is displayed
config::display_led = false; // In order to avoid overwriting the desired color next time CO2 is displayed
pixels.setBrightness(255);
pixels.fill(color);
pixels.show();
......@@ -88,17 +97,21 @@ namespace led_effects {
pixels.begin();
pixels.setBrightness(config::max_brightness);
LEDsOff();
sensor_console::defineCommand("night_mode", toggleNightMode, F("(Toggles night mode on/off)"));
sensor_console::defineIntCommand("led", turnLEDsOnOff, F("0/1 (Turns LEDs on/off)"));
sensor_console::defineIntCommand("color", showColor, F("0xFF0015 (Shows color, specified as RGB, for debugging)"));
}
void toggleNightMode() {
config::night_mode = !config::night_mode;
if (config::night_mode) {
Serial.println(F("NIGHT MODE!"));
LEDsOff();
turnLEDsOnOff(!config::display_led);
}
void turnLEDsOnOff(int32_t display_led) {
config::display_led = display_led;
if (config::display_led) {
Serial.println(F("LEDs are on!"));
} else {
Serial.println(F("DAY MODE!"));
Serial.println(F("Night mode!"));
LEDsOff();
}
}
......@@ -106,7 +119,7 @@ namespace led_effects {
void showWaitingLED(uint32_t color) {
using namespace config;
delay(80);
if (night_mode) {
if (!display_led) {
return;
}
static uint16_t kitt_offset = 0;
......@@ -162,7 +175,7 @@ namespace led_effects {
* Fills the whole ring with green, yellow, orange or black, depending on co2 input and CO2_TICKS.
*/
void displayCO2color(uint16_t co2) {
if (config::night_mode) {
if (!config::display_led) {
return;
}
pixels.setBrightness(config::max_brightness);
......@@ -177,7 +190,7 @@ namespace led_effects {
}
void showRainbowWheel(uint16_t duration_ms) {
if (config::night_mode) {
if (!config::display_led) {
return;
}
static uint16_t wheel_offset = 0;
......@@ -195,7 +208,7 @@ namespace led_effects {
}
void redAlert() {
if (config::night_mode) {
if (!config::display_led) {
onBoardLEDOn();
delay(500);
onBoardLEDOff();
......@@ -218,7 +231,7 @@ namespace led_effects {
* been released or after every LED has been turned off.
*/
bool countdownToZero() {
if (config::night_mode) {
if (!config::display_led) {
Serial.println(F("Night mode. Not doing anything."));
delay(1000); // Wait for a while, to avoid coming back to this function too many times when button is pressed.
return false;
......
#ifndef LED_EFFECTS_H_INCLUDED
#define LED_EFFECTS_H_INCLUDED
#include <Arduino.h>
#include "config.h"
#include "sensor_console.h"
// Adafruit NeoPixel (Arduino library for controlling single-wire-based LED pixels and strip)
// https://github.com/adafruit/Adafruit_NeoPixel
// Documentation : http://adafruit.github.io/Adafruit_NeoPixel/html/class_adafruit___neo_pixel.html
#include "src/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.h"
#include <stdint.h> // For uint32_t
namespace color {
const uint32_t red = 0xFF0000;
......@@ -22,6 +16,7 @@ namespace led_effects {
void onBoardLEDOff();
void onBoardLEDOn();
void toggleNightMode();
void turnLEDsOnOff(int32_t);
void LEDsOff();
void setupRing();
......
#include "lorawan.h"
#if defined(AMPEL_LORAWAN) && defined(ESP32)
#include "led_effects.h"
#include "sensor_console.h"
#include "util.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"
// Tested successfully with v3.2.0 and connected to a thethingsnetwork.org app.
#include <lmic.h>
#include <SPI.h>
#include <hal/hal.h>
#include <arduino_lmic_hal_boards.h>
namespace config {
#if defined(CFG_eu868)
const char *lorawan_frequency_plan = "Europe 868";
#elif defined(CFG_us915)
const char *lorawan_frequency_plan = "US 915";
#elif defined(CFG_au915)
const char *lorawan_frequency_plan = "Australia 915";
#elif defined(CFG_as923)
const char *lorawan_frequency_plan = "Asia 923";
#elif defined(CFG_kr920)
const char *lorawan_frequency_plan = "Korea 920";
#elif defined(CFG_in866)
const char *lorawan_frequency_plan = "India 866";
#else
# error "Region should be specified"
#endif
// Values should be defined in config.h
uint16_t lorawan_sending_interval = LORAWAN_SENDING_INTERVAL; // [s]
......@@ -36,7 +71,9 @@ namespace lorawan {
char last_transmission[23] = "";
void initialize() {
Serial.println(F("Starting LoRaWAN. Frequency plan : " LMIC_FREQUENCY_PLAN " MHz."));
Serial.print(F("Starting LoRaWAN. Frequency plan : "));
Serial.print(config::lorawan_frequency_plan);
Serial.println(F(" MHz."));
// More info about pin mapping : https://github.com/mcci-catena/arduino-lmic#pin-mapping
// Has been tested successfully with ESP32 TTGO LoRa32 V1, and might work with other ESP32+LoRa boards.
......@@ -51,7 +88,7 @@ namespace lorawan {
}
// Checks if OTAA is connected, or if payload should be sent.
// NOTE: while a transaction is in process (i.e. until the TXcomplete event has been received, no blocking code (e.g. delay loops etc.) are allowed, otherwise the LMIC/OS code might miss the event.
// NOTE: while a transaction is in process (i.e. until the TXcomplete event has been received), no blocking code (e.g. delay loops etc.) are allowed, otherwise the LMIC/OS code might miss the event.
// If this rule is not followed, a typical symptom is that the first send is ok and all following ones end with the 'TX not complete' failure.
void process() {
os_runloop_once();
......
......@@ -3,39 +3,15 @@
#include "config.h"
#if defined(AMPEL_LORAWAN) && defined(ESP32)
#include <Arduino.h>
// 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.
#include <lmic.h>
#include <hal/hal.h>
#include <arduino_lmic_hal_boards.h>
#include <SPI.h>
# if defined(AMPEL_LORAWAN) && defined(ESP32)
#include "led_effects.h"
#include "sensor_console.h"
#include "util.h"
#include <stdint.h> // For uint32_t & uint16_t
namespace config {
extern uint16_t lorawan_sending_interval; // [s]
extern const char *lorawan_frequency_plan; // e.g. "Europe 868"
}
#if defined(CFG_eu868)
# define LMIC_FREQUENCY_PLAN "Europe 868"
#elif defined(CFG_us915)
# define LMIC_FREQUENCY_PLAN "US 915"
#elif defined(CFG_au915)
# define LMIC_FREQUENCY_PLAN "Australia 915"
#elif defined(CFG_as923)
# define LMIC_FREQUENCY_PLAN "Asia 923"
#elif defined(CFG_kr920)
# define LMIC_FREQUENCY_PLAN "Korea 920"
#elif defined(CFG_in866)
# define LMIC_FREQUENCY_PLAN "India 866"
#else
# error "Region should be specified"
#endif
namespace lorawan {
extern bool waiting_for_confirmation;
extern bool connected;
......@@ -47,5 +23,5 @@ namespace lorawan {
void setLoRaInterval(int32_t sending_interval);
}
#endif
# endif
#endif
#include "mqtt.h"
#include "config.h"
#include "led_effects.h"
#include "sensor_console.h"
#include "wifi_util.h"
#include "ntp.h"
#include "src/lib/PubSubClient/src/PubSubClient.h"
#if defined(ESP8266)
# include <ESP8266WiFi.h>
#elif defined(ESP32)
# include <WiFi.h>
#endif
namespace config {
// Values should be defined in config.h
uint16_t mqtt_sending_interval = MQTT_SENDING_INTERVAL; // [s]
......@@ -12,10 +25,16 @@ namespace config {
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.
}
#if defined(ESP32)
# include <WiFiClientSecure.h>
#endif
#if MQTT_ENCRYPTED
# if defined(ESP32)
# include <WiFiClientSecure.h>
# endif
WiFiClientSecure espClient;
#else
WiFiClient espClient;
#endif
PubSubClient mqttClient(espClient);
namespace mqtt {
......@@ -30,9 +49,11 @@ namespace mqtt {
void initialize(const char *sensorId) {
json_sensor_format = PSTR("{\"time\":\"%s\", \"co2\":%d, \"temp\":%.1f, \"rh\":%.1f}");
snprintf(publish_topic, sizeof(publish_topic), "CO2sensors/%s", sensorId);
#if MQTT_ENCRYPTED
// The sensor doesn't check the fingerprint of the MQTT broker, because otherwise this fingerprint should be updated
// on the sensor every 3 months. The connection can still be encrypted, though:
espClient.setInsecure(); // If not available for ESP32, please update Arduino IDE / PlatformIO
#endif
mqttClient.setServer(config::mqtt_server, config::mqtt_port);
sensor_console::defineIntCommand("mqtt", setMQTTinterval, F("60 (Sets MQTT sending interval, in s)"));
......@@ -91,7 +112,13 @@ namespace mqtt {
// No WIFI
return;
}
Serial.print(F("MQTT - Attempting connection..."));
Serial.print(F("MQTT - Attempting connection to "));
Serial.print(MQTT_SERVER);
Serial.print(MQTT_ENCRYPTED ? F(" (Encrypted") : F(" (Unencrypted"));
Serial.print(F(", port "));
Serial.print(MQTT_PORT);
Serial.print(F(") ..."));
led_effects::onBoardLEDOn();
// Wait for connection, at most 15s (default)
......
#ifndef MQTT_H_INCLUDED
#define MQTT_H_INCLUDED
#include <Arduino.h>
#include <stdint.h> // For uint32_t & uint16_t
#include "config.h"
#include "led_effects.h"
#include "sensor_console.h"
#include "src/lib/PubSubClient/src/PubSubClient.h"
#include "wifi_util.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]
......
#include "ntp.h"
#include "sensor_console.h"
#include "config.h"
#include <WiFiUdp.h> // required for 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
// ifdefs could be used to define functions specific to ESP32, e.g. with configTime
namespace ntp {
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, config::ntp_server, config::utc_offset_in_seconds, 60000UL);
bool connected_at_least_once = false;
void setLocalTime(int32_t unix_seconds);
void initialize() {
timeClient.begin();
sensor_console::defineIntCommand("set_time", ntp::setLocalTime, F("1618829570 (Sets time to the given UNIX time)"));
}
void update() {
connected_at_least_once |= timeClient.update();
}
void getLocalTime(char *timestamp) {
timeClient.getFormattedDate(timestamp);
}
void setLocalTime(int32_t unix_seconds) {
char time[23];
timeClient.getFormattedDate(time);
Serial.print(F("Current time : "));
Serial.println(time);
if (connected_at_least_once) {
Serial.println(F("NTP update already happened. Not changing anything."));
return;
}
Serial.print(F("Setting UNIX time to : "));
Serial.println(unix_seconds);
timeClient.setEpochTime(unix_seconds - seconds());
timeClient.getFormattedDate(time);
Serial.print(F("Current time : "));
Serial.println(time);
}
}
#ifndef AMPEL_TIME_H_INCLUDED
#define AMPEL_TIME_H_INCLUDED
namespace ntp {
void initialize();
void update();
void getLocalTime(char *timestamp);
}
//NOTE: Only use seconds() for duration comparison, not timestamps comparison. Otherwise, problems happen when millis roll over.
#define seconds() (millis() / 1000UL)
#endif
#ifndef SENSOR_CONSOLE_H_INCLUDED
#define SENSOR_CONSOLE_H_INCLUDED
#include <Arduino.h>
#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, night mode, ...)
* 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.
*/
......@@ -12,7 +12,7 @@ namespace sensor_console {
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 processSerialInput(const byte in_byte);
void processSerialInput(const uint8_t in_byte);
void execute(const char *command_line);
}
......
......@@ -56,6 +56,10 @@ Compatibility notes: Port A is not supported on any AVR processors at this time
- ESP8266 any speed
- ESP32 any speed
- Nordic nRF52 (Adafruit Feather nRF52), nRF51 (micro:bit)
- Infineon XMC1100 BootKit @ 32 MHz
- Infineon XMC1100 2Go @ 32 MHz
- Infineon XMC1300 BootKit @ 32 MHz
- Infineon XMC4700 RelaxKit, XMC4800 RelaxKit, XMC4800 IoT Amazon FreeRTOS Kit @ 144 MHz
Check forks for other architectures not listed here!
......
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