/*** * ____ ___ ____ _ _ * / ___/ _ \___ \ / \ _ __ ___ _ __ ___| | * | | | | | |__) | / _ \ | '_ ` _ \| '_ \ / _ \ | * | |__| |_| / __/ / ___ \| | | | | | |_) | __/ | * \____\___/_____| /_/__ \_\_| |_| |_| .__/ \___|_| _ * | | | |/ _|_ _| / ___|| |_ _ _| |_| |_ __ _ __ _ _ __| |_ * | |_| | |_ | | \___ \| __| | | | __| __/ _` |/ _` | '__| __| * | _ | _| | | ___) | |_| |_| | |_| || (_| | (_| | | | |_ * |_| |_|_| |_| |____/ \__|\__,_|\__|\__\__, |\__,_|_| \__| * |___/ */ #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 * Michael Käppler */ /***************************************************************** * Configuration * *****************************************************************/ /* * Please define settings in 'config.h'. * There's an example config file called 'config.example.h'. * You can copy 'config.public.h' (stored in Git) to 'config.h' (not stored in Git), * and define your credentials and parameters in 'config.h'. */ /***************************************************************** * Setup * *****************************************************************/ void setup() { led_effects::setupOnBoardLED(); led_effects::onBoardLEDOff(); Serial.begin(BAUDS); pinMode(buttonPin, INPUT); // Flash button (used for forced calibration) Serial.println(); Serial.print(F("Sensor ID: ")); Serial.println(ampel.sensorId); Serial.print(F("MAC : ")); Serial.println(ampel.macAddress); Serial.print(F("Board : ")); Serial.println(ampel.board); Serial.print(F("Firmware : ")); Serial.println(ampel.version); led_effects::setupRing(); led_effects::showRainbowWheel(5000); sensor::initialize(); #ifdef AMPEL_CSV csv_writer::initialize(ampel.sensorId); #endif #ifdef AMPEL_WIFI wifi::connect(ampel.sensorId); Serial.print(F("WiFi - Status: ")); Serial.println(WiFi.status()); if (WiFi.status() == WL_CONNECTED) { # ifdef AMPEL_HTTP web_server::initialize(); # endif ntp::initialize(); if (MDNS.begin(ampel.sensorId)) { // 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 AMPEL_MQTT mqtt::initialize(ampel.sensorId); # endif } #endif #if defined(AMPEL_LORAWAN) && defined(ESP32) lorawan::initialize(); #endif } /***************************************************************** * Helper functions * *****************************************************************/ void keepServicesAlive(); void checkFlashButton(); void checkSerialInput(); /***************************************************************** * Main loop * *****************************************************************/ void loop() { #if defined(AMPEL_LORAWAN) && defined(ESP32) //LMIC Library seems to be very sensitive to timing issues, so run it first. lorawan::process(); if (lorawan::waiting_for_confirmation) { // If node is waiting for join confirmation from Gateway, nothing else should run. return; } #endif //NOTE: Loop should never take more than 1000ms. Split in smaller methods and logic if needed. //NOTE: Only use millis() for duration comparison, not timestamps comparison. Otherwise, problems happen when millis roll over. uint32_t t0 = millis(); keepServicesAlive(); // Short press for night mode, Long press for calibration. checkFlashButton(); checkSerialInput(); if (sensor::processData()) { #ifdef AMPEL_CSV csv_writer::logIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity); #endif #if defined(AMPEL_WIFI) && defined(AMPEL_MQTT) mqtt::publishIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity); #endif #if defined(AMPEL_LORAWAN) && defined(ESP32) lorawan::preparePayloadIfTimeHasCome(sensor::co2, sensor::temperature, sensor::humidity); #endif } uint32_t duration = millis() - t0; if (duration > ampel.max_loop_duration) { ampel.max_loop_duration = duration; Serial.print(F("Debug - Max loop duration : ")); Serial.print(ampel.max_loop_duration); Serial.println(F(" ms.")); } } void checkSerialInput() { while (Serial.available() > 0) { sensor_console::processSerialInput(Serial.read()); } } /** * Checks if flash button has been pressed: * If not, do nothing. * If short press, toggle LED display. * If long press, start calibration process. */ void checkFlashButton() { if (!digitalRead(0)) { // Button has been pressed led_effects::onBoardLEDOn(); delay(300); if (digitalRead(0)) { Serial.println(F("Flash has been pressed for a short time. Should toggle night mode.")); led_effects::toggleNightMode(); } else { Serial.println(F("Flash has been pressed for a long time. Keep it pressed for calibration.")); if (led_effects::countdownToZero()) { Serial.println(F("You can now release the button.")); sensor::startCalibrationProcess(); led_effects::showKITTWheel(color::red, 2); } } led_effects::onBoardLEDOff(); } } void keepServicesAlive() { #ifdef AMPEL_WIFI 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 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 }