#include <LiquidCrystal_I2C.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include "RTClib.h"
#include <IRremote.h>
#include <ESPAsyncWebSrv.h>
#define TEMP_PIN 39
#define LED_1_PIN 16
#define RELAY_PIN 18
#define RECEIVER_PIN 17
#define SHOW_IP_BUTTON_PIN 33
#define MESSURMENT_SWITCH_PIN 2
#define MODE_SWITCH_PIN 4
#define ENCODER_CLK_PIN 25
#define ENCODER_DT_PIN 26
const char* ssid = "Wokwi-GUEST";
const char* password = "";
String deviceId = "12345";
unsigned long lastTime = 0;
unsigned long timerDelay = 10000 ; // 10s
long targetTemperatureInC = 25.0;
bool relayState = false;
String termoState = "NONE";
int showIpButtonLastState = HIGH;
int modeLastState = LOW;
unsigned long prevMillis = 0.0;
unsigned long timerInS = 0;
bool timerIsOn = false;
bool timerOnPause = false;
char buffer[7] = "000000";
int currentIndex = 5;
IRrecv RECEIVER = IRrecv(RECEIVER_PIN);
LiquidCrystal_I2C LCD = LiquidCrystal_I2C(0x27, 20, 4);
RTC_DS1307 RTC;
AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
analogReadResolution(10);
initLCD();
initRTC();
initRelay();
initEncoder();
initOtherPins();
WiFi.begin(ssid, password, 6);
}
// START ------------------- LOOP -------------------
void loop() {
tempLoop();
relayLoop();
setRelayState();
showTime();
wifiIconAnimation();
showIp();
}
void tempLoop() {
int tempVal = analogRead(TEMP_PIN);
float tempC = readTempC(tempVal);
printTemp(tempC);
sendTempAfterDelay(tempC);
}
void relayLoop() {
int modeSwitchState = digitalRead(MODE_SWITCH_PIN);
if (modeSwitchState != HIGH) {
if (modeLastState != modeSwitchState) {
relayState = false;
modeLastState = modeSwitchState;
}
printTimer();
if (RECEIVER.decode()) {
changeRelayState();
setTimer();
RECEIVER.resume();
} else {
highlightTimerIndex();
}
startTimer();
printRelayState();
} else {
modeLastState = modeSwitchState;
}
}
// END ------------------- LOOP -------------------
// START ------------------- TEMPERATURE -------------------
void printTemp(float tempC) {
int tempSwitchState = digitalRead(MESSURMENT_SWITCH_PIN);
int modeSwitchState = digitalRead(MODE_SWITCH_PIN);
if (tempSwitchState == HIGH) {
printTemp(tempC, "C");
if (modeSwitchState == HIGH) {
printTargetTemp(targetTemperatureInC, "C");
}
} else {
float tempF = readTempF(tempC);
printTemp(tempF, "F");
if (modeSwitchState == HIGH) {
float targetTempF = readTempF(targetTemperatureInC);
printTargetTemp(targetTempF, "F");
}
}
if (modeSwitchState == HIGH) {
setTempState(tempC);
}
}
void setTempState(int tempC) {
LCD.setCursor(18, 1);
LCD.print(" ");
if (tempC < targetTemperatureInC) {
termoState = "HIGH";
relayState = true;
LCD.write((byte)262);
} else if (tempC > targetTemperatureInC) {
termoState = "LOW";
relayState = false;
LCD.write((byte)263);
} else {
termoState = "NONE";
relayState = false;
LCD.print(" ");
}
}
float readTempC(int analogValue) {
const float BETA = 3950;
return 1 / (log(1 / (1023. / analogValue - 1)) / BETA + 1.0 / 298.15) - 273.15;
}
float readTempF(float tempC) {
return (tempC - 32.0) / 1.8;
}
void sendTempAfterDelay(float tempC) {
unsigned long currentTime = millis();
if ((unsigned long)(currentTime - lastTime) >= timerDelay && WiFi.status() == WL_CONNECTED) {
digitalWrite(LED_1_PIN, HIGH);
String body = R"(
{
"data": [
"attributes": {
"mode": "%TERMOSTAT_MODE%",
"deviceId": "%DEVICE_ID%",
"deviceHash": "%DEVICE_HASH%"
"devices": [
{
"type": "relay",
%TIMER_SECTION%
"state": "%RELAY_STATUS%"
},
{
"type": "termometer",
"state": "%TERMO_STATE%"
"temperature": [
{
"value": "%TEMPERATURE_C%",
"messurmentUnit": "C"
},
{
"value": "%TEMPERATURE_F%",
"messurmentUnit": "F"
}
]
}
]
}
]
}
)";
if (!modeLastState) {
String timerSection = R"(,
"timer": {
"timerInSeconds": "%TIMER_IN_SECONDS%"
"timeLeft": "%TIME_LEFT%"
}
)";
body.replace("%TIMER_SECTION%", timerSection);
body.replace("%TIMER_IN_SECONDS%", String(timerInS));
body.replace("%TIME_LEFT%", String(timeLeftOnTimer()));
} else {
body.replace("%TIMER_SECTION%", "");
}
body.replace("%TERMOSTAT_MODE%", modeLastState ? "TARGET_TEMPERATURE" : "HEAT_BY_TIMER");
body.replace("%RELAY_STATUS%", relayState ? "ON" : "OFF");
body.replace("%TERMO_STATE%", termoState);
body.replace("%DEVICE_ID%", deviceId);
body.replace("%TEMPERATURE_C%", String(tempC));
body.replace("%TEMPERATURE_F%", String(readTempF(tempC)));
HTTPClient http;
http.useHTTP10(true);
http.addHeader("Content-Type", "application/json");
http.addHeader("Api-Token", "sImp0aSI6InBqcTNtODViMW9iYiJ9");
Serial.println("Request send to: http://myesp32.termostat:80/api/v1/tempermostat/" + deviceId);
Serial.println("body: " + body);
http.begin("http://myesp32.termostat:80/api/v1/tempermostat/" + deviceId);
http.POST(body);
Serial.println("Request end...");
http.end();
lastTime = currentTime;
delay(250);
digitalWrite(LED_1_PIN, LOW);
}
}
void printTemp(float temp, String messurment)
{
char tempString[6] = " ";
dtostrf(temp, 3, 1, tempString);
LCD.setCursor(0, 0);
LCD.print("CURRENT ");
LCD.write((byte)256);
LCD.print(" ");
LCD.print(tempString);
LCD.print((char)223);
LCD.print(messurment);
LCD.print(" ");
}
void printTargetTemp(float temp, String messurment)
{
char tempString[6] = " ";
dtostrf(temp, 3, 1, tempString);
LCD.setCursor(0, 1);
LCD.print("TARGET ");
LCD.write((byte)256);
LCD.print(" ");
LCD.print(tempString);
LCD.print((char)223);
LCD.print(messurment);
LCD.print(" ");
}
// END ------------------- TEMPERATURE -------------------
void readEncoder() {
int dtValue = digitalRead(ENCODER_DT_PIN);
if (dtValue == HIGH) {
++targetTemperatureInC;
}
if (dtValue == LOW) {
--targetTemperatureInC;
}
}
// START ------------------- LCD -------------------
void showIp() {
int value = digitalRead((SHOW_IP_BUTTON_PIN));
if (showIpButtonLastState != value) {
showIpButtonLastState = value;
if (value == HIGH) {
LCD.setCursor(0, 0);
LCD.clear();
LCD.print("IP: ");
LCD.print(WiFi.localIP());
delay(5000);
LCD.clear();
}
}
}
void showTime() {
LCD.setCursor(0, 3);
LCD.write((byte)257);
LCD.write((byte)258);
DateTime now = RTC.now();
LCD.print(now.hour(), DEC);
LCD.print(':');
LCD.print(now.minute(), DEC);
LCD.print(':');
LCD.print(now.second(), DEC);
LCD.print(" ");
}
void wifiIconAnimation() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Connecting to WiFi...");
LCD.setCursor(19, 0);
LCD.write(byte(259));
delay(200);
LCD.setCursor(19, 0);
LCD.write(byte(260));
delay(200);
LCD.setCursor(19, 0);
LCD.write(byte(261));
delay(200);
} else {
LCD.setCursor(19, 0);
LCD.write(byte(261));
}
}
void wifiIcons() {
byte wifi1[8] = { B00000, B00000, B00000, B00000, B00000, B00100, B00000, B00000 };
byte wifi2[8] = { B00000, B00000, B00100, B01010, B00000, B00100, B00000, B00000 };
byte wifi3[8] = { B01110, B10001, B00100, B01010, B00000, B00100, B00000, B00000 };
LCD.createChar(259, wifi1);
LCD.createChar(260, wifi2);
LCD.createChar(261, wifi3);
}
void termostatStateIcons() {
byte stateHigh[] = { B00000, B00100, B01010, B10001, B00100, B01010, B10001, B00000 };
byte stateLow[] = { B00000, B10001, B01010, B00100, B10001, B01010, B00100, B00000 };
byte termometrIcon[8] = {B00100, B01010, B01010, B01110, B01110, B11111, B11111, B01110};
LCD.createChar(262, stateHigh);
LCD.createChar(263, stateLow);
LCD.createChar(256, termometrIcon);
}
void clockIcons() {
byte clockChar1[] = { B00111, B01000, B10010, B10010, B10011, B10000, B01000, B00111};
byte clockChar2[] = {B10000, B01000, B00100, B00100, B10100, B00100, B01000, B10000};
LCD.createChar(257, clockChar1);
LCD.createChar(258, clockChar2);
}
// END ------------------- LCD -------------------
// START ------------------- RELAY -------------------
void changeRelayState() {
if (RECEIVER.decodedIRData.command == 162) {
relayState = !relayState;
if (timerIsOn) {
pauseTheTimer();
}
}
}
void printRelayState() {
LCD.setCursor(17, 1);
if (relayState) {
digitalWrite(RELAY_PIN, HIGH);
LCD.print(" ON");
} else {
digitalWrite(RELAY_PIN, LOW);
LCD.print("OFF");
}
}
void setRelayState() {
if (relayState) {
digitalWrite(RELAY_PIN, HIGH);
} else {
digitalWrite(RELAY_PIN, LOW);
}
}
void setupRelayServer() {
server.on("/setup", HTTP_POST, [](AsyncWebServerRequest * request) {
String deviceIdParamName = "deviceId";
String timerParamName = "timer";
if (request->hasParam(deviceIdParamName)) {
deviceId = request->getParam(deviceIdParamName)->value();
}
if (request->hasParam(timerParamName)) {
timerInS = request->getParam(timerParamName)->value().toInt();
}
String response = R"(
{
"data": {
"type": "relay",
"timer": {
"seconds": "%TIMER%"
},
"state": "%RELAY_STATUS"%
"deviceId": "%DEVICE_ID%"
}
}
)";
response.replace("%TIMER%", String(timerInS));
response.replace("%RELAY_STATUS%", relayState ? "ON" : "OFF");
response.replace("%DEVICE_ID%", deviceId);
request->send(200, "application/json", response);
});
}
void toggleRelay() {
server.on("/relay/toggle", HTTP_POST, [](AsyncWebServerRequest * request) {
String response = R"(
{
"data": {
"type": "relay",
"deviceId": "%DEVICE_ID%",
"state": "%RELAY_STATUS%"
%TIMER_SECTION%
}
}
)";
String timerSection = R"(,
"timer": {
"timerInSeconds": "%TIMER_IN_SECONDS%"
"timeLeft": "%TIME_LEFT%"
}
)";
String timerParamName = "timer";
if (request->hasParam(timerParamName) && request->getParam(timerParamName)->value() == "true") {
response.replace("%TIMER_SECTION%", timerSection);
startTheTimer();
}
relayState = !relayState;
response.replace("%RELAY_STATUS%", relayState ? "ON" : "OFF");
response.replace("%DEVICE_ID%", deviceId);
response.replace("%TIMER%", String(timerInS));
request->send(200, "application/json", response);
});
}
// END ------------------- RELAY -------------------
// START ------------------- TIMER -------------------
void highlightTimerIndex() {
if (!timerIsOn) {
if (currentIndex < 0) currentIndex = 5;
if (currentIndex > 5) currentIndex = 0;
int cursorPosition = 8 + currentIndex;
if (currentIndex > 1) cursorPosition++;
if (currentIndex > 3) cursorPosition++;
LCD.setCursor(cursorPosition, 1);
LCD.print(" ");
delay(100);
LCD.setCursor(cursorPosition, 1);
LCD.print(buffer[currentIndex]);
delay(100);
}
}
void startTimer() {
unsigned long timeout = timerInS * 1000;
unsigned long time = millis() - prevMillis;
if (timerIsOn) {
if (time >= timeout) {
pauseTheTimer();
strncpy(buffer, "000000", sizeof buffer);
currentIndex = 5;
}
updateTimer();
}
}
void setTimer() {
if (currentIndex < 0) currentIndex = 5;
if (currentIndex > 5) currentIndex = 0;
if (!timerIsOn) {
switch (RECEIVER.decodedIRData.command) {
case 104:
buffer[currentIndex] = '0';
currentIndex--;
break;
case 48:
buffer[currentIndex] = '1';
currentIndex--;
break;
case 24:
buffer[currentIndex] = '2';
currentIndex--;
break;
case 122:
buffer[currentIndex] = '3';
currentIndex--;
break;
case 16:
buffer[currentIndex] = '4';
currentIndex--;
break;
case 56:
buffer[currentIndex] = '5';
currentIndex--;
break;
case 90:
buffer[currentIndex] = '6';
currentIndex--;
break;
case 66:
buffer[currentIndex] = '7';
currentIndex--;
break;
case 74:
buffer[currentIndex] = '8';
currentIndex--;
break;
case 82:
buffer[currentIndex] = '9';
currentIndex--;
break;
case 176:
buffer[currentIndex] = '0';
currentIndex++;
break;
case 144:
currentIndex++;
break;
case 224:
currentIndex--;
break;
}
printTimer();
}
if (RECEIVER.decodedIRData.command == 168) {
if (timerIsOn) {
pauseTheTimer();
printTimer();
} else {
int timerHours = String(buffer).substring(0, 2).toInt();
int timerMinutes = String(buffer).substring(2, 4).toInt();
int timerSeconds = String(buffer).substring(4, 6).toInt();
timerInS = timerSeconds + timerMinutes * 60 + timerHours * 60 * 60;
prevMillis = millis();
startTheTimer();
}
}
}
void pauseTheTimer() {
timerIsOn = false;
relayState = false;
}
void startTheTimer() {
timerIsOn = true;
relayState = true;
}
void printTimer() {
LCD.setCursor(0, 1);
LCD.print("TIMER ");
String timerHours = String(buffer).substring(0, 2);
String timerMinutes = String(buffer).substring(2, 4);
if (timerMinutes.toInt() > 59) {
buffer[2] = '5';
buffer[3] = '9';
timerMinutes = 59;
}
String timerSeconds = String(buffer).substring(4, 6);
if (timerSeconds.toInt() > 59) {
buffer[4] = '5';
buffer[5] = '9';
timerSeconds = 59;
}
LCD.print(timerHours);
LCD.print(":");
LCD.print(timerMinutes);
LCD.print(":");
LCD.print(timerSeconds);
LCD.print(" ");
}
void updateTimer() {
unsigned long newTimerInS = timeLeftOnTimer();
int hoursLeft = newTimerInS / 60 / 60;
newTimerInS = newTimerInS - (hoursLeft * 60 * 60);
int minutesLeft = newTimerInS / 60;
newTimerInS = newTimerInS - (minutesLeft * 60);
int secondsLeft = newTimerInS;
String bufferString;
if (hoursLeft < 10) bufferString += String("0");
bufferString += String(hoursLeft);
if (minutesLeft < 10) bufferString += String("0");
bufferString += String(minutesLeft);
if (secondsLeft < 10) bufferString += String("0");
bufferString += String(secondsLeft);
bufferString.toCharArray(buffer, 7);
}
unsigned long timeLeftOnTimer() {
unsigned long timeElapsed = (millis() - prevMillis) / 1000;
return timerInS - timeElapsed;
}
// END ------------------- TIMER -------------------
// START ------------------- INIT -------------------
void initEncoder() {
pinMode(ENCODER_CLK_PIN, INPUT);
pinMode(ENCODER_DT_PIN, INPUT);
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK_PIN), readEncoder, FALLING);
}
void initOtherPins() {
pinMode(TEMP_PIN, INPUT);
pinMode(MESSURMENT_SWITCH_PIN, INPUT);
pinMode(MODE_SWITCH_PIN, INPUT);
pinMode(LED_1_PIN, OUTPUT);
pinMode(SHOW_IP_BUTTON_PIN, INPUT_PULLUP);
}
void initLCD() {
LCD.init();
LCD.backlight();
wifiIcons();
termostatStateIcons();
clockIcons();
}
void initRTC() {
if (!RTC.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
abort();
}
}
void initRelay() {
pinMode(RELAY_PIN, OUTPUT);
toggleRelay();
setupRelayServer();
RECEIVER.enableIRIn();
Serial.begin(115200);
}
// END ------------------- INIT -------------------