/* * reptile_mqtt.ino — XIAO ESP32C6 Reptile Monitor * Publishes SHT31 x2 sensor data via MQTT every 5 s. * Subscribes reptile/fan/cmd ("ON"/"OFF") — web button takes full priority. */ #include #include #include #include #include "Grove_Motor_Driver_TB6612FNG.h" #define WIFI_SSID "Omoshiro2G" #define WIFI_PASSWORD "qqqqqqqqqqqqq" #define MQTT_SERVER "192.168.11.201" #define MQTT_PORT 1883 #define MQTT_CLIENT_ID "reptile_monitor" #define TOPIC_SENSOR "reptile/sensor" #define TOPIC_FAN_CMD "reptile/fan/cmd" #define TEMP_FAN_ON 27.0f #define PUBLISH_INTERVAL_MS 5000UL WiFiClient wifiClient; PubSubClient mqttClient(wifiClient); Adafruit_SHT31 sht31_bottom; Adafruit_SHT31 sht31_top; MotorDriver motor; bool fanOn = false; bool manualOverride = false; unsigned long lastPublish = 0; void fanTurnOn() { motor.dcMotorRun(MOTOR_CHA, 255); fanOn = true; Serial.println("[Fan] ON"); } void fanTurnOff() { motor.dcMotorStop(MOTOR_CHA); fanOn = false; Serial.println("[Fan] OFF"); } // Read one SHT31 sensor. Returns false (and zeroes output) on NaN. bool readSensor(Adafruit_SHT31& sensor, const char* label, float& temp, float& hum) { temp = sensor.readTemperature(); hum = sensor.readHumidity(); if (isnan(temp) || isnan(hum)) { Serial.print("[SHT31] "); Serial.print(label); Serial.println(" read error."); temp = hum = 0.0f; return false; } return true; } void initSensor(Adafruit_SHT31& sensor, uint8_t addr, const char* label) { if (!sensor.begin(addr)) { Serial.print("[SHT31] "); Serial.print(label); Serial.println(" not found!"); } else { Serial.print("[SHT31] "); Serial.print(label); Serial.println(" OK."); } } void mqttCallback(char* topic, byte* payload, unsigned int length) { char msg[16]; unsigned int len = min(length, (unsigned int)(sizeof(msg) - 1)); memcpy(msg, payload, len); msg[len] = '\0'; Serial.print("[MQTT] cmd: "); Serial.println(msg); if (strcmp(msg, "ON") == 0) { manualOverride = true; fanTurnOn(); } else if (strcmp(msg, "OFF") == 0) { manualOverride = false; fanTurnOff(); } } void connectWiFi() { Serial.print("[WiFi] Connecting"); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.print(" IP: "); Serial.println(WiFi.localIP()); } void connectMQTT() { while (!mqttClient.connected()) { Serial.print("[MQTT] Connecting..."); if (mqttClient.connect(MQTT_CLIENT_ID)) { mqttClient.subscribe(TOPIC_FAN_CMD); Serial.println(" connected."); } else { Serial.print(" failed rc="); Serial.print(mqttClient.state()); Serial.println(". Retry 5s."); delay(5000); } } } void setup() { Serial.begin(115200); delay(500); Serial.println("=== Reptile Monitor MQTT ==="); Wire.setPins(22, 23); Wire.begin(); initSensor(sht31_bottom, 0x44, "Bottom"); initSensor(sht31_top, 0x45, "Top"); motor.init(0x14); mqttClient.setServer(MQTT_SERVER, MQTT_PORT); mqttClient.setCallback(mqttCallback); connectWiFi(); connectMQTT(); } void loop() { if (WiFi.status() != WL_CONNECTED) { Serial.println("[WiFi] Reconnecting..."); connectWiFi(); } if (!mqttClient.connected()) connectMQTT(); mqttClient.loop(); unsigned long now = millis(); if (now - lastPublish < PUBLISH_INTERVAL_MS) return; lastPublish = now; float temp_b, hum_b, temp_t, hum_t; bool valid_b = readSensor(sht31_bottom, "Bottom", temp_b, hum_b); readSensor(sht31_top, "Top", temp_t, hum_t); if (!manualOverride && valid_b) { if (temp_b > TEMP_FAN_ON && !fanOn) fanTurnOn(); else if (temp_b <= TEMP_FAN_ON && fanOn) fanTurnOff(); } char payload[160]; snprintf(payload, sizeof(payload), "{\"temp_b\":%.1f,\"hum_b\":%.1f,\"temp_t\":%.1f,\"hum_t\":%.1f,\"fan\":\"%s\",\"manual\":%d}", temp_b, hum_b, temp_t, hum_t, fanOn ? "ON" : "OFF", manualOverride ? 1 : 0); Serial.print("[MQTT] "); Serial.print(payload); Serial.println(mqttClient.publish(TOPIC_SENSOR, payload) ? " [OK]" : " [FAIL]"); }