Lección 1. PLC M-Duino como módulo de E/S MQTT
#1 Cliente MQTT para PLC basado en Arduino como módulo de E/S
En el capítulo #1 se explica cómo configurar tu PLC industrial basado en Arduino como un módulo de E/S MQTT.
Antes de empezar este capítulo sería interesante echar un vistazo a cómo funciona el protocolo MQTT, qué es un JSON y sus aplicaciones.
Después, echa un vistazo rápido a las siguientes librerías:
Nota: Para que funcione correctamente con nuestros dispositivos, instala la versión 5.13.5 de la Librería Arduino JSON. Una versión más nueva podría presentar algunos problemas.
La arquitectura del código está hecha para un M-Duino21+, pero está diseñada para ser extendida para otros M-Duino's. Hay cuatro matrices diferentes que incluyen todas las E/S disponibles en M-Duino 21+. Estas matrices son:
- digitalOutputs
- analogOutputs
- digitalInputs
- analogInputs
Así que, 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 está subdividida con su nomenclatura de pines relacionados.
Después de haber explicado cómo acceder al mapeo de entradas, estamos listos para explicar el resto del código.
En la función setup() sólo estamos haciendo la inicialización de la Ethernet y del cliente MQTT llamado MQTT, relacionando la función callback 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 una conexión sólo estamos usando la función mqtt.loop(), que mantendrá la conectividad con nuestro servidor. Además de estas dos funciones, tenemos la función updateInputs() que actualizará el valor real de las entradas.
Profundicemos en las funciones para tener una mejor comprensión de lo que hacen.
Gestión de salidas
En primer lugar, tenemos la función reconnect(), que se suscribe a los topics "Q" y "A". Un topic está dedicado a las salidas analógicas y el otro a las salidas digitales. Si no está conectado al servidor, se detiene 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 las salidas, tenemos que ver la función callback. En este programa, la función callback es receiveMqttMessage(). La función receiveMqttMessage() se ejecuta cada vez que recibimos un mensaje. Así, cuando llega un mensaje, la función callback decide si el topic es para los pines analógicos o digitales y llama a la función correspondiente: la función setDigitalOutput() o la función setAnalogOutput().
setDigitalOutput() extrae y analiza el archivo JSON, confirma que la zona y el pin existen y luego ejecuta la función digitalWrite() para actualizar la salida deseada.
La función setAnalogOutput() funciona exactamente igual que setDigitalOutput().
ANALOG_OUTPUTS_OFFSET es usado para compensar el valor de los pines analógicos. Los pines analógicos se colocan en las posiciones 5, 6 ,7. Por eso el ANALOG_OUTPUTS_OFFSET es 5.
Gestión de entradas
En el bucle principal, tenemos la función updateInputs(), que actualiza todos los valores de las entradas. Esta función compara todos los valores anteriores con los actuales y, si el valor ha cambiado, el programa publica el nuevo valor por MQTT. En los valores digitales se compara si hay un 0 o un 1. La función que hace este trabajo es updateDigitalInput().
En el lado 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, entonces publicará el nuevo valor. La función que hace este trabajo es updateAnalogInput().
Nota: Asegúrate de que la Librería ArduinoJson está en la versión 5. Ve a Sketch > Include Library > Manage Libraries... > Type: ArduinoJson y asegúrate de que la versión 5 está instalada.
Código de ejemplo
/*
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/>.
*/
// incluyendo la biblioteca Ethernet
#include <Ethernet.h>
// Bibliotecas necesarias para la comunicación MQTT
#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, 10, 10, 60);
/* Establecer una dirección IP para el broker MQTT. Recuerda establecer la misma dirección IP estática en el dispositivo configurado como broker. */
IPAddress ip(10, 10, 10, 70); //ESTABLECE AQUÍ LA DIRECCIÓN IP DE TU M-DUINO
unsigned port = 1883;
// Inicializar cliente
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, ip);
mqtt.setServer(broker, port);
mqtt.setCallback(receiveMqttMessage);
// Inicializar 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) {
// Zona inválida
return;
}
int index = root["index"];
index -= DIGITAL_OUTPUTS_OFFSET;
if (index >= NUM_DIGITAL_OUTPUTS_PER_ZONE) {
// Salida digital inválida
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) {
// Zona inválida
return;
}
int index = root["index"];
index -= ANALOG_OUTPUTS_OFFSET;
if (index >= NUM_ANALOG_OUTPUTS_PER_ZONE) {
// Salida analógica inválida
return;
}
int value = root["value"];
analogWrite(analogOutputs[zone][index], value);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void reconnect() {
if (mqtt.connect(MQTT_ID)) {
mqtt.subscribe("Q");
mqtt.subscribe("A");
} else {
// Fallo al conectar MQTT
client.stop();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void receiveMqttMessage(char* topic, byte* payload, unsigned int len) {
if (strcmp(topic, "Q") == 0) {
// Establecer salida digital
setDigitalOutput((char*) payload, len);
} else if (strcmp(topic, "A") == 0) {
// Establecer salida analógica
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);
}
}
No se permite comentar en este curso.