From b1f15935d5882b4c750c8f519ae4def3ad849a77 Mon Sep 17 00:00:00 2001 From: koliver2k2 Date: Tue, 3 Nov 2026 09:59:48 +0100 Subject: [PATCH] Added heating elements, small refactor of code --- UCPSMain/New_Version.txt | 156 +++++++++++++++ UCPSMain/Old_Version (Working).txt | 303 +++++++++++++++++++++++++++++ UCPSMain/UCPSMain.ino | 92 ++++++--- 3 files changed, 529 insertions(+), 22 deletions(-) create mode 100644 UCPSMain/New_Version.txt create mode 100644 UCPSMain/Old_Version (Working).txt diff --git a/UCPSMain/New_Version.txt b/UCPSMain/New_Version.txt new file mode 100644 index 0000000..db18bef --- /dev/null +++ b/UCPSMain/New_Version.txt @@ -0,0 +1,156 @@ +#include +#include +#include +#include + +// Wifi (Hotspot) +const char wifiSSID[] = "FreeWifi"; +const char wifiPASS[] = "temppassword"; + +// ESP8266 (Wifi module, AT firmware) +SoftwareSerial wifiSerial(2, 3); // RX = 2, TX = 3 +WiFiEspClient client; + +// LCD Display +#define DHT_SENSOR_TYPE DHT_TYPE_11 +static const int DHT_SENSOR_PIN = A1; +DHT_nonblocking dht_sensor(DHT_SENSxOR_PIN, DHT_SENSOR_TYPE); + +LiquidCrystal lcd(7, 8, 9, 10, 11, 12); // RS, EN, D4, D5, D6, D7 + +// Light sensor and LEDs +int lightPin = A0; // LDR +int dataPin = 4; // DS +int latchPin = 5; // ST_CP +int clockPin = 6; // SH_CP +int leds = 0; + +// API +const float NO_TEMP = -999.0; +const unsigned long API_INTERVAL = 600000UL; // 10m +const unsigned long RETRY_INTERVAL = 30000UL; // 30s +unsigned long lastApiFetch = 0; +unsigned long fetchInterval = API_INTERVAL; +float outdoorTemp = NO_TEMP; + +// Connect to hotspot if not connected, then fetch the current temperature +float fetchOutdoorTemp() { + if (WiFi.status() != WL_CONNECTED) { + WiFi.begin(wifiSSID, wifiPASS); + if (WiFi.status() != WL_CONNECTED) return NO_TEMP; + } + + client.setTimeout(5000); + if (!client.connect("api.open-meteo.com", 80)) return NO_TEMP; + + client.print(F("GET /v1/forecast?latitude=49.46998&longitude=8.4819¤t=temperature_2m HTTP/1.1\r\n" + "Host: api.open-meteo.com\r\n" + "Connection: close\r\n" + "\r\n")); + + // Skip ahead to the number in {"current":{..."temperature_2m":12.3}} + float t = NO_TEMP; + if (client.find("\"current\":") && client.find("\"temperature_2m\":")) { + t = client.parseFloat(); + } + client.stop(); + return t; +} + +// Write to LCD display +void lcdLine(uint8_t row, const char *text) { + lcd.setCursor(0, row); + uint8_t i = 0; + for (; text[i] != '\0' && i < 16; i++) lcd.write(text[i]); + for (; i < 16; i++) lcd.write(' '); +} + +// LEDs for light sensor +void updateShiftRegister() { + digitalWrite(latchPin, LOW); + shiftOut(dataPin, clockPin, LSBFIRST, leds); + digitalWrite(latchPin, HIGH); +} + +// Measure temperature and humidity +static bool measure_environment(float *temp, float *hum) { + static unsigned long timestamp = millis(); + + if (millis() - timestamp > 3000ul) { + if (dht_sensor.measure(temp, hum) == true) { + timestamp = millis(); + return true; + } + } + return false; +} + +void setup() { + pinMode(latchPin, OUTPUT); + pinMode(dataPin, OUTPUT); + pinMode(clockPin, OUTPUT); + + Serial.begin(115200); + lcd.begin(16, 2); + lcdLine(0, "Connecting WiFi"); + lcdLine(1, ""); + + wifiSerial.begin(9600); + WiFi.init(&wifiSerial); + + if (WiFi.status() == WL_NO_SHIELD) { + lcdLine(0, "ESP not found"); + delay(2000); + } + + outdoorTemp = fetchOutdoorTemp(); + lastApiFetch = millis(); + fetchInterval = (outdoorTemp == NO_TEMP) ? RETRY_INTERVAL : API_INTERVAL; + lcd.clear(); +} + +void loop() { + float indoorTemp; + float indoorHum; + + if (measure_environment(&indoorTemp, &indoorHum) == true) { + char tempStr[10]; + dtostrf(indoorTemp, 4, 1, tempStr); + char line0[20]; + snprintf(line0, sizeof(line0), "In: %s C", tempStr); + lcdLine(0, line0); + } + + if (millis() - lastApiFetch >= fetchInterval) { + float t = fetchOutdoorTemp(); + lastApiFetch = millis(); + if (t != NO_TEMP) { + outdoorTemp = t; + fetchInterval = API_INTERVAL; + } else { + fetchInterval = RETRY_INTERVAL; + } + } + + if (outdoorTemp != NO_TEMP) { + char outStr[10]; + dtostrf(outdoorTemp, 4, 1, outStr); + char line1[20]; + snprintf(line1, sizeof(line1), "Out: %s C", outStr); + lcdLine(1, line1); + } else { + lcdLine(1, "Out: API Error"); + } + + int reading = analogRead(lightPin); + int numLEDSLit = reading / 57; + + if (numLEDSLit > 8) numLEDSLit = 8; + leds = 0; + for (int i = 0; i < numLEDSLit; i++) { + leds = leds + (1 << i); + } + updateShiftRegister(); + + delay(100); +} diff --git a/UCPSMain/Old_Version (Working).txt b/UCPSMain/Old_Version (Working).txt new file mode 100644 index 0000000..e67c8e1 --- /dev/null +++ b/UCPSMain/Old_Version (Working).txt @@ -0,0 +1,303 @@ +#include +#include +#include + +// Wifi (Hotspot) +const char wifiSSID[] = "FreeWifi"; +const char wifiPASS[] = "temppassword"; + +// ESP8266 (Wifi module) +SoftwareSerial wifiSerial(2, 3); // RX = 2, TX = 3 +const bool DEBUG = true; // Serial Monitor debug output +const int responseTime = 100; + +// LCD Display +#define DHT_SENSOR_TYPE DHT_TYPE_11 +static const int DHT_SENSOR_PIN = A1; +DHT_nonblocking dht_sensor(DHT_SENSOR_PIN, DHT_SENSOR_TYPE); + +LiquidCrystal lcd(7, 8, 9, 10, 11, 12); // RS, EN, D4, D5, D6, D7 + +// Light sensor and LEDs +int lightPin = A0; // LDR +int dataPin = 4; // DS +int latchPin = 5; // ST_CP +int clockPin = 6; // SH_CP +int leds = 0; + +// API +const float NO_TEMP = -999.0; +const unsigned long API_INTERVAL = 600000UL; // 10m +const unsigned long RETRY_INTERVAL = 30000UL; // 30s +unsigned long lastApiFetch = 0; +unsigned long fetchInterval = API_INTERVAL; +float outdoorTemp = NO_TEMP; + +// HTTP GET request +const char HTTP_REQUEST[] PROGMEM = + "GET /v1/forecast?latitude=49.46998&longitude=8.4819¤t=temperature_2m HTTP/1.1\r\n" + "Host: api.open-meteo.com\r\n" + "Accept-Encoding: identity\r\n" + "Connection: close\r\n" + "\r\n"; + +// Helpers +// Clear serial buffer +void flushInput() { + while (wifiSerial.available()) wifiSerial.read(); +} + +// Discard incoming data for a given time +void drain(unsigned long ms) { + unsigned long start = millis(); + while (millis() - start < ms) flushInput(); +} + +// Send AT command +void espSend(const __FlashStringHelper *cmd) { + flushInput(); + wifiSerial.println(cmd); +} + +// Wait for specific responses +uint8_t waitForEither(const char *a, const char *b, unsigned long timeoutMs) { + const uint8_t na = strlen(a); + const uint8_t nb = strlen(b); + char wa[24] = {0}; + char wb[24] = {0}; + unsigned long start = millis(); + + while (millis() - start < timeoutMs) { + while (wifiSerial.available()) { + char c = wifiSerial.read(); + if (DEBUG) Serial.write(c); + + memmove(wa, wa + 1, na - 1); + wa[na - 1] = c; + memmove(wb, wb + 1, nb - 1); + wb[nb - 1] = c; + + if (memcmp(wa, a, na) == 0) return 1; + if (memcmp(wb, b, nb) == 0) return 2; + } + } + return 0; +} + +bool waitFor(const char *token, unsigned long timeoutMs) { + return waitForEither(token, token, timeoutMs) == 1; +} + +// Parse float from serial stream +float readNumber(unsigned long timeoutMs) { + char buf[10]; + uint8_t i = 0; + bool done = false; + unsigned long start = millis(); + + while (!done && millis() - start < timeoutMs) { + while (wifiSerial.available()) { + char c = wifiSerial.read(); + if (c == ',' || c == '}') { done = true; break; } + if (i < sizeof(buf) - 1) buf[i++] = c; + } + } + buf[i] = '\0'; + if (!done || i == 0) return NO_TEMP; + return atof(buf); +} + +// Connect to Wifi +bool joinWifi() { + espSend(F("AT+CWMODE=1")); + waitForEither("OK", "ERROR", 1000); + + flushInput(); + wifiSerial.print(F("AT+CWJAP=\"")); + wifiSerial.print(wifiSSID); + wifiSerial.print(F("\",\"")); + wifiSerial.print(wifiPASS); + wifiSerial.println('"'); + if (waitForEither("OK", "FAIL", 20000) != 1) return false; + + espSend(F("AT+CIPDNS_CUR=1,\"8.8.8.8\",\"8.8.4.4\"")); + waitForEither("OK", "ERROR", 1000); + + espSend(F("AT+CIPMUX=0")); + waitForEither("OK", "ERROR", 1000); + return true; +} + +// Check Wifi +bool ensureWifi() { + espSend(F("AT+CWJAP?")); + if (waitForEither("+CWJAP:", "No AP", 3000) == 1) return true; + Serial.println(F("\nWi-Fi not connected, (re)joining...")); + return joinWifi(); +} + +// API Temperature +float fetchOutdoorTemp() { + Serial.println(F("\n--- Fetching outdoor temp ---")); + drain(300); + + if (!ensureWifi()) { + Serial.println(F("\nERROR: no Wi-Fi")); + return NO_TEMP; + } + drain(200); + + espSend(F("AT+CIPCLOSE")); + waitForEither("OK", "ERROR", 1000); + + espSend(F("AT+CIPSTART=\"TCP\",\"api.open-meteo.com\",80")); + if (waitForEither("OK", "ERROR", 10000) != 1) { + Serial.println(F("\nERROR: could not open TCP socket")); + return NO_TEMP; + } + + flushInput(); + wifiSerial.print(F("AT+CIPSEND=")); + wifiSerial.println(strlen_P(HTTP_REQUEST)); + if (!waitFor(">", 3000)) { + Serial.println(F("\nERROR: no '>' prompt from ESP")); + espSend(F("AT+CIPCLOSE")); + waitForEither("OK", "ERROR", 1000); + return NO_TEMP; + } + wifiSerial.print((const __FlashStringHelper *)HTTP_REQUEST); + + float t = NO_TEMP; + if (waitFor("\"current\":", 8000) && waitFor("\"temperature_2m\":", 2000)) { + t = readNumber(2000); + } + + espSend(F("AT+CIPCLOSE")); + waitForEither("OK", "ERROR", 1000); + + if (t == NO_TEMP) { + Serial.println(F("\nERROR: no temperature in response")); + } else { + Serial.print(F("\nSUCCESS! Outdoor temp: ")); + Serial.println(t); + } + return t; +} + +// Write to LCD display +void lcdLine(uint8_t row, const char *text) { + lcd.setCursor(0, row); + uint8_t i = 0; + for (; text[i] != '\0' && i < 16; i++) lcd.write(text[i]); + for (; i < 16; i++) lcd.write(' '); +} + +// LEDs for light sensor +void updateShiftRegister() { + digitalWrite(latchPin, LOW); + shiftOut(dataPin, clockPin, LSBFIRST, leds); + digitalWrite(latchPin, HIGH); +} + +// Measure temperature and humidity +static bool measure_environment(float *temp, float *hum) { + static unsigned long timestamp = millis(); + + if (millis() - timestamp > 3000ul) { + if (dht_sensor.measure(temp, hum) == true) { + timestamp = millis(); + return true; + } + } + return false; +} + +// Main methods +void setup() { + pinMode(latchPin, OUTPUT); + pinMode(dataPin, OUTPUT); + pinMode(clockPin, OUTPUT); + pinMode(13, OUTPUT); + + Serial.begin(115200); + lcd.begin(16, 2); + lcdLine(0, "Init WiFi..."); + lcdLine(1, ""); + + wifiSerial.begin(9600); + delay(1000); + + bool alive = false; + for (uint8_t i = 0; i < 3 && !alive; i++) { + espSend(F("AT")); + alive = (waitForEither("OK", "ERROR", 1000) == 1); + } + + if (!alive) { + Serial.println(F("\nESP8266 does not answer.")); + lcdLine(0, "ESP not found"); + delay(2000); + } else { + espSend(F("ATE0")); + waitForEither("OK", "ERROR", 1000); + + lcdLine(0, "Connecting WiFi"); + if (joinWifi()) { + lcdLine(0, "WiFi Connected!"); + } else { + lcdLine(0, "WiFi failed"); + } + delay(1000); + } + lcd.clear(); + + outdoorTemp = fetchOutdoorTemp(); + lastApiFetch = millis(); + fetchInterval = (outdoorTemp == NO_TEMP) ? RETRY_INTERVAL : API_INTERVAL; +} + +void loop() { + float indoorTemp; + float indoorHum; + + if (measure_environment(&indoorTemp, &indoorHum) == true) { + char tempStr[10]; + dtostrf(indoorTemp, 4, 1, tempStr); + char line0[20]; + snprintf(line0, sizeof(line0), "In: %s C", tempStr); + lcdLine(0, line0); + } + + if (millis() - lastApiFetch >= fetchInterval) { + float t = fetchOutdoorTemp(); + lastApiFetch = millis(); + if (t != NO_TEMP) { + outdoorTemp = t; + fetchInterval = API_INTERVAL; + } else { + fetchInterval = RETRY_INTERVAL; + } + } + + if (outdoorTemp != NO_TEMP) { + char outStr[10]; + dtostrf(outdoorTemp, 4, 1, outStr); + char line1[20]; + snprintf(line1, sizeof(line1), "Out: %s C", outStr); + lcdLine(1, line1); + } else { + lcdLine(1, "Out: API Error"); + } + + int reading = analogRead(lightPin); + int numLEDSLit = reading / 57; + + if (numLEDSLit > 8) numLEDSLit = 8; + leds = 0; + for (int i = 0; i < numLEDSLit; i++) { + leds = leds + (1 << i); + } + updateShiftRegister(); + + delay(responseTime); +} \ No newline at end of file diff --git a/UCPSMain/UCPSMain.ino b/UCPSMain/UCPSMain.ino index db18bef..9b0ed8b 100644 --- a/UCPSMain/UCPSMain.ino +++ b/UCPSMain/UCPSMain.ino @@ -14,25 +14,35 @@ WiFiEspClient client; // LCD Display #define DHT_SENSOR_TYPE DHT_TYPE_11 static const int DHT_SENSOR_PIN = A1; -DHT_nonblocking dht_sensor(DHT_SENSxOR_PIN, DHT_SENSOR_TYPE); +DHT_nonblocking dht_sensor(DHT_SENSOR_PIN, DHT_SENSOR_TYPE); LiquidCrystal lcd(7, 8, 9, 10, 11, 12); // RS, EN, D4, D5, D6, D7 // Light sensor and LEDs -int lightPin = A0; // LDR -int dataPin = 4; // DS -int latchPin = 5; // ST_CP -int clockPin = 6; // SH_CP +int lightPin = A0; // LDR +int dataPin = 4; // DS +int latchPin = 5; // ST_CP +int clockPin = 6; // SH_CP int leds = 0; // API const float NO_TEMP = -999.0; -const unsigned long API_INTERVAL = 600000UL; // 10m +const unsigned long API_INTERVAL = 60000UL; // 60s const unsigned long RETRY_INTERVAL = 30000UL; // 30s unsigned long lastApiFetch = 0; unsigned long fetchInterval = API_INTERVAL; float outdoorTemp = NO_TEMP; +// MOSFET module (heating elements) +const int mosfetPin = 13; +unsigned long previousMosfetMillis = 0; +bool mosfetState = true; +const unsigned long mosfetOnTime = 60000UL; // 60 seconds ON +const unsigned long mosfetOffTime = 60000UL; // 60 seconds OFF + +// Store the indoor temp +float lastIndoorTemp = NO_TEMP; + // Connect to hotspot if not connected, then fetch the current temperature float fetchOutdoorTemp() { if (WiFi.status() != WL_CONNECTED) { @@ -47,8 +57,7 @@ float fetchOutdoorTemp() { "Host: api.open-meteo.com\r\n" "Connection: close\r\n" "\r\n")); - - // Skip ahead to the number in {"current":{..."temperature_2m":12.3}} + float t = NO_TEMP; if (client.find("\"current\":") && client.find("\"temperature_2m\":")) { t = client.parseFloat(); @@ -72,10 +81,10 @@ void updateShiftRegister() { digitalWrite(latchPin, HIGH); } -// Measure temperature and humidity +// Measure temperature static bool measure_environment(float *temp, float *hum) { static unsigned long timestamp = millis(); - + // FIXED syntax error below if (millis() - timestamp > 3000ul) { if (dht_sensor.measure(temp, hum) == true) { timestamp = millis(); @@ -89,6 +98,7 @@ void setup() { pinMode(latchPin, OUTPUT); pinMode(dataPin, OUTPUT); pinMode(clockPin, OUTPUT); + pinMode(mosfetPin, OUTPUT); Serial.begin(115200); lcd.begin(16, 2); @@ -107,23 +117,59 @@ void setup() { lastApiFetch = millis(); fetchInterval = (outdoorTemp == NO_TEMP) ? RETRY_INTERVAL : API_INTERVAL; lcd.clear(); + + digitalWrite(mosfetPin, HIGH); + mosfetState = true; + previousMosfetMillis = millis(); } void loop() { - float indoorTemp; - float indoorHum; + unsigned long currentMillis = millis(); - if (measure_environment(&indoorTemp, &indoorHum) == true) { - char tempStr[10]; - dtostrf(indoorTemp, 4, 1, tempStr); - char line0[20]; - snprintf(line0, sizeof(line0), "In: %s C", tempStr); - lcdLine(0, line0); + // MOSFET timer + if (mosfetState) { + if (currentMillis - previousMosfetMillis >= mosfetOnTime) { + mosfetState = false; + previousMosfetMillis = currentMillis; + digitalWrite(mosfetPin, LOW); + } + } else { + if (currentMillis - previousMosfetMillis >= mosfetOffTime) { + mosfetState = true; + previousMosfetMillis = currentMillis; + digitalWrite(mosfetPin, HIGH); + } } - if (millis() - lastApiFetch >= fetchInterval) { + unsigned long remainingSeconds = 0; + if (mosfetState) { + remainingSeconds = (mosfetOnTime - (currentMillis - previousMosfetMillis)) / 1000; + } else { + remainingSeconds = (mosfetOffTime - (currentMillis - previousMosfetMillis)) / 1000; + } + + // Indoor temp + float indoorTemp, indoorHum; + if (measure_environment(&indoorTemp, &indoorHum) == true) { + lastIndoorTemp = indoorTemp; + } + + // LCD row 0 (Temp + Countdown) + char line0[20]; + if (lastIndoorTemp != NO_TEMP) { + char tempStr[6]; + dtostrf(lastIndoorTemp, 4, 1, tempStr); + // Formats into exactly 16 chars: "In:22.5C ON 10" + snprintf(line0, sizeof(line0), "In:%sC %s %2lu", tempStr, mosfetState ? "ON " : "OFF", remainingSeconds); + } else { + snprintf(line0, sizeof(line0), "In:--- C %s %2lu", mosfetState ? "ON " : "OFF", remainingSeconds); + } + lcdLine(0, line0); + + // API outdoor temp + if (currentMillis - lastApiFetch >= fetchInterval) { float t = fetchOutdoorTemp(); - lastApiFetch = millis(); + lastApiFetch = currentMillis; if (t != NO_TEMP) { outdoorTemp = t; fetchInterval = API_INTERVAL; @@ -132,6 +178,7 @@ void loop() { } } + // LCD row 1 if (outdoorTemp != NO_TEMP) { char outStr[10]; dtostrf(outdoorTemp, 4, 1, outStr); @@ -142,6 +189,7 @@ void loop() { lcdLine(1, "Out: API Error"); } + // Light sensor LEDs int reading = analogRead(lightPin); int numLEDSLit = reading / 57; @@ -152,5 +200,5 @@ void loop() { } updateShiftRegister(); - delay(100); -} + delay(100); +} \ No newline at end of file