SICK FlexiSoft, Modbus/TCP and python

Last week i tried to read and write data to and from a SICK FlexiSoft Safty controller which has a Modbus/TCP gateway.

Because i had a lot of trouble to get that up and running i decided to write a few lines about i managed to solve my problems.

I connected the safety controller to my computer using a normal 100Mbit switch. But at first i did the mistake to use port 9100 instead of port 502 which is the standard port for Modbus/TCP. I did that mistake because on the SICK FlexiSoft Designer configuration the port 9100 is shown but no word about port 502.

I decided not to use a ready to use Modbus lib like pyModbus because i wanted to learn something.

So here is my code:

#!/usr/bin/env python
#-*- coding: utf-8 -*-

#DEBUG = True
DEBUG = False

import socket
from struct import pack

class Modbus:

    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.socket = None

    def connect(self):
        if self.socket is None:
            try:
                self.socket = socket.create_connection((self.host,self.port))
            except socket.error as msg:
                print("Connection to {0}{1} failed:\n{2}".format(self.host, self.port, msg))

    def disconnect(self):
        if self.socket is not None:
            self.socket.close()
            self.socket = None

    def read_holding_registers(self, reg_addr, reg_cnt, tid=0, uid=0):
        _tid        = pack('>H', tid)
        _pid        = pack('>H', 0)
        _uid        = pack('>B', uid)
        _fcode      = pack('>B', 0x03)
        _reg_addr   = pack('>H', reg_addr-1)
        _reg_cnt    = pack('>H', reg_cnt)
        _length     = pack('>H', (len(_uid) + len(_fcode) + len(_reg_addr) + len(_reg_cnt)))
        msg = _tid + _pid + _length + _uid + _fcode + _reg_addr + _reg_cnt

        if DEBUG:
            print("sent:\n{0}".format([hex(b) for b in msg]))

        self.socket.send(msg)

        header = self.socket.recv(9)
        if DEBUG:
            print("recieved header:\n{0}".format([hex(b) for b in header]))
            print("receive {0} bytes of data".format(header[8]))

        _data = self.socket.recv(header[8])

        if DEBUG:
            print("recieved data:\n{0}".format([hex(b) for b in _data]))

        data = []
        # rotate big endian 16bit values
        for i in range(0,len(_data)-1,2):
            data.append(_data[i+1])
            data.append(_data[i])
        return data

    def write_multiple_registers(self, reg_addr, data, tid=0, uid=0):
        _tid        = pack('>H', tid)
        _pid        = pack('>H', 0)
        _uid        = pack('>B', uid)
        _fcode      = pack('>B', 0x10)
        _reg_addr   = pack('>H', reg_addr-1)
        _reg_cnt    = pack('>H', int(len(data)/2))
        _byte_cnt   = pack('>B', len(data))
        _data       = b''
        # rotate big endian 16bit values
        for i in range(0,len(data)-1,2):
            _data += pack('>B', data[i+1])
            _data += pack('>B', data[i])
        _length     = pack('>H', (len(_uid) + len(_fcode) + len(_reg_addr) + len(_reg_cnt) + len(_byte_cnt) + len(_data)))

        msg = _tid + _pid + _length + _uid + _fcode + _reg_addr + _reg_cnt + _byte_cnt + _data

        if DEBUG:
            print("sent:\n{0}".format([hex(b) for b in msg]))


        self.socket.send(msg)

        data = self.socket.recv(12)
        if DEBUG:
            print("recieved data:\n{0}".format([hex(b) for b in data]))
        return [hex(b) for b in data]

So far I've just implemented read_holding_registers and write_multiple_registers beacuse the gateway does only support these two commands and one other. For me those two functions are everything i need, so i will not implement any other commands.

I use the code to read status information from the controller and write bits to trigger actions.