/*************************************************************************/ /* Copyright (C) 2020 Wizardry and Steamworks - License: GNU GPLv3 */ /*************************************************************************/ // The AP to connect to via Wifi. #define STA_SSID "" // The AP Wifi password. #define STA_PSK "" // The MQTT broker to connect to. #define MQTT_HOST "" // The MQTT broker username. #define MQTT_USERNAME "" // The MQTT broker password. #define MQTT_PASSWORD "" // The MQTT broker port. #define MQTT_PORT 1883 // The default MQTT client ID is "esp-CHIPID" where CHIPID is the ESP8266 // or ESP32 chip identifier. #define MQTT_CLIENT_ID() String("esp-" + String(GET_CHIP_ID(), HEX)) // The authentication password to use for OTA updates. #define OTA_PASSWORD "" // The OTA port on which updates take place. #define OTA_PORT 8266 // The default topic that the sketch subscribes to is "esp/CHIPID" where // CHIPID is the ESP8266 or ESP32 chip identifier. #define MQTT_TOPIC() String("esp/" + String(GET_CHIP_ID(), HEX)) // Platform specific defines. #if defined(ARDUINO_ARCH_ESP8266) #define GET_CHIP_ID() (ESP.getChipId()) #elif defined(ARDUINO_ARCH_ESP32) #define GET_CHIP_ID() ((uint16_t)(ESP.getEfuseMac()>>32)) #endif // Miscellaneous defines. //#define CHIP_ID_HEX (String(GET_CHIP_ID()).c_str()) #define HOSTNAME() String("esp-" + String(GET_CHIP_ID(), HEX)) // Platform specific libraries. #if defined(ARDUINO_ARCH_ESP8266) #include #include #elif defined(ARDUINO_ARCH_ESP32) #include #include #endif // General libraries. #include #include #include #include #if defined(ARDUINO_ARCH_ESP32) #include #include #endif // DHT11 #include #include #include const char *sta_ssid = STA_SSID; const char *sta_psk = STA_PSK; const char *mqtt_host = MQTT_HOST; const char *mqtt_username = MQTT_USERNAME; const char *mqtt_password = MQTT_PASSWORD; const int mqtt_port = MQTT_PORT; const char *ota_password = OTA_PASSWORD; const int ota_port = OTA_PORT; WiFiClient espClient; PubSubClient mqttClient(espClient); // Uncomment the type of sensor in use: #define DHTTYPE DHT11 // DHT 11 //#define DHTTYPE DHT22 // DHT 22 (AM2302) //#define DHTTYPE DHT21 // DHT 21 (AM2301) // Define GPIO pins for supported architectures. #if defined(ARDUINO_ARCH_ESP8266) int PINS[] = { D0, D1, D2, D3, D4, D5, D6, D7, D8 }; #elif defined(ARDUINO_ARCH_ESP32) int PINS[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39 }; #endif const char* mqttSerialize(StaticJsonDocument<256> msg) { char message[256]; serializeJson(msg, message); return (const char*) message; } void mqttCallback(char *topic, byte *payload, unsigned int length) { String msgTopic = String(topic); // payload is not null terminated and casting will not work char msgPayload[length + 1]; snprintf(msgPayload, length + 1, "%s", payload); Serial.println("Message received on topic: " + String(topic) + " with payload: " + String(msgPayload)); // Parse the payload sent to the MQTT topic as a JSON document. StaticJsonDocument<256> doc; Serial.println("Deserializing message...."); DeserializationError error = deserializeJson(doc, msgPayload); if (error) { Serial.println("Failed to parse MQTT payload as JSON: " + String(error.c_str())); return; } // Ignore message with no identifier in the payload. if (!doc.containsKey("id")) { return; } // Do not listen to self. String id = (const char *)doc["id"]; if (id == String(MQTT_CLIENT_ID().c_str())) { return; } // Reject messages that do not provide a reading pin. if (!doc.containsKey("pin")) { Serial.println("MQTT message received but no pin supplied..."); return; } const int pin = (const int)doc["pin"]; StaticJsonDocument<256> msg; msg["id"] = String(MQTT_CLIENT_ID().c_str()); DHT_Unified dht(PINS[pin], DHTTYPE); dht.begin(); // Print temperature sensor details. sensors_event_t event; dht.temperature().getEvent(&event); switch (!isnan(event.temperature)) { case true: msg["temperature"] = event.temperature; break; default: Serial.println("Error reading temperature..."); break; } dht.humidity().getEvent(&event); switch (!isnan(event.relative_humidity)) { case true: msg["humidity"] = event.relative_humidity; break; default: Serial.println("Error reading humidity..."); break; } // Eeek? //dht.end(); // Publish the sensor data. mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg)); } bool mqttConnect() { Serial.println("Attempting to connect to MQTT broker: " + String(mqtt_host)); mqttClient.setServer(mqtt_host, mqtt_port); StaticJsonDocument<255> msg; if (mqttClient.connect(MQTT_CLIENT_ID().c_str(), mqtt_username, mqtt_password)) { Serial.println("Established connection with MQTT broker using client ID: " + MQTT_CLIENT_ID()); mqttClient.setCallback(mqttCallback); msg["action"] = "connected"; mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg)); Serial.println("Attempting to subscribe to MQTT topic: " + MQTT_TOPIC()); if (!mqttClient.subscribe(MQTT_TOPIC().c_str())) { Serial.println("Failed to subscribe to MQTT topic: " + MQTT_TOPIC()); return false; } Serial.println("Subscribed to MQTT topic: " + MQTT_TOPIC()); msg.clear(); msg["action"] = "subscribed"; mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg)); return true; } else { Serial.println("Connection to MQTT broker failed with MQTT client state: " + String(mqttClient.state())); } return false; } bool loopWifiConnected() { // Process OTA loop first since emergency OTA updates might be needed. ArduinoOTA.handle(); // Process MQTT client loop. if (!mqttClient.connected()) { // If the connection to the MQTT broker has failed then sleep before carrying on. if (!mqttConnect()) { return false; } } mqttClient.loop(); return true; } void setup() { Serial.begin(115200); Serial.println("Booted, setting up Wifi in 10s..."); delay(10000); WiFi.mode(WIFI_STA); #if defined(ARDUINO_ARCH_ESP8266) WiFi.hostname(HOSTNAME().c_str()); #elif defined(ARDUINO_ARCH_ESP32) WiFi.setHostname(HOSTNAME().c_str()); #endif WiFi.begin(sta_ssid, sta_psk); uint8_t result = WiFi.waitForConnectResult(); while (result != WL_CONNECTED) { Serial.print("Failed to connect to Wifi: "); Serial.println(result); Serial.println("Rebooting in 5s..."); delay(5000); ESP.restart(); } Serial.print("Connected to Wifi: "); Serial.println(WiFi.localIP()); Serial.println("Setting up OTA in 10s..."); delay(10000); // Port defaults to 8266 ArduinoOTA.setPort(ota_port); // Hostname defaults to esp-[ChipID] ArduinoOTA.setHostname(HOSTNAME().c_str()); // Set the OTA password ArduinoOTA.setPassword(ota_password); ArduinoOTA.onStart([]() { switch (ArduinoOTA.getCommand()) { case U_FLASH: // Sketch Serial.println("OTA start updating sketch."); break; #if defined(ARDUINO_ARCH_ESP8266) case U_FS: #elif defined(ARDUINO_ARCH_ESP32) case U_SPIFFS: #endif Serial.println("OTA start updating filesystem."); SPIFFS.end(); break; default: Serial.println("Unknown OTA update type."); break; } }); ArduinoOTA.onEnd([]() { Serial.println("OTA update complete."); SPIFFS.begin(); #if defined(ARDUINO_ARCH_ESP8266) // For what it's worth, check the filesystem on ESP8266. SPIFFS.check(); #endif ESP.restart(); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("OTA update progress: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("OTA update error [%u]: ", error); switch (error) { case OTA_AUTH_ERROR: Serial.println("OTA authentication failed"); break; case OTA_BEGIN_ERROR: Serial.println("OTA begin failed"); break; case OTA_CONNECT_ERROR: Serial.println("OTA connect failed"); break; case OTA_RECEIVE_ERROR: Serial.println("OTA receive failed"); break; case OTA_END_ERROR: Serial.println("OTA end failed"); break; default: Serial.println("Unknown OTA failure"); break; } ESP.restart(); }); ArduinoOTA.begin(); // Set up MQTT client. mqttClient.setServer(mqtt_host, mqtt_port); mqttClient.setCallback(mqttCallback); // Touchdown. Serial.println("Setup complete."); } void loop() { // Check the Wifi connection status. int wifiStatus = WiFi.status(); switch (wifiStatus) { case WL_CONNECTED: if (!loopWifiConnected()) { delay(1000); break; } delay(1); break; case WL_NO_SHIELD: Serial.println("No Wifi shield present."); goto DEFAULT_CASE; break; case WL_NO_SSID_AVAIL: Serial.println("Configured SSID not found."); goto DEFAULT_CASE; break; // Temporary statuses indicating transitional states. case WL_IDLE_STATUS: case WL_SCAN_COMPLETED: delay(1000); break; // Fatal Wifi statuses trigger a delayed ESP restart. case WL_CONNECT_FAILED: case WL_CONNECTION_LOST: case WL_DISCONNECTED: default: Serial.println("Wifi connection failed with status: " + String(wifiStatus)); DEFAULT_CASE: delay(10000); ESP.restart(); break; } }