Gateway: ESP8266 Modbus RTU MQTT + HMI Industrial Panasonic
Dado el crecimiento de aplicaciones IoT utilizando el protocolo MQTT como base y la plataforma Node-RED, hemos creado una posibilidad para dispositivos industriales o dispositivos que utilicen protocolo Modbus RTU (RS232 o RS485) para interactuar con las aplicaciones o plataformas IoT que utilicen MQTT en este caso Node-RED.
En la industra existen gran cantidad de Dispositivos de Monitoreo y control que cuentan con Modbus RTU tales como Contadores, Medidores (Flujo, consumo electrico, Temperatura, Humedad, etc), Controladores Industriales PLC, PAC, Pantallas HMI, Variadores de velocidad entre otros, para los cuales la unica posibilidad de extraer y concentrar datos atravez de servidores OPC.
Hemos configurado un Modulo ESP8266 12E NodeMCU como Maestro Modbus RTU serial via RS232 (CHIP MAX232) para solicitar y enviar datos a una Pantalla Industrial GT01 Marca panasonic, la referencia que tengo se alimenta a 5V, El mismo modulo ESP esta configurado como cliente MQTT, la rutina creada en Arduino IDE realiza un puente entre registros de lectura y escritura Modbus en este caso «Holding Registers» y por la parte MQTT topicos de suscripcion y publicacion.
Dado que el modulo ESP8266 cuenta con un puerto serial, hemos utilizado la libreria softwareserial para crear un segundo puerto que se utilizara solo para Modbus RTU permitiendo la programacion y depuracion sin interrumpir fisicamente la comunicacion modbus, adicionamos un integrado MAX232 para la conversion del protocolo a RS232.
Referencias Recomendadas
En este caso especifico la prueba que realizaremos tendrá como base utilizar un dispositivo industrial, conectaremos una Pantalla HMI GT01 marca Panasonic configurada como esclavo Modbus RTU (RS232) con software GTWIN, por que una pantalla? seria muy aburrido y simple utilizar un simulador y queremos implementar hardware real.
Referencias Recomendadas
Pruebas
Hemos realizado 2 pruebas una para presentación y otra para explicación :
1 – Prueba Rapida HMI Panasonic y Node-RED Dashboard
- Utilizamos solo 2 registros modbus 1 para lectura y otro para escritura.
- Realizamos visualización y control con dashboard.
2- Explicación Completa y prueba 10 Lectura 10 escritura
- Utilizaremos 10 registros de lectura y 10 de escritura entre Node-RED y la HMI.
- Explicaremos los métodos de envió y recepción en Node-red.
Prueba Rapida HMI Panasonic y Node-RED Dashboard
En este ejemplo la HMI esta configurada como esclavo modbus rtu solo utilizaremos 2 registros disponibles:
- Holding Register[0] – Leer
- Holding Register[10] – Escribir
En Node-RED se ha creado un dashboard y se han configurado previamente 2 nodos MQTT asignados a Topicos de envio y recepción ajustado al codigo en ESP8266.
El valor ingresado en la pantalla sera enviado a un Topico MQTT y el valor enviado desde Node-RED por un tópico llegara a un registro de la pantalla.
Descargas: codigo arduino en la parte inferior
Node-RED
Recepcion de Registros Holding Register desde MQTT
Se recibe el Holding Register[0] en un nodo de suscripcion MQTT y el valor de manera individual y se envia a 2 Widgets Nodo Text y Nodo Gauge para visualizacion en Dashboard.
Nota: Explicación completa mas mas abajo «Recepcion Node-RED desde HMI Panasonic«
Envio de Registros via MQTT hacia Holding Registers
Agregamos un widget Nodo slider configurado para envia un valor de 0 a 1000 el cual conecta con el nodo de publicacion MQTT para enviar el valor en el Holding register [10] y conecta con el nodo Gauge para visualizar el cambio desde el dashboard.
Nota: Explicación completa mas mas abajo «Envio desde Node-RED Hacia HMI Panasonic»
Configuracion Dashboard
Orden de widgets para visualizacion y control.
Node-RED Dashboard
Explicación Completa
El siguiente vídeo realizara la explicación completa de la conexión utilizando 20 registros:
Diseño HMI GT01 Panasonic en GTWIN
La pantalla esta configurada como esclava Modbus RTU conexion RS232, Previamente en la Pantalla GT01 se han diseñado 2 pantallas el software de Configuracion GTWIN :
- Lectura de 3 Holding Registers [0][1][2] desde 3 registros MQTT.
- Escritura de 3 Holding Registers [10][11][12] hacia 3 registros MQTT.
Node-RED
En la prueba anterior solo utilizamos 2 registros en este nuevo caso utilizaremos 10 registros de lectura y 10 registros de escritura
Configuración de Cliente MQTT en Node-RED
En este caso previamente hemos instalado el Broker MQTT «Mosquitto» instalado en mi servidor (Lubuntu) Linux, a ese broker MQTT conectan Node-RED y el modulo ESP8266.
Esta es la configuración de la conexion MQTT de Node-red para conectar con mosquito y por ende con el ESP8266.
Envio desde Node-RED Hacia HMI Panasonic
Se ha utilizado un Nodo inject «Send 10 Registers» el cual contiene un 10 valores separados por comas«,» ejemplo:«1,2,3,4,5,6,7,8,9,10,» Nota: tener en cuenta que modbus maneja enteros de 16 bits para no enviar valores de mayor tamaño.
Se conecta al nodo de publicación MQTT«MQTT_Holding_Array- Holding Registers [0] – Holdin Registers [9]» y se enviaran 10 valores a los 10 primeros holding registers en la pantalla.
Recepcion Node-RED desde HMI Panasonic
Hemos creado 2 metodos de recepción de datos desde el Modulo ESP8266:
Recepcion grupal 10 registros un solo topico MQTT
En este caso para la recepción se utiliza un Nodo de suscripción MQTT «Holding_to_MQTT_Array – Holding Register [10] … Holding Register [19]» y el topico Holding_to_MQTT_Array y recibira todos los 10 registros de la siguiente forma 1,2,3,4,5,6,7,8,9,10.
Recepcion individual 1 registro 1 topico MQTT
Dado que en ciertos casos se requiere que un valor se actualice mas rapido que otros, se ha creado manera que el ESP8266 envie en 1 solo topico el valor de 1 solo holding register en especifico, cada nodo de suscripcion MQTT solicitara de manera individual, ejemplo Nodo «Holding Register [10]» y el topico «»Holding_to_MQTT_1.
Arduino IDE
El codigo integra 2 tutoriales realizados anteriormente, ESP8266 como cliente MQTT y ESP8266 como Maestro Modbus RTU Via RS232.
- El primer caso requiere la libreria < PubSubClient > la que permite ser cliente MQTT.
- El segundo caso requiere la libreria < ModbusMaster232. > para modbus y la libreria < SoftwareSerial.h < para emular el puerto serial para modbus.
Nota Importante: Para conprender correctamente el siguiente código recomendamos primero ver los siguientes tutoriales:
Este ejemplo envia Lee 10 registros y escribe en 10 registros Modbus RTU,
NOTA:En la parte interior Link de descargas de codigo Arduino IDE.
Recepcion MQTT y envio a Esclavo Modbus RTU
la recepción de topicos MQTT desde Node-red es realizado por la funcion callback , en resumen:
- Esp8266 se suscribe al topico » MQTT_Holding_Array».
- Node-RED publica en el topico «Holding_to_MQTT_Array» y envia 1,2,3,4,5,6,7,8,9,10, en un String.
- funcion callback recibe String y un bucle for divide los valores separados por comas»,» y almacena cada valor en el array de Strings MQTT_to_Holding[ ].
- el valor en el array se convierte a int y se envia al esclavo modbus de la siguiente manera ejemplo node.writeSingleRegister(0, MQTT_to_Holding[0].toInt()); para el Holding Register[0] .
Fragmento de codigo funcion callback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/* funtion callback * * Esta funcion realiza la recepcion de los topic suscritos * This function performs the reception of subscribed topics * * aqui se realiza en almacenamiento de datos en Array String MQTT_to_Holding * Here is done in data storage in Array String MQTT_to_Holding */ void callback(char* topic, byte* payload, unsigned int length) { for (int i = 0; i < 20; i++) { MQTT_to_Holding[i]=""; } //limpia cadenas recibidas // Cleans received strings String string; Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); int cantidad=0 ; // cantidad de registros // number of records for (int i = 0; < length; i++) { string+=((char)payload[i]); if (topic ="MQTT_Holding_Array") { // Check the topic /// Verifica el topico if ( payload[i] == 44 ) // ","= 44 ascii // detecta separador "," // Detect tab "," { cantidad++; } else { //Serial.println(cantidad); MQTT_to_Holding[cantidad]+=((char)payload[i]); /// Serial.println((char)payload[i]); } } } Serial.println(string); // Imprime mensajes MQTT Recibidos // Prints MQTT messages received } |
Lectura de Registros Modbus y envio MQTT
Se realiza una unica lectura de registros pero existen 2 metodos de envio MQTT (individual y grupal) mencionados anteriormente en «Recepcion Node-RED desde HMI Panasonic».
Se debe tener en cuenta que para la lectura Modbus se utilizan unos delay para lectura de los registros delay(tdelay); de 5 ms este valor variara dependiendo de la cantidad de registros a solicitar.
Combinamos 2 pasos:
- Lectura de registros Modbus (Holding Registers).
- Envio via topico MQTT grupal ej: «Holding_to_MQTT_Array» y envio via topicos MQTT individuales ej: «Holding_to_MQTT_1».
Fragmento de código Lectura de Holding Register [10] y envio MQTT topico «Holding_to_MQTT_1»
1 2 3 4 5 6 |
node.readHoldingRegisters(10, 1); // Holding Registers [10] Holding_to_MQTT[0]= String(node.getResponseBuffer(0)); String (node.getResponseBuffer(0)).toCharArray(buf, 10); /// String a char para mensaje MQTT /// String to char for MQTT message client_MQTT.publish("Holding_to_MQTT_1",buf); node.clearResponseBuffer(); /// Limpiar buffer modbus RTU /// Clean modbus RTU buffer delay(tdelay); |
Código Completo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
/* * Modified by Trialcommand * More Tutorials: * Website http://trialcommand.com * In English: http://en.trialcommand.com * En Español: http://es.trialcommand.com * */ #include < ESP8266WiFi.h > #include < PubSubClient.h > #include < ModbusMaster232.h > #include < SoftwareSerial.h > // Modbus RTU pins D7(13),D8(15) RX,TX WiFiClient espClient; PubSubClient client_MQTT (espClient); const char* ssid = "1503523"; const char* password = "D2E7D32DBC883"; const char* mqtt_server = "192.168.0.19"; /// example 192.168.0.19 int mqtt_port = 1883; String MQTT_to_Holding[20]; // Array recepcion valores MQTT // Array reception MQTT values // Instantiate ModbusMaster object as slave ID 1 ModbusMaster232 node(1); void setup() { Serial.begin(9600); delay(100); node.begin(9600); delay(100); WiFi.begin(ssid, password); client_MQTT.setServer(mqtt_server, mqtt_port); client_MQTT.setCallback(callback); delay(100); Serial.println("."); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("Connected "); Serial.print("MQTT Server "); Serial.print(mqtt_server); Serial.print(":"); Serial.println(String(mqtt_port)); Serial.print("ESP8266 IP "); Serial.println(WiFi.localIP()); Serial.println("Modbus RTU Master Online"); } /* funtion callback * * Esta funcion realiza la recepcion de los topic suscritos * This function performs the reception of subscribed topics * * aqui se realiza en almacenamiento de datos en Array String MQTT_to_Holding * Here is done in data storage in Array String MQTT_to_Holding */ void callback(char* topic, byte* payload, unsigned int length) { for (int i = 0; i < 20; i++) { MQTT_to_Holding[i]=""; } //limpia cadenas recibidas // Cleans received strings String string; Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); int cantidad=0 ; // cantidad de registros // number of records for (int i = 0; < length; i++) { string+=((char)payload[i]); if (topic ="MQTT_Holding_Array") { // Check the topic /// Verifica el topico if ( payload[i] == 44 ) // ","= 44 ascii // detecta separador "," // Detect tab "," { cantidad++; } else { //Serial.println(cantidad); MQTT_to_Holding[cantidad]+=((char)payload[i]); /// Serial.println((char)payload[i]); } } } Serial.println(string); // Imprime mensajes MQTT Recibidos // Prints MQTT messages received } /* * * Funcion que realiza la reconexion de cliente MQTT * Function that performs MQTT client reconnection * * enable/habilita client_MQTT.subscribe("MQTT_Holding_Array"); */ void reconnect() { // Loop until we're reconnected // Bucle hasta que se vuelva a conectar while (!client_MQTT.connected()) { if (client_MQTT.connect("ESP8266Client")) { // Serial.println("MQTT Online"); //// Topico para recibir Holding Registers desde MQTT // Topic to receive Holding Registers from MQTT --- return data in callback funtion client_MQTT.subscribe("MQTT_Holding_Array"); } else { Serial.print("failed, rc="); Serial.print(client_MQTT.state()); } } } void loop() { /* Direccionamiento MQTT Holding Registers [0][9] = 10 registros MQTT Addressing Holding Registers [0] [9] = 10 Registers Topic = "MQTT_Holding_Array" 1 topico MQTT client_MQTT.subscribe("MQTT_Holding_Array"); subscribe topic MQTT_Holding_Array previamente en la funcion callback se toma la cadena mqtt y se separan los registros se almacenan en array string a int y se envian en los primeros 10 registros modbus Previously in the function callback takes the string mqtt and separates the records are stored in array string to int and are sent in the first 10 registers modbus */ node.writeSingleRegister(0, MQTT_to_Holding[0].toInt() ); /// Send MQTT Array String to int to Modbus RTU Master node.writeSingleRegister(1, MQTT_to_Holding[1].toInt() ); node.writeSingleRegister(2, MQTT_to_Holding[2].toInt() ); node.writeSingleRegister(3, MQTT_to_Holding[3].toInt() ); node.writeSingleRegister(4, MQTT_to_Holding[4].toInt() ); node.writeSingleRegister(5, MQTT_to_Holding[5].toInt() ); node.writeSingleRegister(6, MQTT_to_Holding[6].toInt() ); node.writeSingleRegister(7, MQTT_to_Holding[7].toInt() ); node.writeSingleRegister(8, MQTT_to_Holding[8].toInt() ); node.writeSingleRegister(9, MQTT_to_Holding[9].toInt() ); /* Direccionamiento Holding Registers MQTT = 10 registros Addressing Holding Registers [10] [19] MQTT = 10 Registers - 10 topics MQTT Topic INDIVIDUAL = "Holding_to_MQTT_1"....... "Holding_to_MQTT_10" 10 topics MQTT publish message 10 cada uno de los 10 registro modbus se envia en un topico MQTT diferente de manera individual Each of the 10 modbus registers is sent in a different MQTT topic individually */ int tdelay = 5 ; /// delay char buf[10]; String Holding_to_MQTT[10]; node.readHoldingRegisters(10, 1); // Holding Registers [10] Holding_to_MQTT[0]= String(node.getResponseBuffer(0)); String (node.getResponseBuffer(0)).toCharArray(buf, 10); /// String a char para mensaje MQTT /// String to char for MQTT message client_MQTT.publish("Holding_to_MQTT_1",buf); node.clearResponseBuffer(); /// Limpiar buffer modbus RTU /// Clean modbus RTU buffer delay(tdelay); node.readHoldingRegisters(11, 1); // Holding Registers [11] Holding_to_MQTT[1]= String(node.getResponseBuffer(0)); String (node.getResponseBuffer(0)).toCharArray(buf, 10); client_MQTT.publish("Holding_to_MQTT_2",buf); node.clearResponseBuffer(); delay(tdelay); node.readHoldingRegisters(12, 1); // Holding Registers [12] Holding_to_MQTT[2]= String(node.getResponseBuffer(0)); String (node.getResponseBuffer(0)).toCharArray(buf, 10); client_MQTT.publish("Holding_to_MQTT_3",buf); node.clearResponseBuffer(); delay(tdelay); node.readHoldingRegisters(13, 1); // Holding Registers [13] Holding_to_MQTT[3]= String(node.getResponseBuffer(0)); String (node.getResponseBuffer(0)).toCharArray(buf, 10); client_MQTT.publish("Holding_to_MQTT_4",buf); node.clearResponseBuffer(); delay(tdelay); node.readHoldingRegisters(14, 1); // Holding Registers [14] Holding_to_MQTT[4]= String(node.getResponseBuffer(0)); String (node.getResponseBuffer(0)).toCharArray(buf, 10); client_MQTT.publish("Holding_to_MQTT_5",buf); node.clearResponseBuffer(); delay(tdelay); node.readHoldingRegisters(15, 1); // Holding Registers [15] Holding_to_MQTT[5]= String(node.getResponseBuffer(0)); String (node.getResponseBuffer(0)).toCharArray(buf, 10); client_MQTT.publish("Holding_to_MQTT_6",buf); node.clearResponseBuffer(); delay(tdelay); node.readHoldingRegisters(16, 1); // Holding Registers [16] Holding_to_MQTT[6]= String(node.getResponseBuffer(0)); String (node.getResponseBuffer(0)).toCharArray(buf, 10); client_MQTT.publish("Holding_to_MQTT_7",buf); node.clearResponseBuffer(); delay(tdelay); node.readHoldingRegisters(17, 1); // Holding Registers [17] Holding_to_MQTT[7]= String(node.getResponseBuffer(0)); String (node.getResponseBuffer(0)).toCharArray(buf, 10); client_MQTT.publish("Holding_to_MQTT_8",buf); node.clearResponseBuffer(); delay(tdelay); node.readHoldingRegisters(18, 1); // Holding Registers [18] Holding_to_MQTT[8]= String(node.getResponseBuffer(0)); String (node.getResponseBuffer(0)).toCharArray(buf, 10); client_MQTT.publish("Holding_to_MQTT_9",buf); node.clearResponseBuffer(); delay(tdelay); node.readHoldingRegisters(19, 1); // Holding Registers [19] Holding_to_MQTT[9]= String(node.getResponseBuffer(0)); String (node.getResponseBuffer(0)).toCharArray(buf, 10); client_MQTT.publish("Holding_to_MQTT_10",buf); node.clearResponseBuffer(); delay(tdelay); /* Direccionamiento Holding Registers MQTT = 10 registros Addressing Holding Registers [10] [19] MQTT = 10 Registers - 1 topic MQTT single Topic = "Holding_to_MQTT_Array" 1 topics MQTT publish message 1 Se concatena el valor de todos los 10 Registros Modbus y se envian en 1 solo topico The value of all 10 Modbus Registers is concatenated and sent in 1 single topic */ String Holding_to_MQTT_Array; Holding_to_MQTT_Array = Holding_to_MQTT[0]; Holding_to_MQTT_Array +=","; Holding_to_MQTT_Array +=Holding_to_MQTT[1]; Holding_to_MQTT_Array +=","; Holding_to_MQTT_Array +=Holding_to_MQTT[2]; Holding_to_MQTT_Array +=","; Holding_to_MQTT_Array +=Holding_to_MQTT[3]; Holding_to_MQTT_Array +=","; Holding_to_MQTT_Array +=Holding_to_MQTT[4]; Holding_to_MQTT_Array +=","; Holding_to_MQTT_Array +=Holding_to_MQTT[5]; Holding_to_MQTT_Array +=","; Holding_to_MQTT_Array +=Holding_to_MQTT[6]; Holding_to_MQTT_Array +=","; Holding_to_MQTT_Array +=Holding_to_MQTT[7]; Holding_to_MQTT_Array +=","; Holding_to_MQTT_Array +=Holding_to_MQTT[8]; Holding_to_MQTT_Array +=","; Holding_to_MQTT_Array +=Holding_to_MQTT[9]; // Serial.print("tamaño "); // Serial.println( Holding_to_MQTT_Array.length()); // Serial.println(Holding_to_MQTT_Array); char buf2[60]; Holding_to_MQTT_Array.toCharArray(buf2, 60); client_MQTT.publish("Holding_to_MQTT_Array",buf2); if (!client_MQTT.connected()) { reconnect(); /// reconection MQTT } client_MQTT.loop(); } |
Conexiones
ESP8266 12E NodeMCU Lolin (Conexion a MAX232)
Recomendaciones para implementacion
- Utilizar velocidad de Comunicación Serial 9600 bauds.
- Dependiendo de la cantidad de registros modbus leidos se debe modificar la variable tdelay, la cual dada un tiempo para la lectura y borrado del buffer modbus, si se presentan fallos de lectura modbus, no superar 10 registros solicitados, aun estamos optimizando las lecturas modbus y tener en cuenta que se utiliza softwareserial emula un puerto serial.
- Existe otra posibilidad de fallo en este caso estamos realizando un envio constante de registros tanto mqtt como modbus, seria recomendable solo enviar o recibir registros MQTT de requerirse, no en todo momento como en este caso.
Conclusiones
En la industrial hemos visto dispositivos tales como ejemplo medidores de consumo electrico Modbus RTU , y agregando un ESP8266 y un convertidor TTL a (RS232 RS485 o RS485 ) podriamos monitorear nuestros valor de consumo electrico sin requerir un OPC u Hardware especifico, como mencionamos anteriormente el protocolo MQTT ha sido implementado en gran variedad de aplicaciones IoT y en la actualidad existen una gran variedad de plataformas tanto locales como en la Nube que podrian albergar nuestros datos.
Para tener en cuenta, si se quieren implementar las pruebas realizadas descritas arriba en un proyecto real, detallamos no hemos realizado pruebas 24/7 al hardware esp8266 , se debe tener en cuenta materiales (pcb, soldadura,cableado), protecciones electricas, aislamiento electrico, temperatura del ambiente, potencia señal Wifi, entre otras variables, pero consideramos que para aplicaciones de automatización basica teniendo en cuenta lo mencionado anteriormente funcionaria correctamente.
Referencias
- ESP8266 como cliente MQTT
- ESP8266 como Maestro Modbus RTU Via RS232.
- Introduccion a Node-RED y Mosquitto MQTT
Descargas
Codigo Arduino IDE
- Ejemplo Completo 10 Registros Gateway_ModbusRTUMaster_MQTTclient
- Ejemplo 2 Registros Gateway_ModbusRTUMaster_MQTTclient_lite
Node RED
- Backup Node-RED Completo 10 Registros Modbus_RTU_to_MQTT
- Backup Node-RED 2 Registros Modbus_RTU_to_MQTT Dashboard