/*************************************************************************/ /* Copyright (C) 2021 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 // Define LED strip PWM pins. #if defined(ARDUINO_ARCH_ESP8266) #define R_PIN D5 #define G_PIN D6 #define B_PIN D7 #elif defined(ARDUINO_ARCH_ESP32) #define R_PIN 33 #define G_PIN 34 #define B_PIN 35 #endif 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); /*************************************************************************/ /* Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 */ /*************************************************************************/ int wasMapValueToRange(int v, int xMin, int xMax, int yMin, int yMax) { return yMin + ( ( yMax - yMin ) * ( v - xMin ) / ( xMax - xMin ) ); } 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; } Serial.println("Message deserialized...."); // 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; } // Set the pin values while mapping from RGB to [0, 3.3]V (PWMRANGE) const int r = wasMapValueToRange((const int)doc["r"], 0, 255, 0, PWMRANGE); analogWrite(R_PIN, r); const int g = wasMapValueToRange((const int)doc["g"], 0, 255, 0, PWMRANGE); analogWrite(G_PIN, g); const int b = wasMapValueToRange((const int)doc["b"], 0, 255, 0, PWMRANGE); analogWrite(B_PIN, b); Serial.println("R: " + String(r) + ", G: " + String(g) + ", B: " + String(b)); // Announce the action. StaticJsonDocument<256> msg; msg["R"] = r; msg["G"] = g; msg["B"] = b; char msgPublish[256]; serializeJson(msg, msgPublish); mqttClient.publish(MQTT_TOPIC().c_str(), (const char*) msgPublish); } bool mqttConnect() { Serial.println("Attempting to connect to MQTT broker: " + String(mqtt_host)); mqttClient.setServer(mqtt_host, mqtt_port); StaticJsonDocument<256> msg; char msgPublish[256]; 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"; serializeJson(msg, msgPublish); mqttClient.publish(MQTT_TOPIC().c_str(), (const char*) msgPublish); 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"; serializeJson(msg, msgPublish); mqttClient.publish(MQTT_TOPIC().c_str(), (const char*) msgPublish); 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); // Initialize pins. pinMode(R_PIN, OUTPUT); analogWrite(R_PIN, 0); pinMode(G_PIN, OUTPUT); analogWrite(G_PIN, 0); pinMode(B_PIN, OUTPUT); analogWrite(B_PIN, 0); 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); while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("Failed to connect to Wifi, 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; } }