-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.cpp
506 lines (419 loc) · 13.2 KB
/
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <AsyncElegantOTA.h>
#include "SPIFFS.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "DHT.h"
#include "max6675.h"
#if __has_include("env.h")
// For local development (rename env-template.h to env.h and type your WiFi credentials there)
#include <env.h>
#else
// For GitHub Actions
// WiFi credentials
#define WIFI_SSID WSSID
#define WIFI_PASSWORD WPASS
#endif
#define LCD_SDA 21
#define LCD_SCL 22
#define MAX_SCK 5
#define MAX_CS 23
#define MAX_SO 19
#define DHT_PIN 18
#define MOTOR1_PIN 25 // 25, but internal LED (2) for debugging
#define MOTOR2_PIN 26
#define MOTOR3_PIN 27
#define BUZZER_PIN 14
#define TIME_A 36
#define TIME_B 34
#define TIME_C 35
#define TIME_ADDER 12
#define TIME_REDUCER 13
AsyncWebServer server(80); // Create AsyncWebServer object on port 80
AsyncEventSource events("/events"); // Create an Event Source on /events
// As a thumb rule, 32 bytes for every key/value pair inside the json
StaticJsonDocument<64> readings; // JSON Variable to Hold Sensor Readings
StaticJsonDocument<64> timer; // JSON Variable to Hold Time Values
StaticJsonDocument<128> states; // JSON Variable to Hold Motor States Values
LiquidCrystal_I2C lcd(0x27, 16, 2); // addr, width (16), height(2) -> 16x2 LCD
DHT dht(DHT_PIN, DHT22); // PIN, MODEL
MAX6675 thermocouple(MAX_SCK, MAX_CS, MAX_SO); // SCK, CS, SO
int temperature, humidity; // Hold the current value of the temperature & humidity
const char *mainTitle = "Tostador ";
int timerCount; // Used to count the number of timers that have run
int counter; // Used to count (decrease) from totalTimeInSeconds to 0
int totalTimeInSeconds; // Used to hold the total number of seconds to run
bool timerIsOn = false; // Represent an active timer
bool timerResponseIsActive = false; // Represent a timer response (after a timer finishes) is active
bool isTimeA, isTimeB, isTimeC; // Represent the selection of a timer configuration for the 3-state switch. Only one is true
int lastMillis = 0; // Used to software dounce the push buttons for timer control
bool motors23Activated = false; // Used to turn on the motors 2 & 3 only once every timer response
const int MAX_TEMP_LIMIT = 1000; // Set a large value for max temperature limit
const float TIMER_DURATION_DEBUG = 0; // Set a default timer duration for debugging. Set 0 for production
// Get Sensor Readings and return JSON object
String getSensorReadings()
{
// Update readings
humidity = (int)dht.readHumidity();
temperature = (int)thermocouple.readCelsius();
// LCD
lcd.setCursor(0, 1); // Set the cursor to column 0, row 1
lcd.printf("T: %dC H: %d%% ", temperature, humidity);
// JSON
readings.clear();
readings["temperature"] = String(temperature);
readings["humidity"] = String(humidity);
String json;
serializeJson(readings, json);
return json;
}
// Get Time Values and return JSON object
String getTimeValues()
{
// JSON
timer.clear();
timer["total"] = String(totalTimeInSeconds);
timer["time"] = String(counter);
String json;
serializeJson(timer, json);
return json;
}
// Get Motor States and return JSON object
String getMotorStates()
{
states.clear();
states["motor1"] = !!digitalRead(MOTOR1_PIN);
states["motor2"] = !!digitalRead(MOTOR2_PIN);
states["motor3"] = !!digitalRead(MOTOR3_PIN);
String json;
serializeJson(states, json);
return json;
}
// Initialize SPIFFS
void initSPIFFS()
{
if (!SPIFFS.begin(true))
{
Serial.println("An Error has occurred while mounting SPIFFS");
return;
}
}
// Initialize WiFi
void initWifi(const char *ssid, const char *password)
{
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.println(".");
}
Serial.print("IP: ");
Serial.println(WiFi.localIP());
lcd.setCursor(0, 1);
lcd.print(WiFi.localIP());
}
// Initialize LCD
void initLCD(const char *title)
{
Wire.begin(LCD_SDA, LCD_SCL);
lcd.init();
lcd.backlight();
lcd.print(title);
}
// Initialize Server
void initServer()
{
// Web Server Root URL
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->send(SPIFFS, "/index.html", "text/html"); });
server.serveStatic("/", SPIFFS, "/");
// HTTP API for remote reset
server.on("/reset", HTTP_POST, [](AsyncWebServerRequest *request)
{
request->send(200, "text/plain", "Reseting ESP32...");
ESP.restart(); });
// Request for the latest sensor readings
server.on("/data", HTTP_GET, [](AsyncWebServerRequest *request)
{
DynamicJsonDocument data(256);
// Create the objects
StaticJsonDocument<64> timerValues;
StaticJsonDocument<64> readingsValues;
StaticJsonDocument<128> statesValues;
// Get values into objects
deserializeJson(timerValues, getTimeValues());
deserializeJson(readingsValues, getSensorReadings());
deserializeJson(statesValues, getMotorStates());
// Add objects to the DynamicJsonDocument
data["timer"] = timerValues.as<JsonVariant>();
data["readings"] = readingsValues.as<JsonVariant>();
data["states"] = statesValues.as<JsonVariant>();
String json;
serializeJson(data, json);
request->send(200, "application/json", json);
json = String(); });
// Update the latest status of the motors states
server.on(
"/motors", HTTP_POST, [](AsyncWebServerRequest *request) {}, NULL,
[](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)
{
StaticJsonDocument<32> response;
DeserializationError error = deserializeJson(response, (const char *)data, len);
if (!error)
{
if (response.containsKey("motor1"))
{
digitalWrite(MOTOR1_PIN, response["motor1"].as<bool>());
}
if (response.containsKey("motor2"))
{
bool motor2state = response["motor2"].as<bool>();
digitalWrite(MOTOR2_PIN, motor2state);
// When motor2 or motor3 are manually turned off, then we reset the timerResponse so another timer can turn both motors on again
if (motor2state == false)
{
motors23Activated = false;
timerResponseIsActive = false;
}
}
if (response.containsKey("motor3"))
{
bool motor3state = response["motor3"].as<bool>();
digitalWrite(MOTOR3_PIN, motor3state);
if (motor3state == false)
{
motors23Activated = false;
timerResponseIsActive = false;
}
}
events.send(getMotorStates().c_str(), "states", millis());
request->send(200, "text/plain", "ok");
}
else
{
request->send(404, "text/plain", error.c_str());
}
});
// Add or reduce 60sec and update the timer
server.on(
"/time", HTTP_POST, [](AsyncWebServerRequest *request) {}, NULL,
[](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)
{
StaticJsonDocument<64> response;
DeserializationError error = deserializeJson(response, (const char *)data, len);
if (!error)
{
if (response.containsKey("time") && response.containsKey("action"))
{
String action = response["action"].as<String>();
int time_in_seconds = response["time"].as<int>();
if (action == "add")
{
counter += time_in_seconds;
totalTimeInSeconds += time_in_seconds;
timerIsOn = true;
timerCount += 1;
}
else if (action == "reduce")
{
counter -= time_in_seconds;
}
}
events.send(getTimeValues().c_str(), "timer", millis());
request->send(200, "text/plain", "ok");
}
else
{
request->send(404, "text/plain", error.c_str());
}
});
events.onConnect([](AsyncEventSourceClient *client)
{
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000); });
server.addHandler(&events);
// Start OTA service
AsyncElegantOTA.begin(&server);
// Start server
server.begin();
}
// Format seconds into mm:ss
String formatTime(int minutes, int seconds)
{
String time;
time = (minutes < 10) ? time + String("0") + String(minutes) : time + String(minutes);
time = time + String(":");
time = (seconds < 10) ? time + String("0") + String(seconds) : time + String(seconds);
if (isTimeA)
{
time = time + String(" Mani"); // TODO: handle accents
}
else if (isTimeB)
{
time = time + String(" Cafe");
}
else if (isTimeC)
{
time = time + String(" Cacao");
}
time = time + String(" ");
return time;
}
// Make noise with the buzzer
void handleBuzzer()
{
tone(BUZZER_PIN, 440);
// tone(BUZZER_PIN, 494, 1000);
// tone(BUZZER_PIN, 523, 1000);
}
// Handle all timer logic with the 3-state switch
void handleTimerAndResponse()
{
if (timerCount > 0 && timerIsOn && counter >= 0)
{
int minutes = floor(counter / 60);
int remainingSeconds = counter - minutes * 60;
lcd.setCursor(0, 0);
lcd.print(formatTime(minutes, remainingSeconds));
counter--;
}
if (counter < 0)
{
timerIsOn = false;
timerResponseIsActive = true;
totalTimeInSeconds = 0;
counter = 0;
// Print the title again
lcd.setCursor(0, 0);
lcd.print(mainTitle);
}
// When switch is moved to OFF, then turn off the response
if (!isTimeA && !isTimeB && !isTimeC)
{
timerResponseIsActive = false;
motors23Activated = false;
noTone(BUZZER_PIN);
}
// If there is a switch after the counter finished, handle the response
if (timerResponseIsActive)
{
handleBuzzer();
// Only turn the motors once every timer response
if (!motors23Activated)
{
digitalWrite(MOTOR2_PIN, HIGH);
digitalWrite(MOTOR3_PIN, HIGH);
motors23Activated = true;
// Send new motor statuses to server
events.send(getMotorStates().c_str(), "states", millis());
}
}
}
// Handle actions depending on the temperature
void handleTemperature()
{
int tempLimit = 0;
float timerDuration = 0;
if (isTimeA)
{
// Mani
tempLimit = 180;
timerDuration = TIMER_DURATION_DEBUG ? TIMER_DURATION_DEBUG : 20;
}
else if (isTimeB)
{
// Cacao
tempLimit = 140;
timerDuration = TIMER_DURATION_DEBUG ? TIMER_DURATION_DEBUG : 33;
}
else if (isTimeC)
{
// Cafe
tempLimit = 170;
timerDuration = TIMER_DURATION_DEBUG ? TIMER_DURATION_DEBUG : 12;
}
else
{
tempLimit = MAX_TEMP_LIMIT;
}
// Check if temperature exceeds the limit and if it is rising & start timer if it's not already running
static int prevTemp = 0;
if (temperature >= tempLimit && temperature > prevTemp && prevTemp < tempLimit && !timerIsOn && timerDuration > 0)
{
digitalWrite(MOTOR1_PIN, HIGH);
// Send new motor statuses to server
events.send(getMotorStates().c_str(), "states", millis());
// Start the timer
totalTimeInSeconds = timerDuration * 60;
counter = totalTimeInSeconds;
timerCount++;
timerIsOn = true;
}
prevTemp = temperature;
}
// Handle adding 1 minute with interrupt
void IRAM_ATTR handleAddTime()
{
if (millis() - lastMillis > 60)
{ // Software debouncing button
totalTimeInSeconds += 60;
counter += 60;
timerIsOn = true;
}
lastMillis = millis();
}
// Handle reducing 1 minute with interrupt
void IRAM_ATTR handleReduceTime()
{
if (millis() - lastMillis > 60)
{ // Software debouncing button
totalTimeInSeconds -= 60;
counter -= 60;
}
lastMillis = millis();
}
void setup()
{
Serial.begin(115200);
dht.begin();
pinMode(MOTOR1_PIN, OUTPUT);
pinMode(MOTOR2_PIN, OUTPUT);
pinMode(MOTOR3_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(TIME_A, INPUT);
pinMode(TIME_B, INPUT);
pinMode(TIME_C, INPUT);
pinMode(TIME_ADDER, INPUT);
pinMode(TIME_REDUCER, INPUT);
attachInterrupt(TIME_ADDER, handleAddTime, FALLING);
attachInterrupt(TIME_REDUCER, handleReduceTime, FALLING);
initLCD(mainTitle);
initWifi(WIFI_SSID, WIFI_PASSWORD);
initSPIFFS();
initServer();
delay(2000);
}
void loop()
{
// Get switch readings
isTimeA = digitalRead(TIME_A);
isTimeB = digitalRead(TIME_B);
isTimeC = digitalRead(TIME_C);
// Send Events to the client with the Sensor Readings
events.send("ping", NULL, millis());
events.send(getSensorReadings().c_str(), "readings", millis());
events.send(getTimeValues().c_str(), "timer", millis());
handleTemperature();
handleTimerAndResponse();
delay(1000);
}