File: remote_server_context.py

package info (click to toggle)
pymodbus 2.1.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 2,704 kB
  • sloc: python: 17,594; makefile: 83; sh: 8
file content (208 lines) | stat: -rw-r--r-- 7,878 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
"""
Although there is a remote server context already in the main library,
it works under the assumption that users would have a server context
of the following form::

    server_context = {
        0x00: client('host1.something.com'),
        0x01: client('host2.something.com'),
        0x02: client('host3.something.com')
    }

This example is how to create a server context where the client is
pointing to the same host, but the requested slave id is used as the
slave for the client::

    server_context = {
        0x00: client('host1.something.com', 0x00),
        0x01: client('host1.something.com', 0x01),
        0x02: client('host1.something.com', 0x02)
    }
"""
from pymodbus.exceptions import NotImplementedException
from pymodbus.interfaces import IModbusSlaveContext

# -------------------------------------------------------------------------- #
# Logging
# -------------------------------------------------------------------------- #

import logging
_logger = logging.getLogger(__name__)

# -------------------------------------------------------------------------- #
# Slave Context
# -------------------------------------------------------------------------- #
# Basically we create a new slave context for the given slave identifier so
# that this slave context will only make requests to that slave with the
# client that the server is maintaining.
# -------------------------------------------------------------------------- #


class RemoteSingleSlaveContext(IModbusSlaveContext):
    """ This is a remote server context that allows one
    to create a server context backed by a single client that
    may be attached to many slave units. This can be used to
    effectively create a modbus forwarding server.
    """

    def __init__(self, context, unit_id):
        """ Initializes the datastores

        :param context: The underlying context to operate with
        :param unit_id: The slave that this context will contact
        """
        self.context = context
        self.unit_id = unit_id

    def reset(self):
        """ Resets all the datastores to their default values """
        raise NotImplementedException()

    def validate(self, fx, address, count=1):
        """ Validates the request to make sure it is in range

        :param fx: The function we are working with
        :param address: The starting address
        :param count: The number of values to test
        :returns: True if the request in within range, False otherwise
        """
        _logger.debug("validate[%d] %d:%d" % (fx, address, count))
        result = self.context.get_callbacks[self.decode(fx)](address,
                                                             count,
                                                             self.unit_id)
        return not result.isError()

    def getValues(self, fx, address, count=1):
        """ Get `count` values from datastore

        :param fx: The function we are working with
        :param address: The starting address
        :param count: The number of values to retrieve
        :returns: The requested values from a:a+c
        """
        _logger.debug("get values[%d] %d:%d" % (fx, address, count))
        result = self.context.get_callbacks[self.decode(fx)](address,
                                                             count,
                                                             self.unit_id)
        return self.__extract_result(self.decode(fx), result)

    def setValues(self, fx, address, values):
        """ Sets the datastore with the supplied values

        :param fx: The function we are working with
        :param address: The starting address
        :param values: The new values to be set
        """
        _logger.debug("set values[%d] %d:%d" % (fx, address, len(values)))
        self.context.set_callbacks[self.decode(fx)](address,
                                                    values,
                                                    self.unit_id)

    def __str__(self):
        """ Returns a string representation of the context

        :returns: A string representation of the context
        """
        return "Remote Single Slave Context(%s)" % self.unit_id

    def __extract_result(self, fx, result):
        """ A helper method to extract the values out of
        a response. The future api should make the result
        consistent so we can just call `result.getValues()`.

        :param fx: The function to call
        :param result: The resulting data
        """
        if not result.isError():
            if fx in ['d', 'c']:
                return result.bits
            if fx in ['h', 'i']:
                return result.registers
        else:
            return result

# -------------------------------------------------------------------------- #
# Server Context
# -------------------------------------------------------------------------- #
# Think of this as simply a dictionary of { unit_id: client(req, unit_id) }
# -------------------------------------------------------------------------- #


class RemoteServerContext(object):
    """ This is a remote server context that allows one
    to create a server context backed by a single client that
    may be attached to many slave units. This can be used to
    effectively create a modbus forwarding server.
    """

    def __init__(self, client):
        """ Initializes the datastores

        :param client: The client to retrieve values with
        """
        self.get_callbacks = {
            'd': lambda a, c, s: client.read_discrete_inputs(a, c, s),
            'c': lambda a, c, s: client.read_coils(a, c, s),
            'h': lambda a, c, s: client.read_holding_registers(a, c, s),
            'i': lambda a, c, s: client.read_input_registers(a, c, s),
        }
        self.set_callbacks = {
            'd': lambda a, v, s: client.write_coils(a, v, s),
            'c': lambda a, v, s: client.write_coils(a, v, s),
            'h': lambda a, v, s: client.write_registers(a, v, s),
            'i': lambda a, v, s: client.write_registers(a, v, s),
        }
        self._client = client
        self.slaves = {}  # simply a cache

    def __str__(self):
        """ Returns a string representation of the context

        :returns: A string representation of the context
        """
        return "Remote Server Context(%s)" % self._client

    def __iter__(self):
        """ Iterater over the current collection of slave
        contexts.

        :returns: An iterator over the slave contexts
        """
        # note, this may not include all slaves
        return iter(self.slaves.items())

    def __contains__(self, slave):
        """ Check if the given slave is in this list

        :param slave: slave The slave to check for existance
        :returns: True if the slave exists, False otherwise
        """
        # we don't want to check the cache here as the
        # slave may not exist yet or may not exist any
        # more. The best thing to do is try and fail.
        return True

    def __setitem__(self, slave, context):
        """ Used to set a new slave context

        :param slave: The slave context to set
        :param context: The new context to set for this slave
        """
        raise NotImplementedException()  # doesn't make sense here

    def __delitem__(self, slave):
        """ Wrapper used to access the slave context

        :param slave: The slave context to remove
        """
        raise NotImplementedException()  # doesn't make sense here

    def __getitem__(self, slave):
        """ Used to get access to a slave context

        :param slave: The slave context to get
        :returns: The requested slave context
        """
        if slave not in self.slaves:
            self.slaves[slave] = RemoteSingleSlaveContext(self, slave)
        return self.slaves[slave]