00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042 import rospy
00043 from post_threading import Post
00044 from std_msgs.msg import Int32MultiArray as HoldingRegister
00045
00046
00047 try:
00048 from pymodbus.server.sync import ModbusSocketFramer, ModbusTcpServer
00049 from pymodbus.device import ModbusDeviceIdentification
00050 from pymodbus.datastore import ModbusSequentialDataBlock
00051 from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
00052 except Exception,e:
00053 print "-- INSTALL DEPENDENCIES -- "
00054 print "sudo apt-get install python-pymodbus"
00055 print "or from source:"
00056 print "git clone git@github.com:bashwork/pymodbus.git"
00057 print "cd pymodbus"
00058 print "sudo python setup.py install"
00059 print "and"
00060 print "sudo apt-get install python-pyasn1 python-twisted-conch"
00061 print e
00062 exit()
00063
00064 ADDRESS_WRITE_START = 40001
00065 ADDRESS_READ_START = 40021
00066
00067 class CustomHoldingRegister(ModbusSequentialDataBlock):
00068 def __init__(self,address,value,sub_topic,pub_topic):
00069 """
00070 Creates a custom holding register to add a publisher and subscriber to the modbus server
00071
00072 :param address: The starting address of the holding register
00073 :type address: int
00074 :param values: The initial values for the holding registers
00075 :type values: list[int]
00076 :param sub_topic: ROS topic name for the subscriber that updates the modbus registers
00077 :type sub_topic: string
00078 :param pub_topic: ROS topic name for the publisher that publishes a message, once there is something written to the writeable modbus registers
00079 :type pub_topic: string
00080 """
00081
00082 super(CustomHoldingRegister,self).__init__(address,value)
00083 self.reading_start = ADDRESS_READ_START
00084 self.writing_start = ADDRESS_WRITE_START
00085 self.sub = rospy.Subscriber(sub_topic,HoldingRegister,self.__updateWriteableRegisters,queue_size=500)
00086 self.pub = rospy.Publisher(pub_topic,HoldingRegister,queue_size=500)
00087
00088
00089 def setValues(self, address, value):
00090 """
00091 Sets the requested values to the holding registers
00092 and publishes they new values on a rostopic
00093
00094 :param address: The starting address
00095 :type address: int
00096 :param values: The new values to be set
00097 :type values: list[int]
00098 """
00099
00100
00101
00102 ModbusSequentialDataBlock.setValues(self,address, value)
00103 if address >= self.reading_start:
00104 msg = HoldingRegister()
00105 msg.data = value
00106 self.pub.publish(msg)
00107
00108 def __updateWriteableRegisters(self,msg):
00109 if len(msg.data) > self.reading_start+1:
00110 rospy.logwarn("Message to long. Shorten it or it will be ignored")
00111 self.setValues(self.writing_start, list(msg.data))
00112
00113
00114
00115 class ModbusWrapperServer():
00116 def __init__(self,port=1234,sub_topic="modbus_server/write_to_registers",pub_topic="modbus_server/read_from_registers"):
00117 """
00118 Creates a Modbus TCP Server object
00119 .. note:: The default port for modbus is 502. This modbus server uses port 1234 by default, otherwise superuser rights are required.
00120
00121 .. note:: Use "startServer" to start the listener.
00122
00123 :param port: Port for the modbus TCP server
00124 :type port: int
00125 :param sub_topic: ROS topic name for the subscriber that updates the modbus registers
00126 :type sub_topic: string
00127 :param pub_topic: ROS topic name for the publisher that publishes a message, once there is something written to the writeable modbus registers
00128 :type pub_topic: string
00129
00130 """
00131 chr = CustomHoldingRegister(ADDRESS_WRITE_START, [17]*100,sub_topic,pub_topic)
00132 self.store = ModbusSlaveContext(
00133 di = ModbusSequentialDataBlock(ADDRESS_WRITE_START, [17]*100),
00134 co = ModbusSequentialDataBlock(ADDRESS_WRITE_START, [17]*100),
00135 hr = chr,
00136 ir = ModbusSequentialDataBlock(ADDRESS_WRITE_START, [17]*100))
00137 self.context = ModbusServerContext(slaves=self.store, single=True)
00138
00139 self.identity = ModbusDeviceIdentification()
00140 self.identity.VendorName = 'Pymodbus'
00141 self.identity.ProductCode = 'PM'
00142 self.identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
00143 self.identity.ProductName = 'Pymodbus Server'
00144 self.identity.ModelName = 'Pymodbus Server'
00145 self.identity.MajorMinorRevision = '1.0'
00146
00147 self.store.setValues(2,0,[0]*1)
00148 self.post = Post(self)
00149 framer = ModbusSocketFramer
00150 self.server = ModbusTcpServer(self.context, framer, self.identity, address=("0.0.0.0", port))
00151
00152 def startServer(self):
00153 """
00154 Non blocking call to start the server
00155 """
00156 self.post.__startServer()
00157 rospy.loginfo("Modbus server started")
00158
00159 def __startServer(self):
00160 self.server.serve_forever()
00161
00162 def stopServer(self):
00163 """
00164 Closes the server
00165 """
00166 self.server.server_close()
00167 self.server.shutdown()
00168
00169 def waitForCoilOutput(self,address,timeout=2):
00170 """
00171 Blocks for the timeout in seconds (or forever) until the specified address becomes true. Adapt this to your needs
00172 :param address: Address of the register that wanted to be read.
00173 :type address: int
00174 :param timeout: The time in seconds until the function should return latest.
00175 :type: float/int
00176 """
00177 now = time.time()
00178 while True:
00179 values = self.store.getValues(1,address, 1)
00180 if values[0] is True:
00181 return True
00182 else:
00183 if timeout <=0 or now + timeout > time.time():
00184 time.sleep(1/50)
00185 else:
00186 return False
00187
00188 def setDigitalInput(self,address,values):
00189 """
00190 Writes to the digital input of the modbus server
00191 :param address: Starting address of the values to write
00192 :type: int
00193 :param values: List of values to write
00194 :type list/boolean/int
00195 """
00196 if not values is list:
00197 values = [values]
00198 self.store.setValues(2,address,values)
00199
00200