ChangeLog

4th of December 2022

  • added the ability to retrieve pin status,
  • fixed broken JSON on send

Boilerplate WiFi Connect, OTA and pin toggling for ESP8266 and ESP32

The code is a boilerplate template that establishes a WiFi connection to an access point and then subscribes to an MQTT server. Once a characteristic JSON payload is received on the MQTT bus, the message is deserialized and a specific pin can be set to on or off. The template implements OTA such that it can be updated over the wire without requiring a physical connection.

Usage

By default the template subscribes to an MQTT server topic matching the chip identifier and whenever a message is received on the MQTT topic in the folllwing JSON format:

{
  "pin": PIN,
  "state": STATE,
  "action": "set"
}

where:

  • PIN is a GPIO pin number (as printed on the board, since the WeMoS may use a different numbering internally),
  • STATE is either the string on or off

then the board will set the state of the GPIO pin.

Conversely, in order to retrieve the state of a pin, send the following JSON to the subscribed MQTT topic:

{
  "pin": PIN,
  "action": "get"
}

where:

  • PIN is the pin number to retrieve the state from

Whenever the ESP looses and regains power, the board will re-connect to the configured WiFi network and also subscribe to the configured MQTT server.

Code

File: http://svn.grimore.org/arduino-sketches/arduinoPinToggle/arduinoPinToggle.ino -

/*************************************************************************/
/*    Copyright (C) 2023 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
 
// Removing comment for debugging over the first serial port.
// #define DEBUG 1
// 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
 
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
 
String mqttSerialize(StaticJsonDocument<256> msg) {
  char output[256];
  serializeJson(msg, output, 256);
  return String(output);
}
 
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);
#ifdef DEBUG
  Serial.println("Message received on topic: " + String(topic) + " with payload: " + String(msgPayload));
#endif
 
  // Parse the payload sent to the MQTT topic as a JSON document.
  StaticJsonDocument<256> doc;
#ifdef DEBUG
  Serial.println("Deserializing message....");
#endif
  DeserializationError error = deserializeJson(doc, msgPayload);
  if (error) {
#ifdef DEBUG
    Serial.println("Failed to parse MQTT payload as JSON: " + String(error.c_str()));
#endif
    return;
  }
 
  // Do not process messages without an action key.
  if (!doc.containsKey("action")) {
    return;
  }
 
  String action = (const char *)doc["action"];
  if (action == "set") {
    String state = (const char *)doc["state"];
    const int pin = (const int)doc["pin"];
#ifdef DEBUG
    Serial.println("Setting pin: " + String(pin) + " to state: " + String(state));
#endif
    pinMode(PINS[pin], OUTPUT);
 
    if (state == "on") {
      digitalWrite(PINS[pin], HIGH);
      int status = digitalRead(PINS[pin]);
#ifdef DEBUG
      Serial.println("Pin " + String(pin) + " state is now: " + String(status));
#endif
      return;
    }
 
    digitalWrite(PINS[pin], LOW);
    int status = digitalRead(PINS[pin]);
#ifdef DEBUG
    Serial.println("Pin " + String(pin) + " state is now: " + String(status));
#endif
    return;
  }
 
  if (action == "get") {
    const int pin = (const int)doc["pin"];
#ifdef DEBUG
    Serial.println("Getting pin: " + String(pin) + " state.");
#endif
    int status = digitalRead(PINS[pin]);
#ifdef DEBUG
    Serial.println("Pin " + String(pin) + " state is now: " + String(status));
#endif
    // Announce the action.
    StaticJsonDocument<256> msg;
    msg["pin"] = pin;
    switch (status) {
      case 1:
        msg["state"] = "on";
        break;
      case 0:
        msg["state"] = "off";
        break;
      default:
        msg["state"] = "unknown";
        break;
    }
 
    mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg).c_str());
    return;
  }
}
 
bool mqttConnect() {
#ifdef DEBUG
  Serial.println("Attempting to connect to MQTT broker: " + String(MQTT_HOST));
#endif
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
 
  StaticJsonDocument<256> msg;
  if (mqttClient.connect(MQTT_CLIENT_ID().c_str(), MQTT_USERNAME, MQTT_PASSWORD)) {
#ifdef DEBUG
    Serial.println("Established connection with MQTT broker using client ID: " + MQTT_CLIENT_ID());
#endif
    mqttClient.setCallback(mqttCallback);
    msg["action"] = "connected";
    mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg).c_str());
#ifdef DEBUG
    Serial.println("Attempting to subscribe to MQTT topic: " + MQTT_TOPIC());
#endif
    if (!mqttClient.subscribe(MQTT_TOPIC().c_str())) {
#ifdef DEBUG
      Serial.println("Failed to subscribe to MQTT topic: " + MQTT_TOPIC());
#endif
      return false;
    }
#ifdef DEBUG
    Serial.println("Subscribed to MQTT topic: " + MQTT_TOPIC());
#endif
    msg.clear();
    msg["action"] = "subscribed";
    mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg).c_str());
    return true;
  }
#ifdef DEBUG
  Serial.println("Connection to MQTT broker failed with MQTT client state: " + String(mqttClient.state()));
#endif
  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);
#ifdef DEBUG
  Serial.println("Booted, setting up Wifi in 10s...");
#endif
  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) {
#ifdef DEBUG
    Serial.println("Failed to connect to Wifi, rebooting in 5s...");
#endif
    delay(5000);
    ESP.restart();
  }
#ifdef DEBUG
  Serial.print("Connected to Wifi: ");
#endif
  Serial.println(WiFi.localIP());
#ifdef DEBUG
  Serial.println("Setting up OTA in 10s...");
#endif
  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
#ifdef DEBUG
        Serial.println("OTA start updating sketch.");
#endif
        break;
#if defined(ARDUINO_ARCH_ESP8266)
      case U_FS:
#elif defined(ARDUINO_ARCH_ESP32)
      case U_SPIFFS:
#endif
#ifdef DEBUG
        Serial.println("OTA start updating filesystem.");
#endif
        SPIFFS.end();
        break;
      default:
#ifdef DEBUG
        Serial.println("Unknown OTA update type.");
#endif
        break;
    }
  });
  ArduinoOTA.onEnd([]() {
#ifdef DEBUG
    Serial.println("OTA update complete.");
#endif
    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) {
#ifdef DEBUG
    Serial.printf("OTA update progress: %u%%\r", (progress / (total / 100)));
#endif
  });
  ArduinoOTA.onError([](ota_error_t error) {
#ifdef DEBUG
    Serial.printf("OTA update error [%u]: ", error);
#endif
    switch (error) {
      case OTA_AUTH_ERROR:
#ifdef DEBUG
        Serial.println("OTA authentication failed");
#endif
        break;
      case OTA_BEGIN_ERROR:
#ifdef DEBUG
        Serial.println("OTA begin failed");
#endif
        break;
      case OTA_CONNECT_ERROR:
#ifdef DEBUG
        Serial.println("OTA connect failed");
#endif
        break;
      case OTA_RECEIVE_ERROR:
#ifdef DEBUG
        Serial.println("OTA receive failed");
#endif
        break;
      case OTA_END_ERROR:
#ifdef DEBUG
        Serial.println("OTA end failed");
#endif
        break;
      default:
#ifdef DEBUG
        Serial.println("Unknown OTA failure");
#endif
        break;
    }
    ESP.restart();
  });
  ArduinoOTA.begin();
 
  // Set up MQTT client.
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  mqttClient.setCallback(mqttCallback);
 
  // Touchdown.
#ifdef DEBUG
  Serial.println("Setup complete.");
#endif
}
 
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:
#ifdef DEBUG
      Serial.println("No Wifi shield present.");
#endif
      goto DEFAULT_CASE;
      break;
    case WL_NO_SSID_AVAIL:
#ifdef DEBUG
      Serial.println("Configured SSID not found.");
#endif
      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:
#ifdef DEBUG
      Serial.println("Wifi connection failed with status: " + String(wifiStatus));
#endif
DEFAULT_CASE:
      delay(10000);
      ESP.restart();
      break;
  }
}

arduino/esp-pin-toggle.txt ยท Last modified: 2022/12/04 01:00 by office

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.