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]
|