class ModbusWrapperServer(): def __init__(self, port=1234, sub_topic="modbus_server/write_to_registers", pub_topic="modbus_server/read_from_registers"): """ Creates a Modbus TCP Server object .. note:: The default port for modbus is 502. This modbus server uses port 1234 by default, otherwise superuser rights are required. .. note:: Use "startServer" to start the listener. :param port: Port for the modbus TCP server :type port: int :param sub_topic: ROS topic name for the subscriber that updates the modbus registers :type sub_topic: string :param pub_topic: ROS topic name for the publisher that publishes a message, once there is something written to the writeable modbus registers :type pub_topic: string """ chr = CustomHoldingRegister(ADDRESS_WRITE_START, [17] * 100, sub_topic, pub_topic) self.store = ModbusSlaveContext( di=ModbusSequentialDataBlock(ADDRESS_WRITE_START, [17] * 100), co=ModbusSequentialDataBlock(ADDRESS_WRITE_START, [17] * 100), hr=chr, ir=ModbusSequentialDataBlock(ADDRESS_WRITE_START, [17] * 100)) self.context = ModbusServerContext(slaves=self.store, single=True) self.identity = ModbusDeviceIdentification() self.identity.VendorName = 'Pymodbus' self.identity.ProductCode = 'PM' self.identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' self.identity.ProductName = 'Pymodbus Server' self.identity.ModelName = 'Pymodbus Server' self.identity.MajorMinorRevision = '1.0' self.store.setValues(2, 0, [0] * 1) self.post = Post(self) framer = ModbusSocketFramer self.server = ModbusTcpServer(self.context, framer, self.identity, address=("0.0.0.0", port)) def startServer(self): """ Non blocking call to start the server """ self.post.__startServer() rospy.loginfo("Modbus server started") def __startServer(self): self.server.serve_forever() def stopServer(self): """ Closes the server """ self.server.server_close() self.server.shutdown() def waitForCoilOutput(self, address, timeout=2): """ Blocks for the timeout in seconds (or forever) until the specified address becomes true. Adapt this to your needs :param address: Address of the register that wanted to be read. :type address: int :param timeout: The time in seconds until the function should return latest. :type: float/int """ now = time.time() while True: values = self.store.getValues(1, address, 1) if values[0] is True: return True else: if timeout <= 0 or now + timeout > time.time(): time.sleep(1 / 50) else: return False def setDigitalInput(self, address, values): """ Writes to the digital input of the modbus server :param address: Starting address of the values to write :type: int :param values: List of values to write :type list/boolean/int """ if not values is list: values = [values] self.store.setValues(2, address, values)
class ModbusWrapperServer(): def __init__(self,port=1234,sub_topic="modbus_server/write_to_registers",pub_topic="modbus_server/read_from_registers"): """ Creates a Modbus TCP Server object .. note:: The default port for modbus is 502. This modbus server uses port 1234 by default, otherwise superuser rights are required. .. note:: Use "startServer" to start the listener. :param port: Port for the modbus TCP server :type port: int :param sub_topic: ROS topic name for the subscriber that updates the modbus registers :type sub_topic: string :param pub_topic: ROS topic name for the publisher that publishes a message, once there is something written to the writeable modbus registers :type pub_topic: string """ chr = CustomHoldingRegister(ADDRESS_WRITE_START, [17]*100,sub_topic,pub_topic) self.store = ModbusSlaveContext( di = ModbusSequentialDataBlock(ADDRESS_WRITE_START, [17]*100), co = ModbusSequentialDataBlock(ADDRESS_WRITE_START, [17]*100), hr = chr, ir = ModbusSequentialDataBlock(ADDRESS_WRITE_START, [17]*100)) self.context = ModbusServerContext(slaves=self.store, single=True) self.identity = ModbusDeviceIdentification() self.identity.VendorName = 'Pymodbus' self.identity.ProductCode = 'PM' self.identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' self.identity.ProductName = 'Pymodbus Server' self.identity.ModelName = 'Pymodbus Server' self.identity.MajorMinorRevision = '1.0' self.store.setValues(2,0,[0]*1) self.post = Post(self) framer = ModbusSocketFramer self.server = ModbusTcpServer(self.context, framer, self.identity, address=("0.0.0.0", port)) def startServer(self): """ Non blocking call to start the server """ self.post.__startServer() rospy.loginfo("Modbus server started") def __startServer(self): self.server.serve_forever() def stopServer(self): """ Closes the server """ self.server.server_close() self.server.shutdown() def waitForCoilOutput(self,address,timeout=2): """ Blocks for the timeout in seconds (or forever) until the specified address becomes true. Adapt this to your needs :param address: Address of the register that wanted to be read. :type address: int :param timeout: The time in seconds until the function should return latest. :type: float/int """ now = time.time() while True: values = self.store.getValues(1,address, 1) if values[0] is True: return True else: if timeout <=0 or now + timeout > time.time(): time.sleep(1/50) else: return False def setDigitalInput(self,address,values): """ Writes to the digital input of the modbus server :param address: Starting address of the values to write :type: int :param values: List of values to write :type list/boolean/int """ if not values is list: values = [values] self.store.setValues(2,address,values)
class plcSlave(): def __init__(self, plcIP, plcPort, initialRegisters, plcName): self.plcIP=plcIP self.plcPort=plcPort self.dataStore = 0 self.context = 0 self.identity = 0 self.initialRegisters = initialRegisters self.plcName = plcName self.run_async_server(address=(self.plcIP,self.plcPort)) def run_async_server(self, address): # ----------------------------------------------------------------------- # # initialize your data store # ----------------------------------------------------------------------- # # The datastores only respond to the addresses that they are initialized to # Therefore, if you initialize a DataBlock to addresses from 0x00 to 0xFF, # a request to 0x100 will respond with an invalid address exception. # This is because many devices exhibit this kind of behavior (but not all) # # block = ModbusSequentialDataBlock(0x00, [0]*0xff) # # Continuing, you can choose to use a sequential or a sparse DataBlock in # your data context. The difference is that the sequential has no gaps in # the data while the sparse can. Once again, there are devices that exhibit # both forms of behavior:: # # block = ModbusSparseDataBlock({0x00: 0, 0x05: 1}) # block = ModbusSequentialDataBlock(0x00, [0]*5) # # Alternately, you can use the factory methods to initialize the DataBlocks # or simply do not pass them to have them initialized to 0x00 on the full # address range:: # # store = ModbusSlaveContext(di = ModbusSequentialDataBlock.create()) # store = ModbusSlaveContext() # # Finally, you are allowed to use the same DataBlock reference for every # table or you you may use a seperate DataBlock for each table. # This depends if you would like functions to be able to access and modify # the same data or not:: # # block = ModbusSequentialDataBlock(0x00, [0]*0xff) # store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block) # # The server then makes use of a server context that allows the server to # respond with different slave contexts for different unit ids. By default # it will return the same context for every unit id supplied (broadcast # mode). # However, this can be overloaded by setting the single flag to False # and then supplying a dictionary of unit id to context mapping:: # # slaves = { # 0x01: ModbusSlaveContext(...), # 0x02: ModbusSlaveContext(...), # 0x03: ModbusSlaveContext(...), # } # context = ModbusServerContext(slaves=slaves, single=False) # # The slave context can also be initialized in zero_mode which means that a # request to address(0-7) will map to the address (0-7). The default is # False which is based on section 4.4 of the specification, so address(0-7) # will map to (1-8):: # # store = ModbusSlaveContext(..., zero_mode=True) # ----------------------------------------------------------------------- # self.dataStore = ModbusSlaveContext( di=ModbusSequentialDataBlock(0, [17]*100), co=ModbusSequentialDataBlock(0, [17]*100), hr=ModbusSequentialDataBlock(0, [17]*100), ir=ModbusSequentialDataBlock(0, [17]*100)) self.context = ModbusServerContext(slaves=self.dataStore, single=True) # ----------------------------------------------------------------------- # # initialize the server information # ----------------------------------------------------------------------- # # If you don't set this or any fields, they are defaulted to empty strings. # ----------------------------------------------------------------------- # self.identity = ModbusDeviceIdentification() self.identity.VendorName = 'Pymodbus' self.identity.ProductCode = 'PM' self.identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' self.identity.ProductName = 'Pymodbus Server' self.identity.ModelName = 'Pymodbus Server' self.identity.MajorMinorRevision = '1.0' # ----------------------------------------------------------------------- # # run the server (listener) # ----------------------------------------------------------------------- # framer = ModbusSocketFramer factory = ModbusServerFactory(self.context, framer, self.identity) reactor.listenTCP(address[1], factory, interface=address[0]) def start(self): coil_list = 1 holdregister = 3 self.setCoils(0x00,[1,0,0,0,0,0,0,0,0,0,0,0,0]) self.setHoldingRegisters(0x00,self.initialRegisters) threading.Thread(target = self.operateLightTimer).start() def getHoldingRegisters(self, startByte, bytesCount): return self.dataStore.getValues(3, startByte,count=bytesCount) def getCoils(self, startBit, bitCount): return self.dataStore.getValues(1,startBit,count=bitCount) def setHoldingRegisters(self,address,values): self.dataStore.setValues(3,address,values) def setCoils(self, address, values): self.dataStore.setValues(1,address,values) def changeLightColor(self,northsouth, color): registers = self.getHoldingRegisters(0x00,4) if northsouth == 1: registers[0] = color registers[2] = color else: registers[1] = color registers[3] = color self.dataStore.setValues(3,0x00,registers) def operateLightTimer(self): ################################# # coils[0] = 1 - machine initialization (move to normal operations) # coils[1] = 1 - machine moved to normal operations # coils[2] = 1 - east/west side is green or yellow # coils[3] = 1 - north/south side is green yellow # coils[4] = 1 - put the machine into maintenancemode (ALL FLASHING RED LIGHTS) # coils[5] = 1 - machine moved to mainteance mode # coils[6] = 1 - puts the machine into TEST Mode (ALL LIGHTS ON) # coils[7] = 1 - machine moved to TEST mode # holdingRegister[0,2] - north/south light state 0 = red, 1 = yellow, 2 = green, 3 = ALL OFF, 4 = ALL ON # holdingRegister[1,3] - east/west light state # holdingRegister [4] - timer/counter # holdingRegisters [5,6,7] - timer values that cause an increment in logic ################ coils = self.getCoils(0,10) #this is the main operation loop while True: coils = self.getCoils(0,10) regs = self.getHoldingRegisters(0,10) if coils[0] == 1: #initialize NORMAL OPS print("The traffic light at: " + str(self.plcName) + " is starting") self.setCoils(0x00,[0,1,1,0,0,0,0,0,0]) self.setHoldingRegisters(0x00,[0,2,0,2,0,10,12,14]) elif coils[4] == 1: #move into MAINTENANCE print("The traffic light at: " + str(self.plcName) + " is going into MAINTENANCE") self.setCoils(0x00,[0,0,0,0,0,1,0,0,0]) self.setHoldingRegisters(0x00,[0,0,0,0,0]) elif coils[6] == 1: #move into TEST print("The traffic light at: " + str(self.plcName) + " is going into TEST mode") self.setCoils(0x00,[0,0,0,0,0,0,0,1,0]) self.setHoldingRegisters(0x00,[0,0,0,0,0]) counter = regs[4] print(self.plcName + "....Coils: " + str(coils[0:10]) +"\t\tRegisters: " + str(regs[0:10])) if coils[1] == True: ##standard traffic light operation self.setHoldingRegisters(4,[counter + 1]) if counter == regs[5]: #change the green to yellow sys.stdout.write("X") sys.stdout.flush() if coils[2] == 1: self.changeLightColor(0, 1) #change the east west side to yellow if coils[3] == 1: self.changeLightColor(1,1) #change the north/south side to yellow elif counter == regs[6]: #everyone goes to red sys.stdout.write("X") sys.stdout.flush() self.changeLightColor(1,0) self.changeLightColor(0,0) elif counter == regs[7]: #change the red light to green (for the favored side) sys.stdout.write("X") sys.stdout.flush() if coils[2] == 1: #east/west was green, change it to red self.setCoils(0x02,[0,1]) self.changeLightColor(1,2) elif coils[3] == 1: #north/south was green, change it to red self.setCoils(0x02,[1,0]) self.changeLightColor(0,2) self.setHoldingRegisters(0x04,[0]) else: sys.stdout.write(".") sys.stdout.flush() time.sleep(1) elif coils[5] == True: ##maintenance if regs[0] == 0: #displaying a red light, make it flash self.setHoldingRegisters(0,[3,3,3,3]) elif regs[0] == 3: #blank light, change to red self.setHoldingRegisters(0,[0,0,0,0]) else: self.setHoldingRegisters(0,[3,3,3,3]) time.sleep(1) elif coils[7] == True: #test mode if regs[0] == 3: #displaying a red light, make it flash self.setHoldingRegisters(0,[4,4,4,4]) elif regs[0] == 4: #blank light, change to red self.setHoldingRegisters(0,[3,3,3,3]) else: self.setHoldingRegisters(0,[3,3,3,3]) time.sleep(1)
class ModbusWrapperServer(): def __init__(self,port=1234): """ Creates a Modbus TCP Server object .. note:: The default port for modbus is 502. This modbus server uses port 1234 by default, otherwise superuser rights are required. .. note:: Use "startServer" to start the listener. :param port: Port for the modbus TCP server :type port: int """ # chr = CustomHoldingRegister(ADDRESS_WRITE_START, [16]*100) # cdi = CustomHoldingRegister(ADDRESS_WRITE_START, [16]*100) self.onShutdown=False; self.state_changed = Signal() cco = CustomHoldingRegister(ADDRESS_WRITE_START, [16]*100) self.store = ModbusSlaveContext( di = ModbusSequentialDataBlock(ADDRESS_WRITE_START, [16]*100), co = cco, hr = ModbusSequentialDataBlock(ADDRESS_WRITE_START, [16]*100), ir = ModbusSequentialDataBlock(ADDRESS_WRITE_START, [16]*100)) self.context = ModbusServerContext(slaves=self.store, single=True) self.identity = ModbusDeviceIdentification() self.identity.VendorName = 'Pymodbus' self.identity.ProductCode = 'PM' self.identity.VendorUrl = 'http://github.com/bashwork/pymodbus/' self.identity.ProductName = 'Pymodbus Server' self.identity.ModelName = 'Pymodbus Server' self.identity.MajorMinorRevision = '1.0' self.store.setValues(2,0,[0]*1) self.post = Post(self) framer = ModbusSocketFramer self.server = ModbusTcpServer(self.context, framer, self.identity, address=("0.0.0.0", port)) def startServer(self): """ Non blocking call to start the server """ self.post._startServer() def _startServer(self): print "Modbus server started" self.server.serve_forever() def stopServer(self): """ Closes the server """ self.onShutdown = True self.server.server_close() self.server.shutdown() def waitForCoilOutput(self,address,timeout=2): now = time.time() while not self.onShutdown: values = self.store.getValues(1,address, 1) if values[0] is True: return True else: if timeout > 0 and now + timeout > time.time(): time.sleep(1/50) else: return False def stateChangeListener(self,address): self.post.__stateChangeListener(address) def __stateChangeListener(self,address): old_state = 0; while not self.onShutdown: value = self.store.getValues(1,address, 1)[0] if old_state != value: old_state = value self.state_changed(address,value) #print "state has changed" time.sleep(1/50) def setDigitalInput(self,address,values): if not values is list: values = [values] self.store.setValues(2,address,values) def triggerInput(self,address,timeout=0.5): self.setDigitalInput(address, True) self.post.resetInput(address, timeout) def resetInput(self,address,timeout): time.sleep(timeout) self.setDigitalInput(address, False)