arduino-sketches – Rev 4

Subversion Repositories:
Rev:
/*************************************************************************/
/*    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 <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#elif defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include <ESPmDNS.h>
#endif
// General libraries.
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#if defined(ARDUINO_ARCH_ESP32)
#include <FS.h>
#include <SPIFFS.h>
#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);

// 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;
  }

  // Do not process messages with an action key.
  if (doc.containsKey("action")) {
    return;
  }

  String state = (const char *)doc["state"];
  const int pin = (const int)doc["pin"];

  Serial.println("Setting pin: " + String(pin) + " to state: " + String(state));
  // Announce the action.
  StaticJsonDocument<256> msg;
  msg["pin"] = pin;
  msg["state"] = state;
  msg["action"] = "set";
  mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg));
  pinMode(PINS[pin], OUTPUT);
  if (state == "on") {
    digitalWrite(PINS[pin], HIGH);
    Serial.println("Pin set to HIGH");
    return;
  }

  digitalWrite(PINS[pin], LOW);
  Serial.println("Pin set to LOW");
}

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);
  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;
  }
}