#include // Required for I2C communication with LCD #include "DFRobot_RGBLCD1602.h" // Library for your specific RGB LCD #include // Required for log() in thermistor calculation #include // Required for WiFi connection (for WiFi.status()) #include // Required by ArduinoIoTCloud for secure communication // --- Arduino Cloud Includes --- // This file is auto-generated by Arduino Cloud and contains: // - #include // - #include // - Your Cloud variable declarations (e.g., float temperatureCelsius;) // - Your Thing ID, potentially Device ID/Key constants // - Your initProperties() function // - Your Connection Handler object (e.g., WiFiConnectionHandler ArduinoIoTPreferredConnection;) // - Callbacks for Read/Write variables (e.g., void onCookingThresholdChange();) #include "thingProperties.h" // --- Xiao ESP32-S3 I2C Pins --- // !!! VERIFY AND UPDATE THESE GPIO NUMBERS FOR YOUR XIAO ESP32S3 !!! // Common defaults for Xiao ESP32S3 are SDA=D4 (GPIO6), SCL=D5 (GPIO7) // Your previous code used 5 and 6. Keeping those for now, but verify based on your wiring. // Check your Xiao pinout diagram carefully! D1 is GPIO5, D2 is GPIO6 etc. const int I2C_SDA_PIN = 5; // Example: GPIO5 (Physical pin D1 on Seeed Studio XIAO ESP32S3) const int I2C_SCL_PIN = 6; // Example: GPIO6 (Physical pin D2 on Seeed Studio XIAO ESP32S3) // --- LCD I2C Address --- const uint8_t LCD_RGB_ADDRESS = 0x60; // Default address for DFRobot RGB LCD DFRobot_RGBLCD1602 lcd(LCD_RGB_ADDRESS, 16, 2); // Initialize the LCD object // --- Thermistor Configuration --- const int THERMISTOR_PIN = 2; // ADC Pin for thermistor (D0/A0 on Xiao ESP32S3 is GPIO2) const float NOMINAL_RESISTANCE = 10000.0; // Resistance at nominal temperature (usually 25°C) const float NOMINAL_TEMPERATURE = 25.0; // Nominal temperature in Celsius const float B_COEFFICIENT = 3950.0; // Beta coefficient of the thermistor (check your thermistor datasheet) const float SERIES_RESISTOR = 10000.0; // Resistance of the series resistor in your voltage divider const float V_IN = 3.3; // Voltage supply for the thermistor divider circuit (Xiao ESP32S3 3.3V) // --- ADC Reading Parameters --- const int ADC_MAX = 4095; // ESP32S3 ADC is 12-bit by default, max value is 2^12 - 1 const int NUM_SAMPLES = 10; // Number of samples to average for a more stable reading const int SAMPLE_DELAY_MS = 10; // Short delay between samples // --- Timing Intervals --- unsigned long lastTempReadTime = 0; const long tempReadInterval = 2000; // Read temperature, update LCD, and check/update Cloud vars every 2 seconds // --- Local variable for temperature --- float currentTemperatureCelsius = -999.0; // Initialize with an error value void setup() { // Start serial communication for debugging Serial.begin(115200); // Wait a moment for the Serial Monitor to open if connected delay(1500); Serial.println("Xiao ESP32S3 Oven Monitor - Arduino Cloud"); // Initialize Cloud Variables and Connection properties (from thingProperties.h) // This sets up the link between local variables and Cloud variables, // registers callbacks (like onCookingThresholdChange), and prepares the connection handler. initProperties(); /* The following function allows you to obtain more information related to the state of network and IoT Cloud connection and errors the higher number the more granular information you’ll get. The default is 0 (only errors). Maximum is 4. Set to 2 for helpful debug messages during network/cloud connection. */ setDebugMessageLevel(2); ArduinoCloud.printDebugInfo(); // Print initial debug info // --- Initialize I2C Communication for the LCD --- Serial.println("Initializing I2C and LCD..."); Serial.print("SDA Pin: "); Serial.println(I2C_SDA_PIN); Serial.print("SCL Pin: "); Serial.println(I2C_SCL_PIN); // It's important to call Wire.begin() with pins BEFORE lcd.init() for ESP32 // This checks if the I2C bus can be initialized on the specified pins bool i2c_ok = Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); if (!i2c_ok) { Serial.println("Failed to initialize I2C bus with specified pins!"); // Critical Error: Halt and indicate on LCD if possible // Attempt basic LCD init and display error, might not work if I2C is truly broken lcd.init(); // Try initializing LCD anyway for error display lcd.setPWM(lcd.REG_RED, 255); lcd.setPWM(lcd.REG_GREEN, 0); lcd.setPWM(lcd.REG_BLUE, 0); // Red error color lcd.setCursor(0, 0); lcd.print("I2C Fail"); lcd.setCursor(0, 1); lcd.print("Check Wiring"); while(1) { delay(1000); } // Halt execution here on critical I2C failure } else { Serial.println("I2C bus initialized."); } delay(100); // Give I2C bus a moment // --- Initialize the LCD Display --- // This command sends the actual initialization sequence to the LCD controller lcd.init(); Serial.println("lcd.init() called."); // Set initial LCD backlight color lcd.setPWM(lcd.REG_RED, 0); lcd.setPWM(lcd.REG_GREEN, 0); lcd.setPWM(lcd.REG_BLUE, 255); // Initial Blue Serial.println("LCD Backlight set to Blue."); // Display initial message on LCD lcd.setCursor(0, 0); lcd.print("Cloud Monitor:"); lcd.setCursor(0, 1); lcd.print("Connecting..."); // Will update later based on Cloud status Serial.println("LCD Initial message displayed."); // --- DEBUGGING WIFI/CLOUD CONNECTION VARIABLES (Should match arduino_secrets.h and Thing Setup) --- Serial.println("--- WiFi and Cloud Connection Debug ---"); Serial.print("Value of SSID variable: "); Serial.println(SSID); // This should print your WiFi SSID (from arduino_secrets.h -> thingProperties.h) Serial.print("Value of PASS variable (CAUTION - PASSWORD): "); // Print only first few characters of password for safety // PASS comes from SECRET_OPTIONAL_PASS or SECRET_PASS in arduino_secrets.h via thingProperties.h if (strlen(PASS) > 0) { for(int i = 0; i < min((int)strlen(PASS), 8); ++i) Serial.print(PASS[i]); Serial.println("..."); } else { Serial.println("Password is empty!"); } // DEVICE_KEY is defined in thingProperties.h and comes from SECRET_DEVICE_KEY in arduino_secrets.h Serial.print("Value of DEVICE_KEY variable (first few chars): "); if (strlen(DEVICE_KEY) > 0) { for(int i = 0; i < min((int)strlen(DEVICE_KEY), 8); ++i) Serial.print(DEVICE_KEY[i]); // Print only first 8 chars Serial.println("..."); } else { Serial.println("Key is empty!"); } Serial.println("---------------------------------------"); // --- END DEBUGGING --- // Connect to Arduino IoT Cloud // This uses the WiFiConnectionHandler object (ArduinoIoTPreferredConnection) // initialized with SSID and PASS from thingProperties.h. // This call is BLOCKING and will wait until connection is established or a timeout occurs. // Watch Serial Monitor closely for connection progress/errors after this line. Serial.println("Attempting to connect to Arduino IoT Cloud..."); ArduinoCloud.begin(ArduinoIoTPreferredConnection); // After attempting connection, check the status if (ArduinoCloud.connected()) { Serial.println("Connected to Arduino IoT Cloud!"); // Sync initial values from the cloud for READWRITE variables (like cookingThreshold) // This fetches the last set value from the dashboard/cloud UI on startup. // !!! COMMENTED OUT: 'syncState' member not found in this library version !!! // ArduinoCloud.syncState(); } else { Serial.println("Failed to connect to Arduino IoT Cloud."); // The sketch will continue running, but ArduinoCloud.update() in loop() will keep trying to reconnect. Serial.print("Final WiFi Status Code: "); Serial.println(WiFi.status()); // Print final WiFi status if Cloud connection ultimately failed Serial.println("Check credentials in arduino_secrets.h / Thing setup."); Serial.println("Check network (2.4GHz, signal)."); } // Initialize default values for cloud variables if they appear uninitialized after sync // cookingThreshold should ideally be initialized by Arduino Cloud via its dashboard or UI // Using 25.0 as a safe fallback default if Cloud value is still out of range after sync. if (cookingThreshold < -50.0 || cookingThreshold > 300.0) { Serial.println("Cooking threshold seems uninitialized or out of range after sync, setting fallback default."); cookingThreshold = 25.0; // Fallback default } Serial.print("Initial/Synced cookingThreshold: "); Serial.println(cookingThreshold); // alertSentFlag is also managed by the cloud, should initialize to false (default for bool). // syncState() should update this from the cloud if it was changed via dashboard previously. Serial.print("Initial/Synced alertSentFlag: "); Serial.println(alertSentFlag); // Verify its state } void loop() { // This is CRITICAL for Arduino Cloud communication and variable syncing. // It handles sending local variable updates to the Cloud, receiving Cloud variable updates, // and attempting to reconnect if the connection is lost. ArduinoCloud.update(); unsigned long currentTime = millis(); // Check if it's time to perform periodic tasks (read sensor, update display/cloud) if (currentTime - lastTempReadTime >= tempReadInterval) { lastTempReadTime = currentTime; // Update the timer readTemperature(); // Read thermistor and calculate currentTemperatureCelsius displayTemperatureOnLCD(); // Update the LCD display with the latest temp (always, regardless of cloud) // Only attempt to update Cloud variables and check/send alerts if connected to the Cloud if (ArduinoCloud.connected()) { updateCloudVariables(); // Assign local temp to cloud variable (temperatureCelsius) checkTemperatureAlert(); // Check alert condition and update alertMessage/alertSentFlag // Optional: Add a status message to LCD when connected // lcd.setCursor(15,0); lcd.print("*"); // Indicate connected with a symbol } else { // Indicate not connected on Serial and LCD Serial.println("Cloud not connected. Skipping Cloud variable updates and alert checks."); // The displayTemperatureOnLCD function already adds 'X' or '*' based on cloud status now // lcd.setCursor(15,0); lcd.print("X"); // Indicate not connected with a symbol } } // Add other non-blocking code here (e.g., reading buttons) // Keep this loop fast to ensure ArduinoCloud.update() runs frequently. } // Function to read thermistor and calculate currentTemperatureCelsius void readTemperature() { float totalAdcValue = 0; for (int i = 0; i < NUM_SAMPLES; i++) { totalAdcValue += analogRead(THERMISTOR_PIN); // Read the analog value delay(SAMPLE_DELAY_MS); // Short delay for ADC stability between samples } float averageAdcValue = totalAdcValue / NUM_SAMPLES; // Calculate the average // --- Convert ADC value to Voltage --- float vOut = (averageAdcValue / ADC_MAX) * V_IN; // --- Calculate Thermistor Resistance --- float thermistorResistance; // Use voltage divider formula: R_therm = R_series * (V_out / (V_in - V_out)) // Protect against division by zero or near-zero in the voltage divider calculation if (abs(V_IN - vOut) < 1e-6) { // V_out is effectively V_IN (open circuit or very cold thermistor) thermistorResistance = 1e9; // Assign a very large number } else if (vOut < 1e-6) { // V_out is effectively 0 (short circuit or very hot thermistor) thermistorResistance = 1e-9; // Assign a very small number (near zero) } else { thermistorResistance = SERIES_RESISTOR * (vOut / (V_IN - vOut)); } // --- Calculate Temperature using Steinhart-Hart Equation --- // Equation: 1/T = 1/T0 + (1/Beta) * ln(R/R0) // Where T is temperature in Kelvin, T0 is nominal temperature in Kelvin, // R is thermistor resistance, R0 is nominal resistance, Beta is B_COEFFICIENT. if (thermistorResistance > 0 && NOMINAL_RESISTANCE > 0 && B_COEFFICIENT > 0) { float t0_kelvin = NOMINAL_TEMPERATURE + 273.15; // Convert nominal temp to Kelvin float steinhart_val = thermistorResistance / NOMINAL_RESISTANCE; if (steinhart_val > 0) { // Ensure log argument is positive for log() steinhart_val = log(steinhart_val); // Calculate natural logarithm steinhart_val /= B_COEFFICIENT; // Divide by Beta coefficient steinhart_val += (1.0 / t0_kelvin); // Add reciprocal of nominal temperature in Kelvin if (abs(steinhart_val) > 1e-9) { // Avoid division by zero or near-zero currentTemperatureCelsius = (1.0 / steinhart_val) - 273.15; // Calculate reciprocal and convert back to Celsius } else { currentTemperatureCelsius = -998.0; // Error in calculation (reciprocal very large) } } else { currentTemperatureCelsius = -997.0; // Error with resistance ratio (zero or negative) } } else { currentTemperatureCelsius = -999.0; // Error with constants or initial resistance check } // Print debugging info to Serial Monitor (local value) Serial.print("Raw ADC: "); Serial.print(averageAdcValue); Serial.print(", V_out: "); Serial.print(vOut, 3); // Print voltage with 3 decimal places Serial.print(", R_therm: "); Serial.print(thermistorResistance, 0); // Print resistance as integer Serial.print(", Temp C (local): "); // Print temperature with 1 decimal place, or error message if (currentTemperatureCelsius > -270.0) { // Check for plausible temperature range (well above absolute zero) Serial.println(currentTemperatureCelsius, 1); } else { Serial.println("Sensor Error"); } } // Function to update the temperature Cloud variable void updateCloudVariables() { // Assign the local temperature reading to the Cloud variable. // The ArduinoCloud.update() in loop() handles sending this to the cloud // if its value has changed since the last update. temperatureCelsius = currentTemperatureCelsius; Serial.print("Temp C (to cloud): "); Serial.println(temperatureCelsius, 1); } // Function to update the LCD display void displayTemperatureOnLCD() { lcd.clear(); lcd.setCursor(0, 0); lcd.print("Oven Temp:"); // Status indicator at the top right: '*' for connected, 'X' for not lcd.setCursor(15,0); if (ArduinoCloud.connected()) { lcd.print("*"); } else { lcd.print("X"); } lcd.setCursor(0, 1); // Check if temperature reading is plausible if (currentTemperatureCelsius > -270.0) { // Valid range roughly above absolute zero char tempStrC[7]; // Format temperature string for display (e.g., " 25.5") dtostrf(currentTemperatureCelsius, 5, 1, tempStrC); // Width 5, 1 decimal place lcd.print(tempStrC); lcd.print((char)223); // Print degree symbol ° lcd.print("C"); // Change backlight color based on temperature relative to threshold // Added checks to avoid strange colors with very low thresholds if (currentTemperatureCelsius < cookingThreshold * 0.6 && cookingThreshold > 50) { // Significantly below threshold (only if threshold > 50) lcd.setPWM(lcd.REG_BLUE, 255); lcd.setPWM(lcd.REG_GREEN, 0); lcd.setPWM(lcd.REG_RED, 0); // Blue } else if (currentTemperatureCelsius < cookingThreshold - 10 && cookingThreshold > 20) { // Warming up, but still below threshold (only if threshold > 20) lcd.setPWM(lcd.REG_GREEN, 200); lcd.setPWM(lcd.REG_BLUE, 50); lcd.setPWM(lcd.REG_RED, 150); // Yellowish/Greenish } else if (currentTemperatureCelsius >= cookingThreshold) { // At or above threshold lcd.setPWM(lcd.REG_RED, 255); lcd.setPWM(lcd.REG_GREEN, 0); lcd.setPWM(lcd.REG_BLUE, 0); // Red } else { // Approaching threshold (e.g., within 10 degrees, or if threshold is low) if (cookingThreshold > 20) { // Only use gradient colors if threshold is reasonable (> 20) lcd.setPWM(lcd.REG_RED, 200); lcd.setPWM(lcd.REG_GREEN, 100); lcd.setPWM(lcd.REG_BLUE, 0); // Orange/Yellow } else { // If threshold is very low (<= 20), maybe just default color // Simple default color for very low thresholds or temps far below threshold lcd.setPWM(lcd.REG_BLUE, 255); lcd.setPWM(lcd.REG_GREEN, 0); lcd.setPWM(lcd.REG_RED, 0); // Blue } } } else { // Display error message if temperature is out of plausible range lcd.print("Sensor Err"); lcd.setPWM(lcd.REG_RED, 255); lcd.setPWM(lcd.REG_GREEN, 0); lcd.setPWM(lcd.REG_BLUE, 0); // Red for error } } // Function to check alert condition and update Cloud variables (alertMessage, alertSentFlag) // This function is called from loop() ONLY when ArduinoCloud.connected() is true void checkTemperatureAlert() { // We only update Cloud variables if connected, as handled in the loop() check. // The logic here just determines the state based on temp vs threshold. if (currentTemperatureCelsius >= cookingThreshold) { // If temperature is at or above the threshold if (!alertSentFlag) { // And the alert hasn't been marked as sent yet // Update alert message and set the alert flag Cloud variables alertMessage = "Alert: Temp >= " + String(cookingThreshold, 0) + " C!"; alertSentFlag = true; Serial.println("ALERT: Cooking temperature reached! Cloud variables updated."); // --- IFTTT Integration (Commented out) --- // uncomment these lines if you have IFTTT setup and IFTTT_API_KEY/IFTTT_EVENT_NAME defined // sendIFTTTAlert(currentTemperatureCelsius, cookingThreshold); // ---------------------------------------- } } else { // If temperature drops below the threshold // Reset the alert flag if it was previously set (using a hysteresis of 2 degrees to avoid flapping) if (alertSentFlag && currentTemperatureCelsius < cookingThreshold - 2.0) { // Update alert message and reset the alert flag Cloud variables alertSentFlag = false; alertMessage = "Temp normal. Last: " + String(currentTemperatureCelsius,1) + "C"; // Clear or update message Serial.println("Temperature back to normal. Alert reset. Cloud variables updated."); } } } /* This function is called automatically by the ArduinoCloud library every time the `cookingThreshold` variable is changed from the Cloud (e.g., from the dashboard). It is registered as a callback via ArduinoCloud.addProperty() in thingProperties.h */ void onCookingThresholdChange() { Serial.print("Cooking threshold changed by Cloud to: "); Serial.println(cookingThreshold); // When the threshold changes, it's good practice to re-evaluate the display // color and the alert status based on the *current* temperature and the *new* threshold. displayTemperatureOnLCD(); // Update LCD colors based on new threshold // Only re-check the alert status and update cloud variables if we are currently connected to the cloud. if (ArduinoCloud.connected()) { checkTemperatureAlert(); } else { Serial.println("Cloud not connected. Cannot re-evaluate alert status based on Cloud change yet."); } } /* All the other cloud variable callback functions (if any more Read/Write variables) would be defined here. For Read Only variables (from device perspective), you just assign values to them in your loop() or other functions (like updateCloudVariables). Example of a callback for a variable named 'myControl': void onMyControlChange() { Serial.print("myControl changed: "); Serial.println(myControl); // Do something with the new value of myControl } */ // --- IFTTT related code is commented out/removed --- /* // Example function if you decide to implement IFTTT later void sendIFTTTAlert(float currentTemp, float threshold) { // ... IFTTT code here ... } */