Exemple #1
0
    def test_client_server_count(self):

        client = ModbusProtocol(
            protocol=TestModbusProtocol.CLIENT_PROTOCOL)

        ADDRESS = 'localhost:3502'
        TAGS = (50, 50, 50, 50)

        try:
            server = ModbusProtocol._start_server(self.SERVER_CMD_PATH, ADDRESS, TAGS)
            time.sleep(1.0)

            print('TEST: Write and Read holding registers, offset=4, count=3')
            what = ('HR', 4)
            hrs = [1, 2, 3]
            client._send(what, hrs, ADDRESS, count=3)
            eq_(client._receive(what, ADDRESS, count=3), hrs)
            print('')

            print('TEST: Write and Read holding registers, offset=4, count=3')
            what = ('CO', 7)
            cos = [True, False, False, True, False]
            client._send(what, cos, ADDRESS, count=5)
            eq_(client._receive(what, ADDRESS, count=5), cos)
            print('')

            ModbusProtocol._stop_server(server)

        except Exception as error:
            ModbusProtocol._stop_server(server)
            print 'ERROR test_client_server_count: ', error
            assert False
Exemple #2
0
    def test_send(self):

        client = ModbusProtocol(
            protocol=TestModbusProtocol.CLIENT_PROTOCOL)

        ADDRESS = 'localhost:3502'
        TAGS = (20, 20, 20, 20)
        OFFSET = 10

        try:
            server = ModbusProtocol._start_server(self.SERVER_CMD_PATH, ADDRESS, TAGS)
            time.sleep(1.0)

            print('TEST: Write to holding registers')
            for offset in range(0, OFFSET):
                what = ('HR', offset)
                client._send(what, offset, ADDRESS)
            print('')

            coil = True
            print('TEST: Write to coils')
            for offset in range(0, OFFSET):
                what = ('CO', offset)
                client._send(what, coil, ADDRESS)
                coil = not coil
            print('')

            ModbusProtocol._stop_server(server)

        except Exception as error:
            ModbusProtocol._stop_server(server)
            print 'ERROR test_send: ', error
            assert False
Exemple #3
0
    def test_client_server(self):

        ADDRESS = 'localhost:3502'

        try:
            # NOTE: same instance used as server and client
            modbus = ModbusProtocol(
                protocol=TestModbusProtocol.CLIENT_SERVER_PROTOCOL)
            time.sleep(1.0)

            print('TEST: Write and read coils')
            what = ('CO', 0)
            value = True
            modbus._send(what, value, ADDRESS)
            what = ('CO', 0)
            eq_(modbus._receive(what, ADDRESS), True)

            what = ('CO', 1)
            value = False
            modbus._send(what, value, ADDRESS)
            what = ('CO', 1)
            eq_(modbus._receive(what, ADDRESS), False)
            print('')

            print('TEST: Write and read holding registers')
            for hr in range(10):
                what = ('HR', hr)
                modbus._send(what, hr, ADDRESS)
                what = ('HR', hr)
                eq_(modbus._receive(what, ADDRESS), hr)
            print('')

            print('TEST: Read discrete inputs (init to False)')
            what = ('DI', 0)
            eq_(modbus._receive(what, ADDRESS), False)
            print('')

            print('TEST: Read input registers (init to 0)')
            for ir in range(10):
                what = ('IR', ir)
                eq_(modbus._receive(what, ADDRESS), 0)
            print('')

            ModbusProtocol._stop_server(modbus._server_subprocess)

        except Exception as error:
            ModbusProtocol._stop_server(modbus._server_subprocess)
            print 'ERROR test_client_server: ', error
            assert False
Exemple #4
0
class Device(object):
    """Base class."""

    # TODO: state dict convention (eg: multiple table support?)
    def __init__(self, name, protocol, state, disk={}, memory={}):
        """Init a Device object.

        :param str name: device name
        :param dict protocol: used to set up the network layer API
        :param dict state: used to set up the physical layer API
        :param dict disk: persistent memory
        :param dict memory: main memory

        ``protocol`` (when is not ``None``) is a ``dict`` containing 3 keys:

            - ``name``: addresses a str identifying the protocol name (eg:
              ``enip``)
            - ``mode``: int identifying the server mode (eg: mode equals
              ``1``)
            - ``server``: if ``mode`` equals ``0`` is empty,
                otherwise it addresses a dict containing the server information
                such as its address, and a list of data to serve.

        ``state`` is a ``dict`` containing 2 keys:

            - ``path``: full (LInux) path to the database (eg: /tmp/test.sqlite)
            - ``name``: table name

        Device construction example:

        >>> device = Device(
        >>>     name='dev',
        >>>     protocol={
        >>>         'name': 'enip',
        >>>         'mode': 1,
        >>>         'server': {
        >>>             'address': '10.0.0.1',
        >>>             'tags': (('SENSOR1', 1), ('SENSOR2', 1)),
        >>>             }
        >>>     state={
        >>>         'path': '/path/to/db.sqlite',
        >>>         'name': 'table_name',
        >>>     }
        >>> )

        """

        self._validate_inputs(name, protocol, state, disk, memory)

        self.name = name
        self.state = state
        self.protocol = protocol
        self.memory = memory
        self.disk = disk

        self._init_state()
        self._init_protocol()
        self._start()
        self._stop()

    def _validate_inputs(self, name, protocol, state, disk, memory):

        # name string
        if type(name) is not str:
            raise TypeError('Name must be a string.')
        elif not name:
            raise ValueError('Name string cannot be empty.')

        # state dict
        if type(state) is not dict:
            raise TypeError('State must be a dict.')
        else:
            state_keys = state.keys()
            if (not state_keys) or (len(state_keys) != 2):
                raise KeyError('State must contain 2 keys.')
            else:
                for key in state_keys:
                    if (key != 'path') and (key != 'name'):
                        raise KeyError('%s is an invalid key.' % key)
            state_values = state.values()
            for val in state_values:
                if type(val) is not str:
                    raise TypeError('state values must be strings.')
            # state['path']
            subpath, extension = splitext(state['path'])
            # print 'DEBUG subpath: ', subpath
            # print 'DEBUG extension: ', extension
            if (extension != '.redis') and (extension != '.sqlite'):
                raise ValueError('%s extension not supported.' % extension)
            # state['name']
            if type(state['name']) is not str:
                raise TypeError('State name must be a string.')

        # protocol dict
        if type(protocol) is not dict:
            if protocol is not None:
                raise TypeError('Protocol must be either None or a dict.')
        else:
            protocol_keys = protocol.keys()
            if (not protocol_keys) or (len(protocol_keys) != 3):
                raise KeyError('Protocol must contain 3 keys.')
            else:
                for key in protocol_keys:
                    if ((key != 'name') and (key != 'mode')
                            and (key != 'server')):
                        raise KeyError('%s is an invalid key.' % key)

            # protocol['name']
            if type(protocol['name']) is not str:
                raise TypeError('Protocol name must be a string.')
            else:
                name = protocol['name']
                if (name != 'enip' and name != 'modbus'):
                    raise ValueError('%s protocol not supported.' % protocol)
            # protocol['mode']
            if type(protocol['mode']) is not int:
                raise TypeError('Protocol mode must be a int.')
            else:
                mode = protocol['mode']
                if (mode < 0):
                    raise ValueError('Protocol mode must be positive.')
            # protocol['server'] TODO

            # protocol['client'] TODO after adding it to the API

    def _init_state(self):
        """Bind device to the physical layer API."""

        subpath, extension = splitext(self.state['path'])

        if extension == '.sqlite':
            # TODO: add parametric value filed
            # print 'DEBUG state: ', self.state
            self._state = SQLiteState(self.state)
        elif extension == '.redis':
            # TODO: add parametric key serialization
            self._state = RedisState(self.state)
        else:
            print 'ERROR: %s backend not supported.' % self.state

    # TODO: add optional process name for the server and log location
    def _init_protocol(self):
        """Bind device to network API."""

        if self.protocol is None:
            print 'DEBUG: %s has no networking capabilities.' % self.name
            pass
        else:
            name = self.protocol['name']
            if name == 'enip':
                self._protocol = EnipProtocol(self.protocol)
            elif name == 'modbus':
                self._protocol = ModbusProtocol(self.protocol)
            else:
                print 'ERROR: %s protocol not supported.' % self.protocol

    def _start(self):
        """Start a device."""

        print "TODO _start: please override me"

    def _stop(self):
        """Start a device."""

        print "TODO _stop: please override me"

    def set(self, what, value):
        """Set (write) a physical process state value.

        The ``value`` to be set (Eg: drive an actuator) is identified by the
        ``what`` tuple, and it is assumed to be already initialize. Indeed
        ``set`` is not able to create new physical process values.

        :param tuple what: field[s] identifier[s]
        :param value: value to be setted

        :returns: setted value or ``TypeError`` if ``what`` is not a ``tuple``
        """

        if type(what) is not tuple:
            raise TypeError('Parameter must be a tuple.')
        else:
            return self._state._set(what, value)

    def get(self, what):
        """Get (read) a physical process state value.

        :param tuple what: field[s] identifier[s]

        :returns: gotten value or ``TypeError`` if ``what`` is not a ``tuple``
        """

        if type(what) is not tuple:
            raise TypeError('Parameter must be a tuple.')
        else:
            return self._state._get(what)

    def send(self, what, value, address, **kwargs):
        """Send (write) a value to another network host.

        ``kwargs`` dict is used to pass extra key-value pair according to the
        used protocol.

        :param tuple what: field[s] identifier[s]
        :param value: value to be setted
        :param str address: ``ip[:port]``

        :returns: ``None`` or ``TypeError`` if ``what`` is not a ``tuple``
        """

        if type(what) is not tuple:
            raise TypeError('Parameter must be a tuple.')
        else:
            return self._protocol._send(what, value, address, **kwargs)

    def receive(self, what, address, **kwargs):
        """Receive (read) a value from another network host.

        ``kwargs`` dict is used to pass extra key-value pair according to the
        used protocol.

        :param tuple what: field[s] identifier[s]
        :param str address: ``ip[:port]``

        :returns: received value or ``TypeError`` if ``what`` is not a ``tuple``
        """

        if type(what) is not tuple:
            raise TypeError('Parameter must be a tuple.')
        else:
            return self._protocol._receive(what, address, **kwargs)
Exemple #5
0
class Device(object):

    """Base class."""

    # TODO: state dict convention (eg: multiple table support?)
    def __init__(self, name, protocol, state, disk={}, memory={}):
        """Init a Device object.

        :param str name: device name
        :param dict protocol: used to set up the network layer API
        :param dict state: used to set up the physical layer API
        :param dict disk: persistent memory
        :param dict memory: main memory

        ``protocol`` (when is not ``None``) is a ``dict`` containing 3 keys:

            - ``name``: addresses a str identifying the protocol name (eg:
              ``enip``)
            - ``mode``: int identifying the server mode (eg: mode equals
              ``1``)
            - ``server``: if ``mode`` equals ``0`` is empty,
                otherwise it addresses a dict containing the server information
                such as its address, and a list of data to serve.

        ``state`` is a ``dict`` containing 2 keys:

            - ``path``: full (LInux) path to the database (eg: /tmp/test.sqlite)
            - ``name``: table name

        Device construction example:

        >>> device = Device(
        >>>     name='dev',
        >>>     protocol={
        >>>         'name': 'enip',
        >>>         'mode': 1,
        >>>         'server': {
        >>>             'address': '10.0.0.1',
        >>>             'tags': (('SENSOR1', 1), ('SENSOR2', 1)),
        >>>             }
        >>>     state={
        >>>         'path': '/path/to/db.sqlite',
        >>>         'name': 'table_name',
        >>>     }
        >>> )

        """

        self._validate_inputs(name, protocol, state, disk, memory)

        self.name = name
        self.state = state
        self.protocol = protocol
        self.memory = memory
        self.disk = disk

        self._init_state()
        self._init_protocol()
        self._start()
        self._stop()

    def _validate_inputs(self, name, protocol, state, disk, memory):

        # name string
        if type(name) is not str:
            raise TypeError('Name must be a string.')
        elif not name:
            raise ValueError('Name string cannot be empty.')

        # state dict
        if type(state) is not dict:
            raise TypeError('State must be a dict.')
        else:
            state_keys = state.keys()
            if (not state_keys) or (len(state_keys) != 2):
                raise KeyError('State must contain 2 keys.')
            else:
                for key in state_keys:
                    if (key != 'path') and (key != 'name'):
                        raise KeyError('%s is an invalid key.' % key)
            state_values = state.values()
            for val in state_values:
                if type(val) is not str:
                    raise TypeError('state values must be strings.')
            # state['path']
            subpath, extension = splitext(state['path'])
            # print 'DEBUG subpath: ', subpath
            # print 'DEBUG extension: ', extension
            if (extension != '.redis') and (extension != '.sqlite'):
                raise ValueError('%s extension not supported.' % extension)
            # state['name']
            if type(state['name']) is not str:
                raise TypeError('State name must be a string.')

        # protocol dict
        if type(protocol) is not dict:
            if protocol is not None:
                raise TypeError('Protocol must be either None or a dict.')
        else:
            protocol_keys = protocol.keys()
            if (not protocol_keys) or (len(protocol_keys) != 3):
                raise KeyError('Protocol must contain 3 keys.')
            else:
                for key in protocol_keys:
                    if ((key != 'name') and
                            (key != 'mode') and
                            (key != 'server')):
                        raise KeyError('%s is an invalid key.' % key)

            # protocol['name']
            if type(protocol['name']) is not str:
                raise TypeError('Protocol name must be a string.')
            else:
                name = protocol['name']
                if (name != 'enip' and name != 'modbus'):
                    raise ValueError('%s protocol not supported.' % protocol)
            # protocol['mode']
            if type(protocol['mode']) is not int:
                raise TypeError('Protocol mode must be a int.')
            else:
                mode = protocol['mode']
                if (mode < 0):
                    raise ValueError('Protocol mode must be positive.')
            # protocol['server'] TODO

            # protocol['client'] TODO after adding it to the API

    def _init_state(self):
        """Bind device to the physical layer API."""

        subpath, extension = splitext(self.state['path'])

        if extension == '.sqlite':
            # TODO: add parametric value filed
            # print 'DEBUG state: ', self.state
            self._state = SQLiteState(self.state)
        elif extension == '.redis':
            # TODO: add parametric key serialization
            self._state = RedisState(self.state)
        else:
            print 'ERROR: %s backend not supported.' % self.state

    # TODO: add optional process name for the server and log location
    def _init_protocol(self):
        """Bind device to network API."""

        if self.protocol is None:
            print 'DEBUG: %s has no networking capabilities.' % self.name
            pass
        else:
            name = self.protocol['name']
            if name == 'enip':
                self._protocol = EnipProtocol(self.protocol)
            elif name == 'modbus':
                self._protocol = ModbusProtocol(self.protocol)
            else:
                print 'ERROR: %s protocol not supported.' % self.protocol

    def _start(self):
        """Start a device."""

        print "TODO _start: please override me"

    def _stop(self):
        """Start a device."""

        print "TODO _stop: please override me"

    def set(self, what, value):
        """Set (write) a physical process state value.

        The ``value`` to be set (Eg: drive an actuator) is identified by the
        ``what`` tuple, and it is assumed to be already initialize. Indeed
        ``set`` is not able to create new physical process values.

        :param tuple what: field[s] identifier[s]
        :param value: value to be setted

        :returns: setted value or ``TypeError`` if ``what`` is not a ``tuple``
        """

        if type(what) is not tuple:
            raise TypeError('Parameter must be a tuple.')
        else:
            return self._state._set(what, value)

    def get(self, what):
        """Get (read) a physical process state value.

        :param tuple what: field[s] identifier[s]

        :returns: gotten value or ``TypeError`` if ``what`` is not a ``tuple``
        """

        if type(what) is not tuple:
            raise TypeError('Parameter must be a tuple.')
        else:
            return self._state._get(what)

    def send(self, what, value, address, **kwargs):
        """Send (write) a value to another network host.

        ``kwargs`` dict is used to pass extra key-value pair according to the
        used protocol.

        :param tuple what: field[s] identifier[s]
        :param value: value to be setted
        :param str address: ``ip[:port]``

        :returns: ``None`` or ``TypeError`` if ``what`` is not a ``tuple``
        """

        if type(what) is not tuple:
            raise TypeError('Parameter must be a tuple.')
        else:
            return self._protocol._send(what, value, address, **kwargs)

    def receive(self, what, address, **kwargs):
        """Receive (read) a value from another network host.

        ``kwargs`` dict is used to pass extra key-value pair according to the
        used protocol.

        :param tuple what: field[s] identifier[s]
        :param str address: ``ip[:port]``

        :returns: received value or ``TypeError`` if ``what`` is not a ``tuple``
        """

        if type(what) is not tuple:
            raise TypeError('Parameter must be a tuple.')
        else:
            return self._protocol._receive(what, address, **kwargs)