modbus_wrapper_client.py
Go to the documentation of this file.
00001 
00002 ########################################################################### 
00003 # This software is graciously provided by HumaRobotics 
00004 # under the BSD License on
00005 # github: https://github.com/Humarobotics/modbus_wrapper
00006 # HumaRobotics is a trademark of Generation Robots.
00007 # www.humarobotics.com 
00008 #
00009 # Copyright (c) 2013, Generation Robots.
00010 # All rights reserved.
00011 # www.generationrobots.com
00012 #
00013 # This wrapper package is based on the pymodbus library developed by:
00014 # Galen Collins
00015 # github: https://github.com/bashwork/pymodbus
00016 #   
00017 # Redistribution and use in source and binary forms, with or without 
00018 # modification, are permitted provided that the following conditions are met:
00019 # 
00020 # 1. Redistributions of source code must retain the above copyright notice,
00021 #  this list of conditions and the following disclaimer.
00022 # 
00023 # 2. Redistributions in binary form must reproduce the above copyright notice,
00024 #  this list of conditions and the following disclaimer in the documentation 
00025 #  and/or other materials provided with the distribution.
00026 # 
00027 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00028 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
00029 # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
00030 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
00031 # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
00032 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
00033 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
00034 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
00035 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
00036 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
00037 # THE POSSIBILITY OF SUCH DAMAGE. 
00038 # 
00039 # The views and conclusions contained in the software and documentation are 
00040 # those of the authors and should not be interpreted as representing official 
00041 # policies, either expressed or implied, of the FreeBSD Project.
00042 import rospy
00043 try:
00044     from pymodbus.client.sync import ModbusTcpClient
00045 except Exception,e:
00046     print "pymodbus does not seem to be installed.\nInstall it by:\nsudo apt-get install python-pymodbus"
00047     print e
00048     exit()
00049 from std_msgs.msg import Int32MultiArray as HoldingRegister
00050 from post_threading import Post
00051 from threading import Lock
00052 
00053 NUM_REGISTERS = 20
00054 ADDRESS_READ_START = 40000
00055 ADDRESS_WRITE_START = 40020
00056 
00057 class ModbusWrapperClient():
00058     """
00059         Wrapper that integrates python modbus into standardized ros msgs.
00060         The wrapper is able to read from and write to a standard modbus tcp/ip server.
00061     """
00062     def __init__(self,host,port=502,rate=50,reset_registers=True,sub_topic="modbus_wrapper/output",pub_topic="modbus_wrapper/input"):
00063         """
00064             Use subscribers and publisher to communicate with the modbus server. Check the scripts for example code.
00065             :param host: Contains the IP adress of the modbus server
00066             :type host: string
00067             :param port: The port number, where the modbus server is runnning
00068             :type port: integer
00069             :param rate: How often the registers on the modbusserver should be read per second
00070             :type rate: float
00071             :param reset_registers: Defines if the holding registers should be reset to 0 after they have been read. Only possible if they are writeable
00072             :type reset_registers: bool
00073         """
00074         try:
00075             self.client = ModbusTcpClient(host,port)
00076         except Exception, e:
00077             rospy.logwarn("Could not get a modbus connection to the host modbus. %s", str(e))
00078             raise e
00079             return
00080         self.__rate = rate
00081         self.__reading_delay = 1/rate
00082         self.post = Post(self)
00083         
00084         self.__reset_registers = reset_registers
00085         self.__reading_register_start = 0
00086         self.__num_reading_registers = 20
00087 #         self.input_size = 16
00088         self.__input = HoldingRegister()
00089         self.__input.data = [0 for i in xrange(self.__num_reading_registers )]
00090 
00091         self.__writing_registers_start = ADDRESS_WRITE_START
00092         self.__num_writing_registers =  20
00093 #         self.output_size = 16 
00094         self.__output = [None for i in range(self.__num_writing_registers)]
00095         
00096         self.__last_output_time = rospy.get_time()
00097         self.__mutex = Lock()
00098         
00099         self.__sub = rospy.Subscriber(sub_topic,HoldingRegister,self.__updateModbusOutput,queue_size=500)
00100         self.__pub = rospy.Publisher(pub_topic,HoldingRegister,queue_size=500, latch=True)
00101         
00102          
00103         
00104         rospy.on_shutdown(self.closeConnection)
00105     
00106     def startListening(self):
00107         """
00108             Non blocking call for starting the listener for the readable modbus server registers 
00109         """
00110         #start reading the modbus
00111         self.post.__updateModbusInput()
00112         
00113     def stopListening(self):
00114         """
00115             Stops the listener loop
00116         """
00117         self.stop_listener = True
00118         while not rospy.is_shutdown() and self.listener_stopped is False:
00119             rospy.sleep(0.01)
00120         
00121     def setReadingRegisters(self,start,num_registers):
00122         """
00123             Sets the start address of the registers which should be read and their number
00124             :param start: First register that is readable
00125             :type start: int
00126             :param num_registers: Amount of readable registers
00127             :type num_registers: int
00128         """
00129         self.__reading_register_start = start
00130         self.__num_reading_registers = num_registers
00131 
00132     def setWritingRegisters(self,start,num_registers):
00133         """
00134             Sets the start address of the registers which are writeable and their number
00135             :param start: First register that is writeable
00136             :type start: int
00137             :param num_registers: Amount of writeable registers
00138             :type num_registers: int
00139         """
00140         self.__writing_registers_start = start
00141         self.__num_writing_registers = num_registers
00142     
00143     def getReadingRegisters(self):
00144         """
00145             :return: Returns the first address of the readable registers and the number of registers
00146             :rtype: int,int
00147         """
00148         return self.__reading_register_start,self.__num_reading_registers
00149         
00150     def getWritingRegisters(self):
00151         """
00152             :return: Returns the first address of the writeable registers and the number of registers
00153             :rtype: int,int
00154         """
00155         return self.__writing_registers_start,self.__num_writing_registers
00156         
00157     def __updateModbusInput(self,delay=0):
00158         """                
00159             Loop that is listening to the readable modbus registers and publishes it on a topic
00160             :param delay: The delay time until the loop starts
00161             :type delay: float 
00162         """
00163         rospy.sleep(delay)
00164         self.listener_stopped = False
00165         self.stop_listener = False
00166         update = True
00167         while not rospy.is_shutdown() and self.stop_listener is False:
00168             try: 
00169                 if not rospy.is_shutdown() :
00170                     tmp =  self.readRegisters()
00171                     if tmp is None:
00172                         rospy.sleep(2)
00173                         continue
00174                     # rospy.logwarn("__updateModbusInput tmp is %s ", str(tmp))
00175                     # rospy.logwarn("__updateModbusInput self.__input.data is %s ", str(self.__input.data))
00176 
00177                     if tmp != self.__input.data:
00178                         update = True
00179                         self.__input.data = tmp
00180                     else:
00181                         update = False 
00182             except Exception,e:
00183                 rospy.logwarn("Could not read holding register. %s", str(e))
00184                 raise e
00185                 rospy.sleep(2)
00186         
00187             if update:
00188                 if self.__pub.get_num_connections() > 0:
00189                     try:
00190                         self.__pub.publish(self.__input)
00191                     except Exception,e:
00192                         rospy.logwarn("Could not publish message. Exception: %s",str(e))
00193                         raise e
00194             rospy.Rate(self.__rate).sleep()
00195         self.listener_stopped = True
00196     
00197     def __updateModbusOutput(self,msg):
00198         """
00199             Callback from the subscriber to update the writeable modbus registers
00200             :param msg: value of the new registers
00201             :type msg: std_msgs.Int32MultiArray
00202         """
00203         output_changed = False
00204         for index in xrange(self.__num_writing_registers):
00205             if self.__output[index] != msg.data[index]:
00206                 output_changed = True
00207                 break
00208         if not output_changed:
00209             return
00210         self.__writeRegisters(self.__writing_registers_start,msg.data)
00211         
00212     def __writeRegisters(self,address,values):
00213         """
00214             Writes modbus registers
00215             :param address: First address of the values to write
00216             :type address: int
00217             :param values: Values to write
00218             :type values: list
00219         """
00220         with self.__mutex:
00221             try:
00222                 if not rospy.is_shutdown() :
00223     #                 print "writing address",address,"value"
00224                     self.client.write_registers(address, values)
00225                     self.output = values
00226             except Exception, e:
00227                 rospy.logwarn("Could not write values %s to address %d. Exception %s",str(values),address, str(e))
00228                 raise e
00229 
00230     def readRegisters(self,address=None,num_registers=None):
00231         """
00232             Reads modbus registers
00233             :param address: First address of the registers to read
00234             :type address: int
00235             :param num_registers: Amount of registers to read
00236             :type num_registers: int
00237         """
00238         if address is None:
00239             address = self.__reading_register_start
00240         if num_registers is None:
00241             num_registers = self.__num_reading_registers
00242 
00243         tmp = None
00244         with self.__mutex:            
00245             try:
00246                 tmp = self.client.read_holding_registers(address,num_registers).registers
00247             except Exception, e:
00248                 rospy.logwarn("Could not read on address %d. Exception: %s",address,str(e))
00249                 raise e
00250             
00251             
00252             if self.__reset_registers:
00253                 try:
00254                     self.client.write_registers(address, [0 for i in xrange(num_registers)])
00255                 except Exception, e:
00256                     rospy.logwarn("Could not write to address %d. Exception: %s", address,str(e))
00257                     raise e
00258             
00259         return tmp
00260     
00261     def setOutput(self,address,value,timeout=0):
00262         """
00263             Directly write one register
00264             :param address: The exact register address to write
00265             :type address: int
00266             :param value: What is written in the register
00267             :type value: int
00268             :param timeout: If set, the register is set after this time to 0
00269             :type timeout: float
00270         """
00271         if not type(value) is list:
00272             value = [int(value)]
00273         self.__writeRegisters(address, value)
00274         if timeout > 0:
00275             self.post.__reset(address,timeout)
00276             
00277     def __reset(self,address,timeout):
00278         """
00279             Resets a register to 0 after a specific amount of time
00280             :param address: The register address to reset
00281             :type address: int
00282             :param timeout: The delay after the register is reset
00283             :type timeout: float
00284         """
00285         rospy.sleep(timeout)
00286         self.__writeRegisters(address,[0])
00287     
00288     def closeConnection(self):
00289         """
00290             Closes the connection to the modbus
00291         """
00292         self.client.close()
00293 
00294 
00295     


modbus
Author(s): Sven Bock
autogenerated on Thu Jun 6 2019 17:39:35