Note: I'm migrating from gonzalo123.com to here. When I finish I'll swap the DNS to here. The "official" blog will be always gonzalo123.com

      Reading Modbus devices with Python from a PHP/Silex Application via Gearman worker

      Yes. I know. I never know how to write a good tittle to my posts. Let me show one integration example that I’ve been working with this days. Let’s start.

      In industrial automation there’re several standard protocols. Modbus is one of them. Maybe isn’t the coolest or the newest one (like OPC or OPC/UA), but we can speak Modbus with a huge number of devices.

      I need to read from one of them, and show a couple of variables in a Web frontend. Imagine the following fake Modbus server (it emulates my real Modbus device)

      #!/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)
      

      This server exposes two variables “Energy” and “Power”. This is a fake server and it will returns always 66706 for energy and 132242 for power. Mobus is a binary protocol so 66706 = [1, 1170] and 132242 = [2, 1170]

      I can read Modbus from PHP, but normally use Python for this kind of logic. I’m not going to re-write an existing logic to PHP. I’m not crazy enough. Furthermore my real Modbus device only accepts one active socket to retrieve information. That’s means if two clients uses the frontend at the same time, it will crash. In this situations Queues are our friends.

      I’ll use a Gearman worker (written in Python) to read Modbus information.

      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()
      

      Our backend is ready. Now we’ll work with the frontend. In this example I’ll use PHP and 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();
      

      As we can see the frontend is a simple Gearman client. It uses our Python worker to read information from Modbus and render a simple html with a Twig template

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

      And that’s all. You can see the full example in my github account

      comments powered by Disqus