Back-End

28 jun, 2016

Lendo devices Modbus com Python de uma aplicação PHP/Silex via worker Gearman

Publicidade

Sim. Eu sei. Nunca sei como escrever um bom título para os meus artigos. Deixe-me mostrar um exemplo de integração com o qual tenho trabalhado esses dias. Vamos começar.

Em automação industrial, há vários protocolos padrão. Modbus é um deles. Talvez não seja o mais legal ou o mais recente (como OPC ou OPC/UA), mas podemos falar Modbus com um grande número de dispositivos.

Eu preciso ler de um deles, e mostrar algumas variáveis em um frontend web. Imagine o seguinte servidor fake Modbus (que emula o dispositivo Modbus real).

#!/usr/bin/env python
 
##
# Fake modbus server
# - exposes "Energy" 66706 = [1, 1170]
# - exposes "Power" 132242 = [2, 1170]
##
 
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.server.async import StartTcpServer
import logging
 
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)
 
hrData = [1, 1170, 2, 1170]
store = ModbusSlaveContext(hr=ModbusSequentialDataBlock(2, hrData))
 
context = ModbusServerContext(slaves=store, single=True)
 
StartTcpServer(context)

Esse servidor expõe duas variáveis “Energy” and “Power”. Trata-se de um servidor falso, e ele vai sempre retornar 66706 para a energy e 132.242 para power. O Mobus é um protocolo binário, então 66706 = [1, 1170] e 132.242 = [2, 1170].

Sou capaz de ler Modbus a partir do PHP, mas normalmente uso Python para esse tipo de lógica. Eu não vou reescrever uma lógica existente para o PHP. Eu não sou louco o suficiente. Além disso, o meu dispositivo Modbus verdadeiro aceita apenas um socket activo para obter informações. Isso significa que se de dois clientes utilizam o frontend ao mesmo tempo, ele irá travar. Nessas situações, filas são nossas amigas.

Vou usar um worker Gearman (escrito em Python) para ler informações Modbus.

from pyModbusTCP.client import ModbusClient
from gearman import GearmanWorker
import json
 
def reader(worker, job):
    c = ModbusClient(host="localhost", port=502)
 
    if not c.is_open() and not c.open():
        print("unable to connect to host")
 
    if c.is_open():
 
        holdingRegisters = c.read_holding_registers(1, 4)
 
        # Imagine we've "energy" value in position 1 with two words
        energy = (holdingRegisters[0] << 16) | holdingRegisters[1]
 
        # Imagine we've "power" value in position 3 with two words
        power = (holdingRegisters[2] << 16) | holdingRegisters[3]
 
        out = {"energy": energy, "power": power}
        return json.dumps(out)
    return None
 
worker = GearmanWorker(['127.0.0.1'])
 
worker.register_task('modbusReader', reader)
 
print 'working...'
worker.work()

Nosso backend está pronto. Agora, vamos trabalhar com o frontend. Nesse exemplo, vou usar PHP e Silex.

<?php
include __DIR__ . '/../vendor/autoload.php';
use Silex\Application;
$app = new Application(['debug' => true]);
$app->register(new Silex\Provider\TwigServiceProvider(), array(
    'twig.path' => __DIR__.'/../views',
));
$app['modbusReader'] = $app->protect(function() {
    $client = new \GearmanClient();
    $client->addServer();
    $handle = $client->doNormal('modbusReader', 'modbusReader');
    $returnCode = $client->returnCode();
    if ($returnCode != \GEARMAN_SUCCESS) {
        throw new \Exception($this->client->error(), $returnCode);
    } else {
        return json_decode($handle, true);
    }
});
$app->get("/", function(Application $app) {
    return $app['twig']->render('home.twig', $app['modbusReader']());
});
$app->run();

Como podemos ver, o frontend é um cliente Gearman simples. Ele usa o nosso worker Python para ler informações do Modbus e renderizar um html simples com um template Twig.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo</title>
</head>
<body>
    Energy: {{ energy }}
    Power: {{ power }}
</body>
</html>

E isso é tudo. Você pode ver o exemplo completo na minha conta do GitHub.

***

Gonzalo Ayuso faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://gonzalo123.com/2016/04/18/reading-modbus-devices-with-python-from-a-phpsilex-application-via-gearman-worker/