Appunti per l'integrazione di Prodino ESP 8266 v. 1.0 con MQTT

Appunti per l'integrazione di Prodino ESP 8266 v. 1.0 con MQTT

Queste note hanno fine esemplificativo.

Per il setup dell'ambiente di programmazione su IDE Arduino vedi link in fondo a questa pagina.

Codice

Questo codice e' tratto quasi integralmente dall'esempio di KMP (vedi link in fondo a questa pagina). L'aggiunta sostanziale e' la parte mDNS che lo rende completamente generico se il server MQTT si trova sul segmento di rete locale ed e' pubblicato su mDNS come descritto nel commento iniziale.

Essendo il codice dotato gia' in origine di supporto WiFiManager, anche il collegamento alla infrastruttura wifi e' del tutto dimanico (il codice genera un suo Access Point nel caso non riesca a collegarsi ad una infrastruttura).

// WiFiCloudDHTMqtt.ino
// Company: KMP Electronics Ltd, Bulgaria
// Web: http://kmpelectronics.eu/
// Supported boards:
//    KMP ProDino WiFi-ESP WROOM-02 (http://www.kmpelectronics.eu/en-us/products/prodinowifi-esp.aspx)
// Description:
//    Cloud MQTT example with DHT support. In this example we show how to connect KMP ProDino WiFi-ESP WROOM-02 with Amazon cloudmqtt.com service 
//      and measure humidity and temperature with DHT22 sensor. For connect with WiFi we use WiFiManager.
// Example link: http://www.kmpelectronics.eu/en-us/examples/prodinowifi-esp/wifiwebrelayserverap.aspx
// Version: 1.0.0
// Date: 04.06.2017
// Author: Plamen Kovandjiev 
//
// =========================================================================================
//
// Date: 12.12.2017 - Andrea Montefusco - experiments with MQTT server on the local network
//
// Before try to compile, check an issue I discovered:
//
// https://github.com/kmpelectronics/Arduino/issues/3
//
// This example try first to connect to a statically defined MQTT server defined as
// per following constants values:
// const char* MQTT_SERVER = "xxx.cloudmqtt.com"; 
// const char* MQTT_PORT   = 1833; 
//
// Preferably the MQTT server cna be searched even dynamically (if present and publisched) on the local broadcast directed 
// segment, using mDNS:
//
// const char* MQTT_SERVER_MDNS = "xxx.cloudmqtt.com"; 
// const char* MQTT_PORT_MNDS   = 1833; 
//
// the above assumes that MNQTT server has been published with the following command (to be executed where the mosquitt
// daemon is running:
//
//  avahi-publish -s "MTT server for ProdinoWiFi" _mqtt_prodino._tcp 1833 "string2"
//
//
// Examples for local mqtt server:
//
// in order to receive info and input statuses, subscribe at  
// mosquitto_sub  -h localhost -t '/KMP/ProDinoWiFi/nInfo'
// You will receive status of optoisolated inputs:
// optoIn:0:1
// optoIn:0:0
// optoIn:0:1
//
// and even the status of relay (if commanded):
//  
// rel:1:1
// rel:3:0
// rel:1:0
// rel:1:1
//
// The relay can be driven as follows:
//
// # turn on the last relay
// mosquitto_pub -h localhost -t '/KMP/ProDinoWiFi/Cmd' -m 'rel:3:1'
//
// # turn off the last relay
// mosquitto_pub -h localhost -t '/KMP/ProDinoWiFi/Cmd' -m 'rel:3:9'
//

#include 
#include 
#include 
#include 
#include 

#include             //Local DNS Server used for redirecting all requests to the configuration portal
#include      //Local WebServer used to serve the configuration portal
#include           //https://github.com/tzapu/WiFiManager WiFi Configuration Magic

#include           // mDSN library (needed to detect local mqtt transparently


// MQTT server settings static/global DNS definitions.
const char* MQTT_SERVER = "xxxx.test.mosquitto.org"; // Test  mosquitto server (remove xxxx.).
// const char* MQTT_SERVER = "192.168.0.31"; // Your local server (statically defined)
const int MQTT_PORT = 1883;                   // Your server port.
const char* MQTT_CLIENT_ID = "ESP8266Client";  // Default server name.
//const char* MQTT_USER = "xxxxxxxx";            // Your xxxxxxxx MQTT server user.
//const char* MQTT_PASS = "xxxxxxxxxxxx";        // Your xxxxxxxxxxxx MQTT server user password.


// MQTT server settings dynamic (mDNS) local definitions.
const char* MQTT_SERVER_MNDS = "prodino_mqtt"; // Please note that the trailing ".local" suffix is implicitly defined !!!


// MQTT topics
const char* TOPIC_INFO = "/KMP/ProDinoWiFi/nInfo";
const char* TOPIC_INFO_DHT_T = "/KMP/ProDinoWiFi/Info/dhtt";
const char* TOPIC_INFO_DHT_H = "/KMP/ProDinoWiFi/Info/dhth";
const char* TOPIC_COMMAND = "/KMP/ProDinoWiFi/Cmd";
const char* CMD_ALL = "all";
const char* CMD_REL = "rel";
const char* CMD_OPTOIN = "optoIn";
const char* CMD_DHT_H = "dht";
const char* CMD_DHT_T = "dht";
const char CMD_SEP = ':';

DHT _dhtSensor(EXT_GROVE_D0, DHT22, 11);
// Contains last measured humidity from sensor.
float _humidity;
// Contains last measured temperature from sensor.
float _temperature;

// Check sensor data, interval in milliseconds.
const long CHECK_HT_INTERVAL_MS = 10000;
// Store last measure time.
unsigned long _mesureTimeout;

// Declares a ESP8266WiFi client.
WiFiClient _wifiClient;
// Declare a MQTT client.
PubSubClient _mqttClient(_wifiClient);

// There arrays store last states by relay and optical isolated inputs.
bool _lastRelayStatus[4] = { false };
bool _lastOptoInStatus[4] = { false };

// Buffer by send output state.
char _payload[16];
bool _sendAllData;

/**
* @brief Execute first after start device. Initialize hardware.
*
* @return void
*/
void setup(void)
{
    // You can open the Arduino IDE Serial Monitor window to see what the code is doing
    // Serial connection from ESP-01 via 3.3v console cable
    Serial.begin(115200);
    // Init KMP ProDino WiFi-ESP board.
    KMPDinoWiFiESP.init();
  
    Serial.println("KMP Mqtt cloud client example.\r\n");

    //WiFiManager
    //Local initialization. Once its business is done, there is no need to keep it around
    WiFiManager wifiManager;
  //wifiManager.resetSettings();
    // Is OptoIn 4 is On the board is resetting WiFi configuration.
    if (KMPDinoWiFiESP.GetOptoInState(OptoIn4))
    {
        Serial.println("Resetting WiFi configuration...\r\n");
        //reset saved settings
        wifiManager.resetSettings();
        Serial.println("WiFi configuration was reset.\r\n");
    }

    //fetches ssid and pass from eeprom and tries to connect
    //if it does not connect it starts an access point with the specified name
    //auto generated name ESP + ChipID
    wifiManager.autoConnect();

    // Initialize MQTT.
    _mqttClient.setCallback(callback);

    _sendAllData = true;

  Serial.printf("MAC: %s\n", WiFi.macAddress().c_str());

  ///////////////////////////////////////////////////////////
  //
  // mDNS client and responder
  //
  ///////////////////////////////////////////////////////////
  if (!MDNS.begin(("sonoff-"+ WiFi.macAddress()).c_str())) {
    Serial.println("Error setting up MDNS responder!");
  }
  Serial.println("mDNS responder started");
  MDNS.addService(("sonoff-"+WiFi.macAddress()).c_str(), "tcp", 8080); // Announce esp tcp service on port 8080

  Serial.println("END OF SETUP");
}

/**
* @brief Callback method. It is fire when has information in subscribed topic.
*
* @return void
*/
void callback(char* topic, byte* payload, unsigned int length) {
    Serial.print("Subscribed topic [");
    Serial.print(topic);
    Serial.print("]");

    // Check topic.
    if (strncmp(TOPIC_COMMAND, topic, strlen(TOPIC_COMMAND)) != 0)
    {
        Serial.println("Is not valid.");
        return;
    }

    Serial.print(" payload [");

    for (uint i = 0; i < length; i++)
    {
        Serial.print((char)payload[i]);
    }
    Serial.println("]");

    // Command send all data.
    if (strncmp((const char*)payload, CMD_ALL, strlen(CMD_ALL)) == 0)
    {
        _sendAllData = true;
        return;
    }

    // Relay command.
    // Command structure: [command (rel):relay number (0..3):relay state (0 - Off, 1 - On)]. Example: rel:0:0 
    size_t cmdRelLen = strlen(CMD_REL);

    if (strncmp(CMD_REL, (const char*)payload, cmdRelLen) == 0 && length >= cmdRelLen + 4)
    {
        KMPDinoWiFiESP.SetRelayState(CharToInt(payload[4]), CharToInt(payload[6]) == 1);
    }
}

/**
* @brief Main method.
*
* @return void
*/
void loop(void)
{
    // By the normal device work need connected with WiFi and MQTT server.
    if (!ConnectWiFi() || !ConnectMqtt())
    {
        return;
    }

    _mqttClient.loop();

    // Publish information in MQTT.
    PublishInformation();
}

/**
* @brief Publish information in the MQTT server.
*
* @return void
*/
void PublishInformation()
{
    char state[2];
    state[1] = '\0';
    // Get current Opto input and relay statuses.
    for (byte i = 0; i < RELAY_COUNT; i++)
    {
        bool rState = KMPDinoWiFiESP.GetRelayState(i);
        if (_lastRelayStatus[i] != rState || _sendAllData)
        {
            _lastRelayStatus[i] = rState;
            state[0] = rState ? '1' : '0';
            buildPayload(_payload, CMD_REL, CMD_SEP, i, state);
            Publish(TOPIC_INFO, _payload);
        }
    }

    for (byte i = 0; i < OPTOIN_COUNT; i++)
    {
        bool oiState = KMPDinoWiFiESP.GetOptoInState(i);
        if (_lastOptoInStatus[i] != oiState || _sendAllData)
        {
            _lastOptoInStatus[i] = oiState;
            state[0] = oiState ? '1' : '0';
            buildPayload(_payload, CMD_OPTOIN, CMD_SEP, i, state);

            Publish(TOPIC_INFO, _payload);
        }
    }

    GetDHTSensorData();

    _sendAllData = false;
}

/**
* @brief Read data from sensors a specified time.
*
* @return void
*/
void GetDHTSensorData()
{
    if (millis() > _mesureTimeout || _sendAllData)
    {
        _dhtSensor.read(true);
        float humidity = _dhtSensor.readHumidity();
        float temperature = _dhtSensor.readTemperature();

        if (_humidity != humidity || _sendAllData)
        {
            FloatToChars(humidity, 1, _payload);
            _humidity = humidity;
            Publish(TOPIC_INFO_DHT_H, _payload);
        }

        if (_temperature != temperature || _sendAllData)
        {
            FloatToChars(temperature, 1, _payload);
            _temperature = temperature;
            Publish(TOPIC_INFO_DHT_T, _payload);
        }

        // Set next time to read data.
        _mesureTimeout = millis() + CHECK_HT_INTERVAL_MS;
    }
}

/**
* @brief Build publish payload.
* @param buffer where fill payload.
* @param command description
* @param number device number
* @param state device state
*
* @return void
*/
void buildPayload(char* buffer, const char* command, char separator, byte number, const char* state)
{
    int cmdLen = strlen(command);
    memcpy(buffer, command, cmdLen);
    buffer[cmdLen++] = separator;
    buffer[cmdLen++] = IntToChar(number);
    buffer[cmdLen++] = separator;
    buffer += cmdLen;
    int stLen = strlen(state);
    memcpy(buffer, state, stLen);
    buffer[stLen] = '\0';
}

/**
* @brief Publish topic.
* @param topic title.
* @param payload data to send
*
* @return void
*/
void Publish(const char* topic, char* payload)
{
    Serial.print("Publish topic [");
    Serial.print(topic);
    Serial.print("] payload [");
    Serial.print(_payload);
    Serial.println("]");

    _mqttClient.publish(topic, (const char*)_payload);
}

/**
* @brief Connect to WiFi access point.
*
* @return bool true - success.
*/
bool ConnectWiFi()
{
    if (WiFi.status() != WL_CONNECTED)
    {
        Serial.print("Reconnecting [");
        Serial.print(WiFi.SSID());
        Serial.println("]...");

        WiFi.begin();
        //WiFi.begin(SSID, SSID_PASSWORD);

        if (WiFi.waitForConnectResult() != WL_CONNECTED)
        {
            return false;
        }

        Serial.print("IP address: ");
        Serial.println(WiFi.localIP());
    }

    return true;
}

/**
* @brief Connect to MQTT server.
*
* @return bool true - success.
*/
bool ConnectMqtt()
{
    if (!_mqttClient.connected())
    {
    // reset ther server IP address as it could be changed by mDNS procedure (see below)
    _mqttClient.setServer(MQTT_SERVER, MQTT_PORT);
  
        Serial.print("Attempting MQTT connection to ");
    Serial.println(MQTT_SERVER);
    Serial.print("Port: ");
    Serial.println(MQTT_PORT);
    
        //if (_mqttClient.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS))
    if (_mqttClient.connect(MQTT_CLIENT_ID))
    {
            Serial.println("Connected.");
            _mqttClient.subscribe(TOPIC_COMMAND);
        }
        else
        {
            Serial.print("failed, rc=");
            Serial.print(_mqttClient.state());
        Serial.println(" try again after 5 seconds");

 
      Serial.println("Search over mDNS");

      // search for MQTT server on the local segment
      // assume that MNQTT server published with the following command
      // avahi-publish -s "MTT server for ProdinoWiFi" _mqtt_prodino._tcp 1833 "string2"

      int n = MDNS.queryService(MQTT_SERVER_MNDS, "tcp"); // Send out query for esp tcp services
      Serial.println("mDNS query done");
      if (n == 0) {
         Serial.println("no services found");
      } else {
         Serial.print(n);
         Serial.println(" service(s) found");
         for (int i = 0; i < n; ++i) {
           // Print details for each service found
            Serial.print(i + 1);
            Serial.print(": ");
            Serial.print(MDNS.hostname(i));
            Serial.print(" (");
            Serial.print(MDNS.IP(i));
            Serial.print(":");
            Serial.print(MDNS.port(i));
            Serial.println(")");
            //mqtt_server = MDNS.IP(i);
            //mqtt_server_found = true;
            //mdns_f = 1;
            
            _mqttClient.setServer(MDNS.IP(i), MDNS.port(i));

            if (_mqttClient.connect(MQTT_CLIENT_ID)) {
               Serial.println("Connected.");
               _mqttClient.subscribe(TOPIC_COMMAND);
            } else {
               Serial.print("failed, rc=");
               Serial.print(_mqttClient.state());
               Serial.println(" try again after 5 seconds");
            }
         }
    }
     
            // Wait 5 seconds before retrying
            delay(5000);
        }
    }

    return _mqttClient.connected();
}

Link

  • KMP examples
  • [https://www.kmpelectronics.eu/en-us/examples/prodinowifi-esp/howtoinstall.aspx](How to install board and examples)

Andrea Montefusco
Currently employed as network architect, always Internet working man, real C/C++ programmer in the past, network and Unix system engineer as needed, HAM Radio enthusiast (former IW0RDI, now IW0HDV), aeromodeller (a person who builds and flies model airplanes) since 1976 (ex FAI10655).
http://www.montefusco.com - https://github.com/amontefusco - https://github.com/IW0HDV - andrew@montefusco.com



Le attività del TanzoLab si svolgono ogni mercoledi sera, salvo casi speciali, dalle ore 18:30 presso i locali della Acme Systems srl e consistono in:

  • Talk monotematici a cura di professionisti in vari settori tecnologici
  • Workshop pratici su elettronica embedded, produzione e informatica
  • Progettazione e realizzazione di nuovi prodotti embedded per l'IT

Le attività vengono coordinate tramite questo sito, in cui vengono pubblicati tutti i lavori svolti o in via di sviluppo, e tramite un gruppo Telegram con cui per interagire direttamente via chat con gli altri membri.