/*** * ____ ___ ____ _ _ * / ___/ _ \___ \ / \ _ __ ___ _ __ ___| | * | | | | | |__) | / _ \ | '_ ` _ \| '_ \ / _ \ | * | |__| |_| / __/ / ___ \| | | | | | |_) | __/ | * \____\___/_____| /_/__ \_\_| |_| |_| .__/ \___|_| _ * | | | |/ _|_ _| / ___|| |_ _ _| |_| |_ __ _ __ _ _ __| |_ * | |_| | |_ | | \___ \| __| | | | __| __/ _` |/ _` | '__| __| * | _ | _| | | ___) | |_| |_| | |_| || (_| | (_| | | | |_ * |_| |_|_| |_| |____/ \__|\__,_|\__|\__\__, |\__,_|_| \__| * |___/ */ #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. * * 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /***************************************************************** * Authors * *****************************************************************/ /* * Eric Duminil * Robert Otto * Myriam Guedey * Tobias Gabriel Erhart * Jonas Stave */ /***************************************************************** * 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'. */ /***************************************************************** * Setup * *****************************************************************/ void setup() { LedEffects::setupOnBoardLED(); LedEffects::onBoardLEDOff(); Serial.begin(BAUDS); pinMode(0, INPUT); // Flash button (used for forced calibration) LedEffects::setupRing(); sensor::initialize(); Serial.print(F("Sensor ID: ")); Serial.println(SENSOR_ID); Serial.print(F("Board : ")); Serial.println(BOARD); // Try to connect to Wi-Fi WiFiConnect(SENSOR_ID); Serial.print(F("WiFi STATUS: ")); Serial.println(WiFi.status()); if (WiFi.status() == WL_CONNECTED) { #ifdef HTTP web_server::initialize(); #endif ntp::initialize(); if (MDNS.begin(SENSOR_ID.c_str())) { // Start the mDNS responder for SENSOR_ID.local MDNS.addService("http", "tcp", 80); Serial.println(F("mDNS responder started")); } else { Serial.println(F("Error setting up MDNS responder!")); } #ifdef MQTT mqtt::initialize("CO2sensors/" + SENSOR_ID); #endif } csv_writer::initialize(); } /***************************************************************** * Main loop * *****************************************************************/ void loop() { //NOTE: Loop should never take more than 1000ms. Split in smaller methods and logic if needed. //TODO: Restart every day or week, in order to not let t0 overflow? uint32_t t0 = millis(); /** * USER INTERACTION */ keepServicesAlive(); // Short press for night mode, Long press for calibration. checkFlashButton(); /** * GET DATA */ bool freshData = sensor::scd30.dataAvailable(); // Alternative : close to time-step AND dataAvailable, to avoid asking the sensor too often. if (freshData) { sensor::co2 = sensor::scd30.getCO2(); sensor::temperature = sensor::scd30.getTemperature(); sensor::humidity = sensor::scd30.getHumidity(); } //NOTE: Data is available, but it's sometimes erroneous: the sensor outputs zero ppm but non-zero temperature and non-zero humidity. if (sensor::co2 <= 0) { // No measurement yet. Waiting. LedEffects::showWaitingLED(color::blue); return; } /** * Fresh data. Show it and send it if needed. */ if (freshData) { sensor::timestamp = ntp::getLocalTime(); Serial.println(sensor::timestamp); Serial.print(F("co2(ppm): ")); Serial.print(sensor::co2); Serial.print(F(" temp(C): ")); Serial.print(sensor::temperature); Serial.print(F(" humidity(%): ")); Serial.println(sensor::humidity); csv_writer::logIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity); #ifdef MQTT mqtt::publishIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity); #endif } if (sensor::co2 < 250) { // Sensor should be calibrated. LedEffects::showWaitingLED(color::magenta); return; } /** * Display data, even if it's "old" (with breathing). * Those effects include a short delay. */ if (sensor::co2 < 2000) { LedEffects::displayCO2color(sensor::co2); LedEffects::breathe(sensor::co2); } else { // >= 2000: entire ring blinks red LedEffects::redAlert(); } uint32_t duration = millis() - t0; if (duration > max_loop_duration) { max_loop_duration = duration; Serial.print("Max loop duration : "); Serial.print(max_loop_duration); Serial.println(" ms."); } } /** * 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 LedEffects::onBoardLEDOn(); delay(300); if (digitalRead(0)) { Serial.println(F("Flash has been pressed for a short time. Should toggle night mode.")); LedEffects::toggleNightMode(); } else { Serial.println(F("Flash has been pressed for a long time. Keep it pressed for calibration.")); if (LedEffects::countdownToZero() < 0) { sensor::startCalibrationProcess(); } } LedEffects::onBoardLEDOff(); } } void keepServicesAlive() { if (WiFi.status() == WL_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. MDNS.update(); #endif ntp::update(); // NTP client has its own timer. It will connect to NTP server every 60s. #ifdef HTTP web_server::update(); #endif #ifdef MQTT mqtt::keepConnection(); // MQTT client has its own timer. It will keep alive every 15s. #endif } }