ampel-firmware.ino 7.62 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
/***
 *       ____ ___ ____       _                         _
 *      / ___/ _ \___ \     / \   _ __ ___  _ __   ___| |
 *     | |  | | | |__) |   / _ \ | '_ ` _ \| '_ \ / _ \ |
 *     | |__| |_| / __/   / ___ \| | | | | | |_) |  __/ |
 *      \____\___/_____| /_/__ \_\_| |_| |_| .__/ \___|_|         _
 *     | | | |/ _|_   _| / ___|| |_ _   _| |_| |_ __ _  __ _ _ __| |_
 *     | |_| | |_  | |   \___ \| __| | | | __| __/ _` |/ _` | '__| __|
 *     |  _  |  _| | |    ___) | |_| |_| | |_| || (_| | (_| | |  | |_
 *     |_| |_|_|   |_|   |____/ \__|\__,_|\__|\__\__, |\__,_|_|   \__|
 *                                               |___/
 */

14
#include "ampel-firmware.h"
15
16
17
18
19

/*****************************************************************
 * GPL License                                                   *
 *****************************************************************/
/*
Eric Duminil's avatar
Eric Duminil committed
20
21
 * 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 )
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
 * 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 <http://www.gnu.org/licenses/>.
 */

/*****************************************************************
 * Authors                                                       *
 *****************************************************************/
/*
 * Eric Duminil
 * Robert Otto
 * Myriam Guedey
 * Tobias Gabriel Erhart
 * Jonas Stave
46
 * Michael Käppler
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
 */

/*****************************************************************
 * 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() {
63
64
  led_effects::setupOnBoardLED();
  led_effects::onBoardLEDOff();
65

Eric Duminil's avatar
Eric Duminil committed
66
  Serial.begin(config::bauds);
67

68
69
  web_config::initialize();

Eric Duminil's avatar
Eric Duminil committed
70
71
72
  web_config::setWifiConnectionCallback(wifiConnected);

  web_config::setWifiFailCallback(wifiFailed);
Eric Duminil's avatar
Eric Duminil committed
73

74
  pinMode(0, INPUT); // Flash button (used for forced calibration)
75

Eric Duminil's avatar
Eric Duminil committed
76
  Serial.println();
77
  Serial.print(F("Sensor ID: "));
78
  Serial.println(ampel.sensorId);
79
80
  Serial.print(F("MAC      : "));
  Serial.println(ampel.macAddress);
81
  Serial.print(F("Board    : "));
Eric Duminil's avatar
Eric Duminil committed
82
  Serial.println(ampel.board);
Eric Duminil's avatar
Eric Duminil committed
83
84
85
86
87
88
  Serial.print(F("Firmware : "));
  Serial.println(ampel.version);

  led_effects::setupRing();

  sensor::initialize();
89

Eric Duminil's avatar
Eric Duminil committed
90
91
  csv_writer::initialize(ampel.sensorId);

Eric Duminil's avatar
Eric Duminil committed
92
93
94
  if (config::wifi_active) {
    wifi::defineCommands();
    web_server::definePages();
Eric Duminil's avatar
Eric Duminil committed
95
    wifi::tryConnection();
Eric Duminil's avatar
Eric Duminil committed
96
  }
Eric Duminil's avatar
Eric Duminil committed
97

Eric Duminil's avatar
Eric Duminil committed
98
99
100
101
#if defined(ESP32)
  if (config::lorawan_active()) {
    lorawan::initialize();
  }
Eric Duminil's avatar
Eric Duminil committed
102
#endif
103
104
105
106
107
108
}

/*****************************************************************
 * Main loop                                                     *
 *****************************************************************/
void loop() {
Eric Duminil's avatar
Eric Duminil committed
109
110
111
112
113
114
115
116
117
#if defined(ESP32)
  if (config::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;
    }
Eric Duminil's avatar
Eric Duminil committed
118
119
  }
#endif
120
  //NOTE: Loop should never take more than 1000ms. Split in smaller methods and logic if needed.
Eric Duminil's avatar
Notes    
Eric Duminil committed
121
  //NOTE: Only use millis() for duration comparison, not timestamps comparison. Otherwise, problems happen when millis roll over.
122
123
124
  uint32_t t0 = millis();

  keepServicesAlive();
125

126
127
128
  // Short press for night mode, Long press for calibration.
  checkFlashButton();

Eric Duminil's avatar
Eric Duminil committed
129
  checkSerialInput();
130

Eric Duminil's avatar
Eric Duminil committed
131
  if (sensor::processData()) {
132
133
134
    if (config::csv_active()) {
      csv_writer::logIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity);
    }
Eric Duminil's avatar
Eric Duminil committed
135

Eric Duminil's avatar
Eric Duminil committed
136
    if (config::wifi_active && config::mqtt_active()) {
Eric Duminil's avatar
Eric Duminil committed
137
138
      mqtt::publishIfTimeHasCome(sensor::timestamp, sensor::co2, sensor::temperature, sensor::humidity);
    }
Eric Duminil's avatar
Eric Duminil committed
139

Eric Duminil's avatar
Eric Duminil committed
140
141
142
143
#if defined(ESP32)
    if (config::lorawan_active()) {
      lorawan::preparePayloadIfTimeHasCome(sensor::co2, sensor::temperature, sensor::humidity);
    }
Eric Duminil's avatar
Eric Duminil committed
144
145
#endif
  }
146
147

  uint32_t duration = millis() - t0;
Eric Duminil's avatar
Eric Duminil committed
148
149
  if (duration > ampel.max_loop_duration) {
    ampel.max_loop_duration = duration;
Eric Duminil's avatar
Eric Duminil committed
150
    Serial.print(F("Debug - Max loop duration : "));
Eric Duminil's avatar
Eric Duminil committed
151
    Serial.print(ampel.max_loop_duration);
152
    Serial.println(F(" ms."));
153
154
155
  }
}

Eric Duminil's avatar
Eric Duminil committed
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*****************************************************************
 * Callbacks                                                     *
 *****************************************************************/
void wifiConnected() {
  led_effects::showKITTWheel(color::green);
  Serial.println();
  Serial.print(F("WiFi - Connected! 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::initialize();

  if (config::mqtt_active()) {
    mqtt::initialize(ampel.sensorId);
  }

  Serial.print(F("You can access this sensor via http://"));
  Serial.print(ampel.sensorId);
  Serial.print(F(".local (might be unstable) or http://"));
  Serial.println(WiFi.localIP());
}

void wifiFailed() {
  Serial.print(F("WiFi - Could not connect to "));
  Serial.println(config::selected_ssid()); //TODO: Rename
  led_effects::showKITTWheel(color::red);
}

/*****************************************************************
 * Helper functions                                              *
 *****************************************************************/

Eric Duminil's avatar
Eric Duminil committed
189
190
191
192
193
194
void checkSerialInput() {
  while (Serial.available() > 0) {
    sensor_console::processSerialInput(Serial.read());
  }
}

195
196
197
198
199
200
201
202
/**
 * 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
203
    led_effects::onBoardLEDOn();
204
205
206
    delay(300);
    if (digitalRead(0)) {
      Serial.println(F("Flash has been pressed for a short time. Should toggle night mode."));
207
      led_effects::toggleNightMode();
Eric Duminil's avatar
Eric Duminil committed
208
      //NOTE: Start Access Point instead?
209
210
    } else {
      Serial.println(F("Flash has been pressed for a long time. Keep it pressed for calibration."));
211
      if (led_effects::countdownToZero()) {
212
        Serial.println(F("You can now release the button."));
213
        sensor::startCalibrationProcess();
214
        led_effects::showKITTWheel(color::red, 2);
215
216
      }
    }
217
    led_effects::onBoardLEDOff();
218
219
220
221
  }
}

void keepServicesAlive() {
Eric Duminil's avatar
Eric Duminil committed
222
223
224
225
226
227
228
  if (config::wifi_active) {
    web_config::update();
    if (wifi::connected()) {
      ntp::update(); // NTP client has its own timer. It will connect to NTP server every 60s.
      if (config::mqtt_active()) {
        mqtt::keepConnection(); // MQTT client has its own timer. It will keep alive every 15s.
      }
Eric Duminil's avatar
Eric Duminil committed
229
    }
Eric Duminil's avatar
Eric Duminil committed
230
  }
231
}