Integración de Cliente OPC con Python

Para finalizar la serie del control de temperatura, vamos a ver como podemos usar un cliente OPC generado en nuestra aplicación Little SCADA, para controlar y monitorizar la temperatura que fuimos capaces de simular primero y ajustar mediante un PID después.

Para ello, necesitaremos también publicar las variables del PLC que queremos ver y controlar, mediante un servidor OPC. Los pasos a seguir serán por tanto:
1- Generación de servidor OPC en PLC Siemens y publicación de variables
2- Creación de cliente OPC mediante Python y Django
3- Visualización y control desde una vista en Little SCADA.

Paso 1: Generación de servidor OPC en PLC Siemens y publicación de variables

Partiendo del proyecto de TIA PORTAL, donde estuvimos ajustando el PID y generamos la temperatura simulada, vamos ahora a publicar en un servidor OPC UA en el propio PLC, para poder tanto monitorizar las variables de temperatura del tanque y apertura de la válvula, como para poder darle distintos Set Points a nuestro tanque simulado.

  • Añadimos una nueva interfaz de OPC UA:
  • Arrastramos las variables que queremos publicar en el servidor OPC:
  • Activamos el servidor OPC en la configuración del PLC, así como la licencia (El servidor OPC UA de Siemens necesita de una licencia específica para este servicio).

Descargamos el PLC y probamos con un Cliente como pro ejemplo UA Expert para comprobar que vemos las variables. En este caso voy a verlas desde otra máquina, donde tengo la aplicación Little SCADA, así aseguro conectividad para mi cliente OPC.

Para este caso concreto, no usamos certificados. Elegimos “none” y procedemos a visualizar las variables.

Perfecto! ya tenemos a nuestro PLC publicando y hemos asegurado la conexión desde un cliente OPC, por tanto ya podemos pasar a “verlo” desde otros clientes OPC.

Paso 2: Creación de cliente OPC mediante Python y Django

Ahora queremos poder crear un cliente OPC, pero para integrarlo en una aplicación de Python, en mi caso en el Little SCADA, es decir en una aplicación WEB que controlará el proceso.

Para no alargar el post, me centraré en el cliente OPC per se, obviando detalles de Django. Básicamente se compone de dos funciones, una que obtiene los datos y otra que escribe el SP de temperatura.

El control se realiza mediante Python para interactuar con el sistema, y Java para el procesamiento de datos y funciones.

  • Obtener datos OPC: os muestro el código, que me proporcionó chatGPT, no sin tener que hacer ciertos ajustes; Sobre todo el como llamar a los nodos, es muy importante. Fíjate en UA Expert para saber mas: (“ns=3;s=\”SIM_Tem\”.\”T_tanque\””)

from django.http import JsonResponse
from opcua import Client, ua
from opcua.ua import DataValue, Variant

def obtener_datos_opc(request):
    try:
        # Conectar al servidor OPC-UA
        client = Client("opc.tcp://192.168.1.43:4840/")
        client.connect()

        # Obtener la temperatura del servidor OPC-UA
        temperatura_node = client.get_node("ns=3;s=\"SIM_Tem\".\"T_tanque\"")
        temperatura = temperatura_node.get_value()

        # Obtener el valor de la válvula del servidor OPC-UA
        valvula_node = client.get_node("ns=3;s=\"SIM_Tem\".\"Valvula100\"")
        valvula = valvula_node.get_value()

        # Desconectar del servidor OPC-UA
        client.disconnect()

        # Devolver los datos como JSON
        return JsonResponse({'temperatura': round(temperatura, 1), 'valvula': round(valvula, 1)})

    except Exception as e:
        # Manejar cualquier error que pueda ocurrir
        error_message = str(e)
        return JsonResponse({'error_message': error_message}, status=500)

Este código es llamado mediante Ajax en el archivo de java correspondiente a esta función:



function obtenerDatos() {
        $.ajax({
            url: '/obtener_datos_opc/',
            type: 'GET',
            success: function(response) {
                $("#current-temperature").text(response.temperatura);
                $("#valve-opening").text(response.valvula);
    
                var now = new Date();
                var timeLabel = now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds();
    
                temperaturaData.push(response.temperatura);
                valvulaData.push(response.valvula);
                labels.push(timeLabel);
    
                // Actualizar los máximos y mínimos del eje Y
                updateYAxis(temperaturaData, valvulaData);
    
                chart.update();
    
                if (labels.length > 120) {
                    temperaturaData.shift();
                    valvulaData.shift();
                    labels.shift();
                }
            },
            error: function(xhr, status, error) {
                console.error('Error al obtener los datos:', error);
            }
        });
    }

  • Escribir SP: en este código le pasamos una temperatura objetivo a nuestra variable T_SP.

A continuación la función en Python para realizar el envío:



from opcua import Client, ua
from opcua.ua import DataValue, Variant
from django.http import JsonResponse

def enviar_sp(request):
    temperatura_sp = request.POST.get('temperatura_sp')
    if temperatura_sp is None:
        return JsonResponse({'error': 'Temperatura setpoint no proporcionada'}, status=400)

    # Intentar convertir la temperatura a float
    try:
        new_value = float(temperatura_sp)
    except ValueError:
        return JsonResponse({'error': 'Valor inválido para temperatura setpoint'}, status=400)

    client = Client("opc.tcp://192.168.1.43:4840/")
    try:
        client.connect()
        print("Conexión establecida con éxito.")

        variable_node = client.get_node("ns=3;s=\"SIM_Tem\".\"T_SP\"")
        print(f"Valor actual del nodo: {variable_node.get_value()}")

        # Crear un DataValue y asignar el valor de temperatura
        data_value = DataValue(Variant(new_value, ua.VariantType.Float))

        # Establecer el nuevo valor del nodo
        variable_node.set_value(data_value)
        print(f"Valor actualizado del nodo: {variable_node.get_value()}")

        return JsonResponse({'message': 'Temperatura setpoint enviada correctamente'})

    except Exception as e:
        print(f"Error al escribir en el nodo: {str(e)}")
        return JsonResponse({'error': str(e)}, status=500)

    finally:
        if client:
            client.disconnect()

Y este código es llamado por una función de Java al pulsar el botón “enviar”:



    $("#setpoint-button").click(function() {
        var setpointValue = $("#setpoint-input").val();
        console.log('Valor del setpoint:', setpointValue);
        var csrftoken = getCookie('csrftoken');

        $.ajax({
            url: '/enviar_sp/',
            type: 'POST',
            data: {
                'temperatura_sp': setpointValue
            },
            headers: {
                'X-CSRFToken': csrftoken
            },
            success: function(response) {
                console.log('Valor de temperatura SP enviado exitosamente:', response);
            },
            error: function(xhr, status, error) {
                console.error('Error al enviar el valor de temperatura SP:', error);
            }
        });
    });

Entiendo que puede ser complejo la utilización de Django por su estructura. Sin embargo, generar un cliente de OPC es muy sencillo y se puede aislar del uso de Django, para probar su potencia. Django en este caso nos facilita la visualización de los datos en formato gráfica así como la estructura web en general.

Esto lo expliqué en el artículo de Little SCADA con un ejemplo de cliente OPC sencillo.

Paso 3: Visualización y control desde una vista en Little SCADA.

Hecho todo lo anterior, ya se puede usar dicha información para mostrarla en nuestra WEB. Para ello han sido necesarios códigos en HTML (Templates), Java, CSS, y Python.

Aquí os muestro como queda el template del control de temperatura, que a su vez usa una base de la propia web. Os aconsejo mirar tutorial de Django porque tiene mucha potencia, y aunque al principio resulta complejo, reduce mucho los tiempos de programación.

HTML

{% extends 'base.html' %}
{% load static %}

{% block title %}Control de Temperatura{% endblock %}

{% block content %}
<div class="content-wrapper">
    <div class="quadrant_controlTem">
        <div class="chart-container">
            <canvas id="temperatura-chart"></canvas>
        </div>
        <div class="control-panel">
            <h2>Control de Temperatura</h2>
            <div class="input-group">
                <label for="setpoint-input">Temperatura SP:</label>
                <input type="number" id="setpoint-input" step="0.1" placeholder="Ingrese temperatura SP">
                <button id="setpoint-button">Enviar</button>
            </div>
            <div class="temperature-info">
                <p>Apertura de la válvula: <span id="valve-opening">-</span>%</p>
                <p>Temperatura Actual: <span id="current-temperature">-</span>°C</p>
            </div>
        </div>

      </div>

    </div>


</div>

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="{% static 'js/controlTem.js' %}"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> <!-- Font Awesome for icons -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
{% endblock %}

RESULTADOS:

Visualización de datos

Consigna de temperatura