/*** * ____ ___ ____ _ _ * / ___/ _ \___ \ / \ _ __ ___ _ __ ___| | * | | | | | |__) | / _ \ | '_ ` _ \| '_ \ / _ \ | * | |__| |_| / __/ / ___ \| | | | | | |_) | __/ | * \____\___/_____| /_/__ \_\_| |_| |_| .__/ \___|_| _ * | | | |/ _|_ _| / ___|| |_ _ _| |_| |_ __ _ __ _ _ __| |_ * | |_| | |_ | | \___ \| __| | | | __| __/ _` |/ _` | '__| __| * | _ | _| | | ___) | |_| |_| | |_| || (_| | (_| | | | |_ * |_| |_|_| |_| |____/ \__|\__,_|\__|\__\__, |\__,_|_| \__| * |___/ */ #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(config::bauds); web_config::initialize(); web_config::setWifiConnectionCallback(wifiConnected); web_config::setWifiFailCallback(wifiFailed); pinMode(0, INPUT); // Flash button (used for forced calibration) Serial.println(); Serial.print(F("Sensor ID: ")); Serial.println(ampel.sensorId); Serial.print(F("Name : ")); Serial.println(config::ampel_name()); Serial.print(F("MAC : ")); Serial.println(ampel.macAddress); Serial.print(F("Board : ")); Serial.println(ampel.board); Serial.print(F("Firmware : ")); Serial.println(ampel.version); led_effects::setupRing(); sensor::initialize(); csv_writer::initialize(config::ampel_name()); ntp::initialize(); if (config::is_wifi_on) { wifi::defineCommands(); web_server::definePages(); wifi::tryConnection(); } #if defined(ESP32) if (config::is_lorawan_active()) { lorawan::initialize(); } #endif } /***************************************************************** * Main loop * *****************************************************************/ void loop() { #if defined(ESP32) if (config::is_lorawan_active()) { //LMIC Library seems to be very sensitive to timing issues, so run it first. lorawan::process(); if (lorawan::waiting_for_confirmation) { // If node is waiting for join confirmation from Gateway, nothing else should run. return; } } #endif //NOTE: Loop should never take more than 1000ms. Split in smaller methods and logic if needed. //NOTE: Only use millis() for duration comparison, not timestamps comparison. Otherwise, problems happen when millis roll over. uint32_t t0 = millis(); keepServicesAlive(); // Short press for night mode, Long press for calibration. checkFlashButton(); sensor_console::checkSerialInput(); if (sensor::processData()) { if (config::is_csv_active()) { csv_writer::logIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity); } if (config::is_wifi_on && config::is_mqtt_active()) { mqtt::publishIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity); } #if defined(ESP32) if (config::is_lorawan_active()) { lorawan::preparePayloadIfTimeHasCome(sensor::co2, sensor::temperature, sensor::humidity); } #endif } uint32_t duration = millis() - t0; if (duration > ampel.max_loop_duration) { ampel.max_loop_duration = duration; Serial.print(F("Debug - Max loop duration : ")); Serial.print(ampel.max_loop_duration); Serial.println(F(" ms.")); } } /***************************************************************** * Callbacks * *****************************************************************/ void wifiConnected() { led_effects::showKITTWheel(color::green); Serial.println(); Serial.print(F("WiFi - Connected to ")); Serial.print(WiFi.SSID()); Serial.print(F(", IP address: ")); IPAddress address = WiFi.localIP(); snprintf(wifi::local_ip, sizeof(wifi::local_ip), "%d.%d.%d.%d", address[0], address[1], address[2], address[3]); Serial.println(wifi::local_ip); ntp::connect(); if (config::is_mqtt_active()) { mqtt::initialize(ampel.sensorId); } 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.print(F("WiFi - Could not connect to ")); Serial.println(config::selected_ssid()); led_effects::showKITTWheel(color::red); } /***************************************************************** * Helper functions * *****************************************************************/ /** * Checks if flash button has been pressed: * If not, do nothing. * If short press, toggle LED display. * If long press, start calibration process. */ void checkFlashButton() { if (!digitalRead(0)) { // Button has been pressed led_effects::onBoardLEDOn(); delay(300); if (digitalRead(0)) { Serial.println(F("Flash has been pressed for a short time. Should toggle night mode.")); led_effects::toggleNightMode(); //NOTE: Start Access Point instead? } else { Serial.println(F("Flash has been pressed for a long time. Keep it pressed for calibration.")); if (led_effects::countdownToZero()) { Serial.println(F("You can now release the button.")); sensor::startCalibrationProcess(); led_effects::showKITTWheel(color::red, 2); } } led_effects::onBoardLEDOff(); } } void keepServicesAlive() { if (config::is_wifi_on) { web_config::update(); if (wifi::connected()) { ntp::update(); // NTP client has its own timer. It will connect to NTP server every 60s. if (config::is_mqtt_active()) { mqtt::keepConnection(); // MQTT client has its own timer. It will keep alive every 15s. } } } }