Cliente MQTT para PLC basado en Arduino como módulo de E/S

25 de enero de 2019 por
Cliente MQTT para PLC basado en Arduino como módulo de E/S
Bernat Garcia

Introducción

En este post se muestra cómo configurar su PLC basado en Arduino industrial como un módulo de E/S MQTT.  

Eche un vistazo a la biblioteca MQTT para obtener más información sobre las funciones: Post MQTT


Además también se utiliza la biblioteca ArduinoJson, echar un vistazo en el Github: Librería ArduinoJson


La arquitectura del código está hecha para un M-Duino 21+, pero está diseñado para ser extendido para otros M-Duino. Hay cuatro matrices diferentes que incluyen todas las E/S disponibles en M-Duino 21+. 

Estas matrices son:  digitalOutputs , analogOutputs, digitalInputs, analogInputs


Por lo tanto, cada matriz se estructurará de la siguiente manera: 

digitalOutputs[NUM_ZONES][NUM_DIGITAL_OUTPUTS_PER_ZONE] = {{Q0_0, Q0_1... }, {Q1_0, Q1_1...}, {Q2_0, Q2_1...} 


Este último ejemplo será para un M-Duino 58+, cada zona se subdivide con su nomenclatura de pines relacionados.

Después de explicar cómo acceder a la asignación de entradas estamos listos para explicar el resto del código. 

En el setup() sólo estamos haciendo la inicialización del cliente Ethernet y MQTT llamado mqtt, relacionando la función de devolución de llamada cuando recibimos un mensaje a través de MQTT y todos los valores de entrada reales. Después de eso sólo tenemos tres funciones principales en la función loop(). Estamos llamando a la función reconnect() si no hay conexión con el servidor, si hay conexión sólo estamos utilizando la función mqtt.loop() que mantendrá la conectividad con nuestro servidor.  Aparte de estas dos funciones tenemos el updateInputs() que actualizará el valor real de las entradas.

Vamos a profundizar en las funciones para tener una mejor subestimación de lo que hacen:


GESTIÓN DE LA SALIDA:

En primer lugar, tenemos la función reconnect() que suscribirá los temas "Q" y "A". Un tema está dedicado a las salidas digitales (Q) y el otro está dedicado a las salidas analógicas (A). Si no está conectado con el servidor, después pararán la conexión del cliente para no causar ningún problema con los puertos del servidor. 

En segundo lugar, para entender cómo funciona el control de salidas necesitamos buscar en la función de devolución de llamada.  En este programa la función de devolución de llamada es receiveMqttMessage(). La función receiveMqttMessage() se ejecutará cada vez que recibamos un mensaje.  Por lo tanto, cuando recibamos un mensaje, la función de devolución de llamada decidirá si el tema es para pines analógicos o digitales y luego llamará a la función correcta para ello,función setDigitalOutput() o setAnalgoOutput().

setDigitalOutput() extrae y analiza el archivo JSON, confirme que la zona y el pin existen y, a continuación, ejecute la función digitalWrite() para actualizar la salida deseada. 

La función setAnalogOutput() funciona exactamente de la misma manera que setDigitalOutput().  

ANALOG_OUTPUTS_OFFSET se utiliza para desplazar el valor de los pines analógicos. Los pines analógicos se colocan en las posiciones 5, 6 ,7. Esta es la razón por la que el ANALOG_OUTPUTS_OFFSET es 5.


GESTIÓN DE ENTRADAS:

En el bucle principal tenemos la función updateInputs() que actualizará todos los valores de las entradas.  Esta función comparará todos los valores anteriores con los actuales y si el nuevo valor ha cambiado, hen el programa publicará el nuevo valor a través de MQTT. Los valores digitales se comperarán si hay un 0 o un 1, la función que hace que esto funcione es updateDigitalInput().

En el sitio analógico el programa comparará si el valor analógico ha cambiado más de 5 puntos,  si el valor actual es mayor o menor que el anterior, publicará el nuevo valor. La función que hace que esto funcione es updateAnalogInput().


/*
   Copyright (c) 2019 Boot&Work Corp., S.L. All rights reserved
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.
   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


#include <Ethernet.h>

// libraries needed for MQTT communication
#include <ArduinoJson.h>
#include <PubSubClient.h>
#define MQTT_ID "demo"
#define NUM_ZONES 1
#define NUM_DIGITAL_OUTPUTS_PER_ZONE 5
#define DIGITAL_OUTPUTS_OFFSET 0

const int digitalOutputs[NUM_ZONES][NUM_DIGITAL_OUTPUTS_PER_ZONE] = {
  {Q0_0, Q0_1, Q0_2, Q0_3, Q0_4},
};
#define NUM_ANALOG_OUTPUTS_PER_ZONE 3
#define ANALOG_OUTPUTS_OFFSET 5

const int analogOutputs[NUM_ZONES][NUM_DIGITAL_OUTPUTS_PER_ZONE] = {
  {A0_5, A0_6, A0_7},
};
#define NUM_DIGITAL_INPUTS_PER_ZONE 7
#define DIGITAL_INPUTS_OFFSET 0

const int digitalInputs[NUM_ZONES][NUM_DIGITAL_INPUTS_PER_ZONE] = {
  {I0_0, I0_1, I0_2, I0_3, I0_4, I0_5, I0_6},
};
#define NUM_ANALOG_INPUTS_PER_ZONE 6
#define ANALOG_INPUTS_OFFSET 7
#define ANALOG_INPUTS_THRESHOLD 5 // Filtering threshold

const int analogInputs[NUM_ZONES][NUM_ANALOG_INPUTS_PER_ZONE] = {
  {I0_7, I0_8, I0_9, I0_10, I0_11, I0_12},
};
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xAE };
IPAddress broker(10, 0, 3, 21);
unsigned port = 1883;
// Initialize client
EthernetClient client;
PubSubClient mqtt(client);
int digitalInputsValues[NUM_ZONES][NUM_DIGITAL_INPUTS_PER_ZONE];
int analogInputsValues[NUM_ZONES][NUM_ANALOG_INPUTS_PER_ZONE];

////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(){ 
  Ethernet.begin(mac);
  mqtt.setServer(broker, port);
  mqtt.setCallback(receiveMqttMessage);
  // Init variables
  for (int i = 0; i < NUM_ZONES; ++i) {
    for (int j = 0; j < NUM_DIGITAL_INPUTS_PER_ZONE; ++j) {
      digitalInputsValues[i][j] = digitalRead(digitalInputs[i][j]);
    }
    for (int j = 0; j < NUM_ANALOG_INPUTS_PER_ZONE; ++j) {
      analogInputsValues[i][j] = analogRead(analogInputs[i][j]);
    }
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void loop() {
  if (!mqtt.connected()) {
    reconnect();
  } else {
    mqtt.loop();
  }
  updateInputs();
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void updateInputs() {
  for (int i = 0; i < NUM_ZONES; ++i) {
    for (int j = 0; j < NUM_DIGITAL_INPUTS_PER_ZONE; ++j) {
      updateDigitalInput(i, j);
    }
    for (int j = 0; j < NUM_ANALOG_INPUTS_PER_ZONE; ++j) {
      updateAnalogInput(i, j);
    }
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void updateDigitalInput(int zone, int index) {
  int value = digitalRead(digitalInputs[zone][index]);
  if (value != digitalInputsValues[zone][index]) {
    digitalInputsValues[zone][index] = value;
    publishInput(zone, index + DIGITAL_INPUTS_OFFSET, value);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void updateAnalogInput(int zone, int index) {
  int value = analogRead(analogInputs[zone][index]);
  int minValue = value > ANALOG_INPUTS_THRESHOLD ? value - ANALOG_INPUTS_THRESHOLD : 0;
  int maxValue = value < 1023 - ANALOG_INPUTS_THRESHOLD ? value + ANALOG_INPUTS_THRESHOLD : 1023;
  if ((analogInputsValues[zone][index] < minValue) || (analogInputsValues[zone][index] > maxValue)) {
    analogInputsValues[zone][index] = value;
    publishInput(zone, index + ANALOG_INPUTS_OFFSET, value);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void setDigitalOutput(char *payload, unsigned int len) {
  DynamicJsonBuffer json(JSON_OBJECT_SIZE(3));
  JsonObject &root = json.parseObject(payload, len);
  if (root.success()) {
    int zone = root["zone"];
    if (zone >= NUM_ZONES) {
      // Invalid zone
      return;
    }
    int index = root["index"];
    index -= DIGITAL_OUTPUTS_OFFSET;
    if (index >= NUM_DIGITAL_OUTPUTS_PER_ZONE) {
      // Invalid digital output
      return;
    }
    int value = root["value"];
    digitalWrite(digitalOutputs[zone][index], value);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void setAnalogOutput(char *payload, unsigned int len) {
  DynamicJsonBuffer json(JSON_OBJECT_SIZE(3));
  JsonObject &root = json.parseObject(payload, len);
  if (root.success()) {
    int zone = root["zone"];
    if (zone >= NUM_ZONES) {
      // Invalid zone
      return;
    }
    int index = root["index"];
    index -= ANALOG_OUTPUTS_OFFSET; 
    if (index >= NUM_ANALOG_OUTPUTS_PER_ZONE) {
      // Invalid analog output
      return;
    }
    int value = root["value"];
    analogWrite(analogOutputs[zone][index], value);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void reconnect() {
  if (mqtt.connect(MQTT_ID)) {
    mqtt.subscribe("Q");
    mqtt.subscribe("A");
  } else {
    // MQTT connect fail
    client.stop();
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void receiveMqttMessage(char* topic, byte* payload, unsigned int len) {
  if (strcmp(topic, "Q") == 0) {
    // Set digital output
    setDigitalOutput((char*) payload, len);
  } else if (strcmp(topic, "A") == 0) {
    // Set analog output
    setAnalogOutput((char*) payload, len);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void publishInput(int zone, int index, int value) {
  DynamicJsonBuffer json(JSON_OBJECT_SIZE(3));
  JsonObject &root = json.createObject();
  if (root.success()) {
    root["zone"] = zone;
    root["index"] = index;
    root["value"] = value;
    publish("I", root);
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void publish(const char *topic, JsonObject &root) {
  unsigned len = root.measureLength();
  if (len > 0) {
    char *payload = new char[len + 1];
    if (payload) {
      root.printTo(payload, len + 1);
      publish(topic, payload);
      delete[] payload;
    }
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void publish(const char *topic, const char *payload) {
  if (mqtt.connected()) {
    mqtt.publish(topic, payload);
  }
}

Buscar en nuestro blog

Cliente MQTT para PLC basado en Arduino como módulo de E/S
Bernat Garcia 25 de enero de 2019
Compartir

¿Estás buscando tu Controlador Lógico Programable ideal?

Echa un vistazo a esta comparativa de producto de varios controladores industriales basados en Arduino.

Comparamos entradas, salidas, comunicaciones y otras especificaciones con las de los equipos de otras marcas destacadas.


Industrial PLC comparison >>>